summaryrefslogtreecommitdiffstats
path: root/src/Authoring/Qt3DStudio/Palettes
diff options
context:
space:
mode:
Diffstat (limited to 'src/Authoring/Qt3DStudio/Palettes')
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp43
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h42
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp235
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h77
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp1203
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h231
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml468
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml169
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp109
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h79
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp276
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h97
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml87
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml51
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml55
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml91
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml59
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml55
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml63
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml56
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml62
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml60
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml290
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml241
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml80
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml98
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml60
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml67
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml64
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp255
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h108
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp130
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h102
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp86
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h57
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml92
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml98
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp630
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h122
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml212
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml72
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp50
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h46
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp137
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h70
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp351
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h78
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml48
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml47
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h211
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml72
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp73
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h51
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp152
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h76
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h54
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp1973
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h258
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp930
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h186
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml1289
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp45
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h52
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml69
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp152
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h61
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml67
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp67
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h47
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp148
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h80
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp199
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h76
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml196
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp177
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h108
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp534
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h147
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp225
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h64
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp52
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h57
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp51
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h57
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp50
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp120
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h65
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml72
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp138
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h69
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp113
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h73
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui113
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp242
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h85
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp104
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h62
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp294
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/PaletteManager.h113
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui131
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp65
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h65
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp105
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h67
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui120
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp197
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h81
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui113
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp231
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h64
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp1372
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h166
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp534
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h149
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml317
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp75
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h53
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp468
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h104
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp621
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h154
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml416
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h61
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp103
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h65
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp107
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h62
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h72
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h75
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h181
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h73
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h73
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp79
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h76
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp254
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h79
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp210
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h82
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp66
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h65
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h118
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp44
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h68
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h63
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp90
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h74
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h42
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp1164
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h205
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp470
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h114
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp223
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h88
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp222
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h90
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp92
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h104
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp241
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h74
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp130
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h61
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp589
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h102
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp414
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h87
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp451
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h84
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h50
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp84
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h52
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h81
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp80
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h60
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp1199
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h174
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp52
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h48
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp1292
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h179
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp59
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp130
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h55
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp98
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h63
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp104
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h58
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp1035
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h126
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp151
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h70
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp275
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h88
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp100
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h54
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp1337
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h228
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp441
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h101
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp175
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h76
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp219
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h73
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp44
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h57
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp456
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h117
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp176
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h72
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp46
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h49
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml81
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml203
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml160
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml41
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml48
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml45
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml118
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml50
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml54
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml52
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp208
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h69
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp160
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h69
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp161
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h80
-rw-r--r--src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui107
232 files changed, 41563 insertions, 0 deletions
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp
new file mode 100644
index 00000000..1e194d79
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2005 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+
+#include "ActionContextMenu.h"
+#include "StudioClipboard.h"
+
+CActionContextMenu::CActionContextMenu(QList<QAction *> actions, QWidget *parent)
+ : QMenu(parent)
+{
+ addActions(actions);
+}
+
+CActionContextMenu::~CActionContextMenu()
+{
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h
new file mode 100644
index 00000000..99d785aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2005 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_ACTION_CONTEXT_MENU_H
+#define INCLUDED_ACTION_CONTEXT_MENU_H 1
+
+#include <QtWidgets/qmenu.h>
+
+class CActionContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ explicit CActionContextMenu(QList<QAction *> actions, QWidget *parent = nullptr);
+ virtual ~CActionContextMenu();
+};
+#endif // INCLUDED_ACTION_CONTEXT_MENU_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp
new file mode 100644
index 00000000..556215de
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ActionModel.h"
+
+#include "Qt3DSCommonPrecompile.h"
+#include "ClientDataModelBridge.h"
+#include "CmdDataModelActionSetValue.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "Qt3DSDMActionSystem.h"
+#include "Qt3DSDMStudioSystem.h"
+
+ActionModel::ActionModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+void ActionModel::setInstanceHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle)
+{
+ beginResetModel();
+ m_handle = handle;
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ m_actions.clear();
+ if (handle.Valid()) {
+ doc->GetStudioSystem()->GetActionSystem()->GetActions(doc->GetActiveSlide(),
+ handle, m_actions);
+ }
+
+ endResetModel();
+}
+
+QHash<int, QByteArray> ActionModel::roleNames() const
+{
+ auto names = QAbstractItemModel::roleNames();
+ names.insert(DescriptionRole, "description");
+ names.insert(VisibleRole, "visible");
+
+ return names;
+}
+
+
+int ActionModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return int(m_actions.size());
+}
+
+QVariant ActionModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(), index.parent()))
+ return {};
+
+ const auto action = m_actions.at(index.row());
+ auto system = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ if (action.Valid()) {
+ auto actionCore = system->GetActionCore();
+
+ // Ensure the handle is still valid on the back-end, as some undo scenarios may cause this
+ // function to be called for already deleted actions
+ if (actionCore->HandleValid(action)) {
+ switch (role)
+ {
+ case DescriptionRole:
+ return actionString(action);
+ case VisibleRole:
+ return system->GetActionSystem()->GetActionEyeballValue(activeSlide(), action);
+ default:
+ return {};
+ }
+ }
+ }
+ return {};
+}
+
+bool ActionModel::setData(const QModelIndex &index, const QVariant &data, int role)
+{
+ if (!hasIndex(index.row(), index.column(), index.parent()))
+ return false;
+
+ const auto action = m_actions.at(index.row());
+
+ if (role == VisibleRole) {
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ CCmd *theCmd = new CCmdDataModelActionSetEyeball(doc, activeSlide(), action, data.toBool());
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+ Q_EMIT dataChanged(index, index, {VisibleRole});
+ }
+
+
+ return false;
+}
+
+void ActionModel::addAction(const Qt3DSDMActionHandle &action)
+{
+ if (std::find(m_actions.begin(), m_actions.end(), action) == m_actions.end()) {
+ const auto count = rowCount();
+ beginInsertRows({}, count, count);
+ m_actions.push_back(action);
+ endInsertRows();
+ }
+}
+
+void ActionModel::removeAction(const Qt3DSDMActionHandle &action)
+{
+ // KDAB_FIXME use beginRemoveRows
+ beginResetModel();
+ m_actions.erase(std::remove(m_actions.begin(), m_actions.end(), action), m_actions.end());
+ endResetModel();
+}
+
+void ActionModel::updateAction(const Qt3DSDMActionHandle &action)
+{
+ for (unsigned i = 0; i < m_actions.size(); i++) {
+ if (m_actions[i] == action) {
+ auto idx = index(i, 0);
+ Q_EMIT dataChanged(idx, idx, {});
+ }
+ }
+}
+
+const Qt3DSDMActionHandle ActionModel::actionAt(int row)
+{
+ if (row >= 0 && static_cast<unsigned>(row) < m_actions.size())
+ return m_actions[row];
+
+ return {};
+}
+
+const SActionInfo ActionModel::actionInfoAt(int row)
+{
+ const auto action = actionAt(row);
+ if (!action.Valid())
+ return {};
+ auto actionCore = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetActionCore();
+ return actionCore->GetActionInfo(action);
+}
+
+qt3dsdm::IActionSystem *ActionModel::actionSystem() const
+{
+ return g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetActionSystem();
+}
+
+qt3dsdm::Qt3DSDMSlideHandle ActionModel::activeSlide() const
+{
+ return g_StudioApp.GetCore()->GetDoc()->GetActiveSlide();
+}
+
+QString ActionModel::actionString(const Qt3DSDMActionHandle &action) const
+{
+ QString result;
+ if (action.Valid()) {
+ auto core = g_StudioApp.GetCore();
+ auto doc = core->GetDoc();
+ auto actionCore = doc->GetStudioSystem()->GetActionCore();
+ auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+
+ const SActionInfo &actionInfo = actionCore->GetActionInfo(action);
+
+ // Query the event name
+ QString eventFormalName(tr("[Unknown Event]"));
+ Qt3DSDMEventHandle eventHandle = bridge->ResolveEvent(actionInfo);
+ if (eventHandle.Valid())
+ eventFormalName =
+ QString::fromWCharArray(bridge->GetEventInfo(eventHandle).m_FormalName.wide_str());
+
+ // Query the asset name
+ QString assetName = tr("[Unknown]");
+ assetName = bridge->GetName(actionInfo.m_Owner).toQString();
+
+ const auto sourceInstance =
+ bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject);
+ const auto targetInstance =
+ bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject);
+ QString sourceName = sourceInstance.Valid()
+ ? bridge->GetName(sourceInstance).toQString()
+ : tr("[Unknown Source]");
+ QString targetName = targetInstance.Valid()
+ ? bridge->GetName(targetInstance).toQString()
+ : tr("[Unknown Target]");
+
+ // Query the action name
+ QString handlerFormalName(tr("[Unknown Handler]"));
+ const auto handlerHandle = bridge->ResolveHandler(actionInfo);
+ if (handlerHandle.Valid())
+ handlerFormalName = QString::fromWCharArray(bridge->GetHandlerInfo(handlerHandle).m_FormalName.wide_str());
+
+ // Format the strings
+ if (actionInfo.m_Owner == sourceInstance) {
+ if (sourceInstance == targetInstance) {
+ result = tr("Listen for '%1', '%2'").arg(eventFormalName, handlerFormalName);
+ } else {
+ result = tr("Listen for '%1', tell %2 to '%3'").arg(eventFormalName, targetName,
+ handlerFormalName);
+ }
+ } else if (actionInfo.m_Owner == targetInstance) {
+ result = tr("Listen to '%1' for '%2', '%3'").arg(sourceName, eventFormalName,
+ handlerFormalName);
+ } else {
+ result = tr("Listen to '%1' for '%2', tell %3 to '%4'").arg(sourceName,
+ eventFormalName,
+ targetName,
+ handlerFormalName);
+ }
+ }
+ return result;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h
new file mode 100644
index 00000000..966c5782
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ACTIONMODEL_H
+#define ACTIONMODEL_H
+
+#include <QAbstractListModel>
+
+#include "Qt3DSDMActionInfo.h"
+#include "Qt3DSDMHandles.h"
+
+namespace qt3dsdm {
+ class IActionSystem;
+}
+
+class ActionModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit ActionModel(QObject *parent = nullptr);
+
+ void setInstanceHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle);
+
+ enum Roles {
+ DescriptionRole = Qt::DisplayRole,
+ VisibleRole = Qt::UserRole + 1,
+
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) override;
+
+ void addAction(const qt3dsdm::Qt3DSDMActionHandle &action);
+ void removeAction(const qt3dsdm::Qt3DSDMActionHandle &action);
+ void updateAction(const qt3dsdm::Qt3DSDMActionHandle &action);
+ const qt3dsdm::Qt3DSDMActionHandle actionAt(int row);
+ const qt3dsdm::SActionInfo actionInfoAt(int row);
+
+private:
+ qt3dsdm::IActionSystem *actionSystem() const;
+ qt3dsdm::Qt3DSDMSlideHandle activeSlide() const;
+ QString actionString(const qt3dsdm::Qt3DSDMActionHandle &action) const;
+
+ qt3dsdm::Qt3DSDMInstanceHandle m_handle;
+ qt3dsdm::TActionHandleList m_actions;
+};
+
+#endif // ACTIONMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp
new file mode 100644
index 00000000..41d67789
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp
@@ -0,0 +1,1203 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ActionView.h"
+#include "ActionContextMenu.h"
+#include "ActionModel.h"
+#include "CmdDataModelActionSetValue.h"
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Dialogs.h"
+#include "Dispatch.h"
+#include "Doc.h"
+#include "IDocumentEditor.h"
+#include "IDocumentReader.h"
+#include "IObjectReferenceHelper.h"
+#include "Literals.h"
+#include "ObjectListModel.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "StudioClipboard.h"
+#include "StudioObjectTypes.h"
+#include "StudioPreferences.h"
+#include "Qt3DSFileTools.h"
+#include "Qt3DSDMActionCore.h"
+#include "Qt3DSDMDataTypes.h"
+#include "Qt3DSDMSlides.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtCore/qtimer.h>
+#include <QtWidgets/qdesktopwidget.h>
+
+ActionView::ActionView(const QSize &preferredSize, QWidget *parent)
+ : QQuickWidget(parent)
+ , TabNavigable()
+ , m_actionsModel(new ActionModel(this))
+ , m_preferredSize(preferredSize)
+{
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &ActionView::initialize);
+
+ g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this);
+
+ connect(this, &ActionView::actionChanged, this, [this] {
+ if (!m_itemHandle.Valid())
+ return;
+
+ if (!m_propertyModel)
+ m_propertyModel = new PropertyModel(this);
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ if (actionInfo.m_Handler == L"Set Property") {
+ setPropertyValueInvalid(true);
+ m_currentPropertyNameHandle = actionInfo.m_HandlerArgs.at(0);
+ m_currentPropertyValueHandle = actionInfo.m_HandlerArgs.at(1);
+ m_propertyModel->setAction(m_actionsModel->actionAt(m_currentActionIndex));
+ m_propertyModel->setNameHandle(m_currentPropertyNameHandle);
+ m_propertyModel->setValueHandle(m_currentPropertyValueHandle);
+ m_currentPropertyIndex = m_propertyModel->defaultPropertyIndex();
+ Q_EMIT propertyChanged();
+ Q_EMIT propertyModelChanged();
+ setPropertyValueInvalid(false);
+ }
+ });
+
+ m_actionChangedCompressionTimer.setInterval(20);
+ m_actionChangedCompressionTimer.setSingleShot(true);
+ connect(&m_actionChangedCompressionTimer, &QTimer::timeout, this, [this] {
+ updateHandlerArguments();
+ updateFiredEvent();
+ Q_EMIT actionChanged();
+ });
+
+ QString ctrlKey(QStringLiteral("Ctrl+"));
+ QString shiftKey(QStringLiteral("Shift+"));
+#ifdef Q_OS_MACOS
+ ctrlKey = "⌘";
+ shiftKey = "⇧";
+#endif
+
+ // These actions will be passed to the context menu. Some of them need to me members, as we
+ // have to change their enabled state based on selection and previous actions.
+ QAction *action = new QAction(tr("New Action\t%1A").arg(shiftKey));
+ action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_A));
+ connect(action, &QAction::triggered, this, &ActionView::addAction);
+ QQuickWidget::addAction(action);
+
+ m_actionCopy = new QAction(tr("Copy Action\t%1C").arg(ctrlKey));
+ connect(m_actionCopy, &QAction::triggered, this, &ActionView::copyAction);
+ QQuickWidget::addAction(m_actionCopy);
+
+ m_actionPaste = new QAction(tr("Paste Action\t%1V").arg(ctrlKey));
+ connect(m_actionPaste, &QAction::triggered, this, &ActionView::pasteAction);
+ QQuickWidget::addAction(m_actionPaste);
+
+ m_actionCut = new QAction(tr("Cut Action\t%1X").arg(ctrlKey));
+ connect(m_actionCut, &QAction::triggered, this, &ActionView::cutAction);
+ QQuickWidget::addAction(m_actionCut);
+
+ m_actionDel = new QAction(tr("Delete Action\tDel"));
+ connect(m_actionDel, &QAction::triggered, [=](){ deleteAction(m_currentActionIndex); });
+ QQuickWidget::addAction(m_actionDel);
+}
+
+ActionView::~ActionView()
+{
+}
+
+QSize ActionView::sizeHint() const
+{
+ return m_preferredSize;
+}
+
+void ActionView::focusInEvent(QFocusEvent *event)
+{
+ updateActionStates();
+ QQuickWidget::focusInEvent(event);
+}
+
+void ActionView::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+ QQuickWidget::mousePressEvent(event);
+}
+
+bool ActionView::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride) {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (m_currentActionIndex >= 0 && (ke->key() == Qt::Key_Delete
+ || (ke->modifiers() == Qt::ControlModifier
+ && (ke->key() == Qt::Key_C || ke->key() == Qt::Key_V
+ || ke->key() == Qt::Key_X)))) {
+ auto focusItem = quickWindow()->activeFocusItem();
+ if (focusItem && (focusItem->objectName() == QStringLiteral("actionListDelegate")
+ || focusItem->objectName() == QStringLiteral("focusEater"))) {
+ if (ke->key() == Qt::Key_Delete) {
+ if (m_actionDel->isEnabled())
+ deleteAction(m_currentActionIndex);
+ } else if (ke->modifiers() == Qt::ControlModifier) {
+ if (ke->key() == Qt::Key_C) {
+ if (m_actionCopy->isEnabled())
+ copyAction();
+ } else if (ke->key() == Qt::Key_V) {
+ if (m_actionPaste->isEnabled())
+ pasteAction();
+ } else if (ke->key() == Qt::Key_X) {
+ if (m_actionCut->isEnabled())
+ cutAction();
+ }
+ }
+ event->accept();
+ return true;
+ }
+ }
+ }
+ return QQuickWidget::event(event);
+}
+
+void ActionView::setItem(const qt3dsdm::Qt3DSDMInstanceHandle &handle)
+{
+ if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ }
+
+ m_objRefHelper = GetDoc()->GetDataModelObjectReferenceHelper();
+ m_itemHandle = handle;
+ m_actionsModel->setInstanceHandle(handle);
+ if (m_itemHandle.Valid() != m_hasItem) {
+ m_hasItem = m_itemHandle.Valid();
+ Q_EMIT hasItemChanged();
+ }
+ emitActionChanged();
+ Q_EMIT itemChanged();
+ Q_EMIT itemTextChanged();
+}
+
+QString ActionView::itemIcon() const
+{
+ if (!m_itemHandle.Valid())
+ return QString();
+
+ auto info = m_objRefHelper->GetInfo(m_itemHandle);
+ return CStudioObjectTypes::GetNormalIconName(info.m_Type);
+}
+
+QString ActionView::itemText() const
+{
+ if (!m_itemHandle.Valid())
+ return tr("No Object Selected");
+
+ const auto data = m_objRefHelper->GetInfo(m_itemHandle);
+ return data.m_Name.toQString();
+}
+
+QColor ActionView::itemColor() const
+{
+ if (!m_itemHandle.Valid())
+ return Qt::white;
+
+ auto info = m_objRefHelper->GetInfo(m_itemHandle);
+ if (info.m_Master)
+ return CStudioPreferences::masterColor();
+ else
+ return CStudioPreferences::textColor();
+}
+
+QAbstractItemModel *ActionView::actionsModel() const
+{
+ return m_actionsModel;
+}
+
+QAbstractItemModel *ActionView::propertyModel() const
+{
+ return m_propertyModel;
+}
+
+QString ActionView::targetObjectName() const
+{
+ if (!GetDoc()->isValid() || !m_itemHandle.Valid())
+ return QString();
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+
+ const auto targetInstance =
+ GetBridge()->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject);
+
+ QString targetName = targetInstance.Valid()
+ ? GetBridge()->GetName(targetInstance).toQString()
+ : tr("[Unknown Target]");
+
+ return targetName;
+}
+
+QString ActionView::triggerObjectName() const
+{
+ if (!GetDoc()->isValid() || !m_itemHandle.Valid())
+ return QString();
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+
+ const auto sourceInstance =
+ GetBridge()->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject);
+
+ QString sourceName = sourceInstance.Valid()
+ ? GetBridge()->GetName(sourceInstance).toQString()
+ : tr("[Unknown Source]");
+
+ return sourceName;
+}
+
+QString ActionView::eventName() const
+{
+ if (!GetDoc()->isValid() || !m_itemHandle.Valid())
+ return QString();
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto eventHandle = bridge->ResolveEvent(actionInfo);
+ const auto eventInfo = bridge->GetEventInfo(eventHandle);
+
+ const QString formalName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str());
+ return formalName.isEmpty() ? tr("[Unknown Event]") : formalName;
+}
+
+QString ActionView::handlerName() const
+{
+ if (!GetDoc()->isValid() || !m_itemHandle.Valid())
+ return QString();
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto handlerHandle = bridge->ResolveHandler(actionInfo);
+
+ if (handlerHandle.Valid()) {
+ const auto handlerInfo = bridge->GetHandlerInfo(handlerHandle);
+ return QString::fromWCharArray(handlerInfo.m_FormalName.wide_str());
+ }
+
+ return tr("[Unknown Handler]");
+}
+
+QVariantList ActionView::handlerArguments() const
+{
+ return m_handlerArguments;
+}
+
+PropertyInfo ActionView::property() const
+{
+ if (!m_propertyModel)
+ return {};
+ return m_propertyModel->property(m_currentPropertyIndex);
+}
+
+bool ActionView::isPropertyValueInvalid() const
+{
+ return m_propertyValueInvalid;
+}
+
+void ActionView::setCurrentActionIndex(int index)
+{
+ if (index == m_currentActionIndex)
+ return;
+
+ m_currentActionIndex = index;
+ emitActionChanged();
+
+ updateActionStates();
+}
+
+void ActionView::setCurrentPropertyIndex(int handle, int index)
+{
+ setPropertyValueInvalid(true);
+ // Make sure propertymodel name & value handles are always up-to-date,
+ // even when index is same as before
+ m_currentPropertyValueHandle = 0;
+ m_currentPropertyNameHandle = handle;
+ for (int i = 0; i < m_handlerArguments.size(); ++i) {
+ auto handlerArg = m_handlerArguments[i].value<HandlerArgument>();
+ if (handlerArg.m_handle.GetHandleValue() == handle && i < m_handlerArguments.size() - 1) {
+ m_currentPropertyValueHandle
+ = m_handlerArguments[i + 1].value<HandlerArgument>().m_handle;
+ if (m_propertyModel) {
+ m_propertyModel->setNameHandle(m_currentPropertyNameHandle);
+ m_propertyModel->setValueHandle(m_currentPropertyValueHandle);
+ }
+ }
+ }
+
+ if (index == m_currentPropertyIndex)
+ return;
+
+ m_currentPropertyIndex = index;
+
+ // set the property for the handler
+ if (m_propertyModel && handle != 0) {
+ qt3dsdm::SValue sValue(QVariant(m_propertyModel->property(index).m_nameId));
+ qt3dsdm::SValue oldValue;
+ GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue);
+
+ if (!Equals(oldValue, sValue)) {
+ CCmd *theCmd =
+ new CCmdDataModelActionSetArgumentValue(GetDoc(), handle, sValue);
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+ }
+ }
+
+ Q_EMIT propertyChanged();
+ // Clear the value invalid flag asynchronously as the value doesn't actually change until
+ // backend tells us it does
+ QTimer::singleShot(0, this, &ActionView::clearPropertyValueInvalid);
+}
+
+void ActionView::addAction()
+{
+ if (m_itemHandle.Valid()) {
+ // Query data model bridge to see the applicable events and actions for this instance.
+ CClientDataModelBridge *theBridge = GetBridge();
+
+ std::wstring theEventName = theBridge->GetDefaultEvent(m_itemHandle);
+ std::wstring theHandlerName = theBridge->GetDefaultHandler(m_itemHandle);
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Add Action"))
+ ->AddAction(GetDoc()->GetActiveSlide(), m_itemHandle, theEventName,
+ theHandlerName);
+ }
+ updateActionStates();
+}
+
+void ActionView::deleteAction(int index)
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ const auto action = m_actionsModel->actionAt(index);
+ if (action.Valid()) {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(),
+ QObject::tr("Delete Action"))->DeleteAction(action);
+ emitActionChanged();
+ }
+ updateActionStates();
+}
+
+QObject *ActionView::showTriggerObjectBrowser(const QPoint &point)
+{
+ if (!m_itemHandle.Valid())
+ return nullptr;
+
+ if (!m_objectsModel) {
+ m_objectsModel = new ObjectListModel(g_StudioApp.GetCore(),
+ GetDoc()->GetSceneInstance(), this);
+ }
+
+ if (!m_triggerObjectBrowser)
+ m_triggerObjectBrowser = new ObjectBrowserView(this);
+
+ m_triggerObjectBrowser->setModel(m_objectsModel);
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto instanceHandle = GetBridge()->GetInstance(actionInfo.m_Owner,
+ actionInfo.m_TriggerObject);
+ m_triggerObjectBrowser->disconnect();
+ m_triggerObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner);
+ CDialogs::showWidgetBrowser(this, m_triggerObjectBrowser, point);
+ m_activeBrowser = m_triggerObjectBrowser;
+
+ connect(m_triggerObjectBrowser, &ObjectBrowserView::selectionChanged,
+ this, &ActionView::OnTriggerSelectionChanged);
+ // update also pathtype in the trigger object when changed from UI
+ connect(m_triggerObjectBrowser, &ObjectBrowserView::pathTypeChanged,
+ this, &ActionView::OnTriggerSelectionChanged);
+
+ return m_triggerObjectBrowser;
+}
+
+QObject *ActionView::showTargetObjectBrowser(const QPoint &point)
+{
+ if (!m_itemHandle.Valid())
+ return nullptr;
+
+ if (!m_objectsModel) {
+ m_objectsModel = new ObjectListModel(g_StudioApp.GetCore(),
+ GetDoc()->GetSceneInstance(), this);
+ }
+
+ if (!m_targetObjectBrowser)
+ m_targetObjectBrowser = new ObjectBrowserView(this);
+
+ m_targetObjectBrowser->setModel(m_objectsModel);
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto instanceHandle = GetBridge()->GetInstance(actionInfo.m_Owner,
+ actionInfo.m_TargetObject);
+ m_targetObjectBrowser->disconnect();
+ m_targetObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner);
+ CDialogs::showWidgetBrowser(this, m_targetObjectBrowser, point);
+ m_activeBrowser = m_targetObjectBrowser;
+
+ connect(m_targetObjectBrowser, &ObjectBrowserView::selectionChanged,
+ this, &ActionView::OnTargetSelectionChanged);
+ // update also pathtype in the target object when changed from UI
+ connect(m_targetObjectBrowser, &ObjectBrowserView::pathTypeChanged,
+ this, &ActionView::OnTargetSelectionChanged);
+
+ return m_targetObjectBrowser;
+}
+
+void ActionView::OnTargetSelectionChanged()
+{
+ auto selectedItem = m_targetObjectBrowser->selectedHandle();
+ setTargetObject(m_objRefHelper->GetAssetRefValue(
+ selectedItem, m_itemHandle,
+ (CRelativePathTools::EPathType)(m_targetObjectBrowser->pathType())));
+ resetFiredEvent();
+}
+
+void ActionView::OnTriggerSelectionChanged()
+{
+ auto selectedItem = m_triggerObjectBrowser->selectedHandle();
+ setTriggerObject(m_objRefHelper->GetAssetRefValue(
+ selectedItem, m_itemHandle,
+ (CRelativePathTools::EPathType)(m_triggerObjectBrowser->pathType())));
+ resetFiredEvent();
+}
+
+void ActionView::showContextMenu(int x, int y)
+{
+ updateActionStates();
+ CActionContextMenu contextMenu(QQuickWidget::actions(), this);
+ contextMenu.exec(mapToGlobal({x, y}));
+}
+
+QObject *ActionView::showEventBrowser(const QPoint &point)
+{
+ if (!m_itemHandle.Valid())
+ return nullptr;
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject);
+
+ if (!instanceHandle.Valid())
+ return nullptr;
+
+ if (!m_eventsModel)
+ m_eventsModel = new EventsModel(this);
+
+ qt3dsdm::TEventHandleList eventList;
+ bridge->GetEvents(instanceHandle, eventList);
+ m_eventsModel->setEventList(eventList);
+
+ if (!m_eventsBrowser)
+ m_eventsBrowser = new EventsBrowserView(this);
+
+ m_eventsBrowser->setModel(m_eventsModel);
+
+ m_eventsBrowser->disconnect();
+ m_eventsBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Event));
+ CDialogs::showWidgetBrowser(this, m_eventsBrowser, point);
+ m_activeBrowser = m_eventsBrowser;
+
+ connect(m_eventsBrowser, &EventsBrowserView::selectionChanged,
+ this, [this] {
+ if (m_eventsBrowser->canCommit())
+ setEvent(qt3dsdm::Qt3DSDMEventHandle(m_eventsBrowser->selectedHandle()));
+ });
+
+ return m_eventsBrowser;
+}
+
+QObject *ActionView::showHandlerBrowser(const QPoint &point)
+{
+ if (!m_itemHandle.Valid())
+ return nullptr;
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject);
+
+ if (!instanceHandle.Valid())
+ return nullptr;
+
+ if (!m_handlersModel)
+ m_handlersModel = new EventsModel(this);
+
+ qt3dsdm::THandlerHandleList handlerList;
+ bridge->GetHandlers(instanceHandle, handlerList);
+ m_handlersModel->setHandlerList(handlerList);
+
+ if (!m_handlerBrowser)
+ m_handlerBrowser = new EventsBrowserView(this);
+
+ m_handlerBrowser->setModel(m_handlersModel);
+
+ m_handlerBrowser->disconnect();
+ m_handlerBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Handler));
+ CDialogs::showWidgetBrowser(this, m_handlerBrowser, point);
+ m_activeBrowser = m_handlerBrowser;
+
+ connect(m_handlerBrowser, &EventsBrowserView::selectionChanged,
+ this, [this] {
+ if (m_handlerBrowser->canCommit())
+ setHandler(qt3dsdm::Qt3DSDMHandlerHandle(m_handlerBrowser->selectedHandle()));
+ });
+
+ return m_handlerBrowser;
+}
+
+QObject *ActionView::showEventBrowserForArgument(int handle, const QPoint &point)
+{
+ if (!m_itemHandle.Valid())
+ return nullptr;
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject);
+
+ if (!instanceHandle.Valid())
+ return nullptr;
+
+ if (!m_fireEventsModel)
+ m_fireEventsModel = new EventsModel(this);
+
+ qt3dsdm::TEventHandleList eventList;
+ bridge->GetEvents(instanceHandle, eventList);
+ m_fireEventsModel->setEventList(eventList);
+
+ if (!m_fireEventsBrowser)
+ m_fireEventsBrowser = new EventsBrowserView(this);
+
+ m_fireEventsBrowser->setModel(m_fireEventsModel);
+ m_fireEventsBrowser->setHandle(handle);
+
+ qt3dsdm::SValue oldValue;
+ GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue);
+
+ QString eventName;
+ for (Qt3DSDMEventHandle eventHandle : eventList) {
+ if (oldValue == eventHandle.GetHandleValue()) {
+ qt3dsdm::SEventInfo eventInfo = bridge->GetEventInfo(eventHandle);
+ eventName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str());
+ if (eventName.isEmpty())
+ eventName = QString::fromWCharArray(eventInfo.m_Name.wide_str());
+ }
+ }
+ m_fireEventsBrowser->disconnect();
+ m_fireEventsBrowser->selectAndExpand(eventName);
+ CDialogs::showWidgetBrowser(this, m_fireEventsBrowser, point);
+ m_activeBrowser = m_fireEventsBrowser;
+
+ connect(m_fireEventsBrowser, &EventsBrowserView::selectionChanged,
+ this, [this, handle] {
+ setArgumentValue(handle, qt3dsdm::Qt3DSDMEventHandle(
+ m_fireEventsBrowser->selectedHandle()).GetHandleValue());
+ });
+
+ return m_fireEventsBrowser;
+}
+
+void ActionView::updateFiredEvent()
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ if (actionInfo.m_Handler != L"Fire Event") {
+ m_firedEvent = tr("[Unknown event]");
+ return;
+ }
+
+ const auto doc = GetDoc();
+ if (!doc->isValid())
+ return;
+
+ const auto bridge = GetBridge();
+ const auto handlerHandle = bridge->ResolveHandler(actionInfo);
+ IActionCore *actionCore = doc->GetStudioSystem()->GetActionCore();
+
+ if (handlerHandle.Valid()) {
+ for (const auto &argHandle: actionInfo.m_HandlerArgs) {
+ const auto &argumentInfo = actionCore->GetHandlerArgumentInfo(argHandle);
+ DataModelDataType::Value theArgType(GetValueType(argumentInfo.m_Value));
+ SValue theArgValue(argumentInfo.m_Value);
+ if (argumentInfo.m_ArgType == HandlerArgumentType::Event) {
+ theArgType = DataModelDataType::String;
+ auto theEventHandle = get<qt3ds::QT3DSI32>(argumentInfo.m_Value);
+ theArgValue = SValue(std::make_shared<CDataStr>(
+ bridge->GetEventInfo(theEventHandle).m_Name.wide_str()));
+ m_firedEvent = theArgValue.toQVariant().toString();
+ Q_EMIT firedEventChanged();
+ }
+ }
+ }
+}
+
+void ActionView::updateFiredEventFromHandle(int handle)
+{
+ m_firedEvent = QString::fromWCharArray(
+ GetBridge()->GetEventInfo(handle).m_FormalName.wide_str());
+ Q_EMIT firedEventChanged();
+}
+
+void ActionView::resetFiredEvent()
+{
+ m_firedEvent = tr("[Unknown Event]");
+ Q_EMIT firedEventChanged();
+}
+
+void ActionView::OnNewPresentation()
+{
+ // Register callback
+ qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalProvider();
+ m_connections.push_back(theSignalProvider->ConnectActionCreated(
+ std::bind(&ActionView::OnActionAdded, this,
+ std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theSignalProvider->ConnectActionDeleted(
+ std::bind(&ActionView::OnActionDeleted, this,
+ std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theSignalProvider->ConnectTriggerObjectSet(
+ std::bind(&ActionView::OnActionModified, this,
+ std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectTargetObjectSet(
+ std::bind(&ActionView::OnActionModified, this,
+ std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectEventSet(
+ std::bind(&ActionView::OnActionModified, this,
+ std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectHandlerSet(
+ std::bind(&ActionView::OnActionModified, this,
+ std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectHandlerArgumentValueSet(
+ std::bind(&ActionView::OnHandlerArgumentModified, this,
+ std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectInstancePropertyValue(
+ std::bind(&ActionView::OnInstancePropertyValueChanged, this,
+ std::placeholders::_1,
+ std::placeholders::_2)));
+ m_connections.push_back(theSignalProvider->ConnectInstanceDeleted(
+ std::bind(&ActionView::OnInstanceDeleted, this,
+ std::placeholders::_1)));
+ CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch();
+ m_connections.push_back(theDispatch->ConnectSelectionChange(
+ std::bind(&ActionView::OnSelectionSet, this,
+ std::placeholders::_1)));
+
+ auto assetGraph = g_StudioApp.GetCore()->GetDoc()->GetAssetGraph();
+ m_connections.push_back(assetGraph->ConnectChildAdded(
+ std::bind(&ActionView::onAssetGraphChanged, this)));
+ m_connections.push_back(assetGraph->ConnectChildRemoved(
+ std::bind(&ActionView::onAssetGraphChanged, this)));
+}
+
+void ActionView::OnClosingPresentation()
+{
+ m_connections.clear();
+}
+
+void ActionView::OnSelectionSet(Q3DStudio::SSelectedValue inSelectable)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance;
+ std::vector<qt3dsdm::Qt3DSDMInstanceHandle> instances;
+
+ switch (inSelectable.getType()) {
+ case Q3DStudio::SelectedValueTypes::Instance:
+ theInstance = inSelectable.getData<qt3dsdm::Qt3DSDMInstanceHandle>();
+ break;
+ case Q3DStudio::SelectedValueTypes::MultipleInstances:
+ instances = inSelectable.getData<std::vector<qt3dsdm::Qt3DSDMInstanceHandle>>();
+ // handling only if we have one selected element.
+ if (instances.size() == 1)
+ theInstance = instances[0];
+ break;
+ case Q3DStudio::SelectedValueTypes::Slide: {
+ qt3dsdm::Qt3DSDMSlideHandle theSlideHandle =
+ inSelectable.getData<Q3DStudio::SSlideInstanceWrapper>().m_Slide;
+ // Get the owning component instance
+ CClientDataModelBridge *theBridge = GetBridge();
+ qt3dsdm::SLong4 theComponentGuid = theBridge->GetComponentGuid(theSlideHandle);
+ Q_ASSERT(theComponentGuid.Valid());
+ theInstance = theBridge->GetInstanceByGUID(theComponentGuid);
+ Q_ASSERT(theInstance.Valid());
+ }
+ break;
+ default:
+ // Clear selection on selecting other types or nothing at all
+ break;
+ };
+
+ setItem(theInstance);
+}
+
+void ActionView::OnActionAdded(qt3dsdm::Qt3DSDMActionHandle inAction,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner)
+{
+ CDoc *theDoc = GetDoc();
+ qt3dsdm::CStudioSystem *theStudioSystem = theDoc->GetStudioSystem();
+
+ qt3dsdm::Qt3DSDMSlideHandle theCurrentSlide = theDoc->GetActiveSlide();
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlideOfAction =
+ theStudioSystem->GetSlideSystem()->GetMasterSlide(inSlide);
+ qt3dsdm::Qt3DSDMSlideHandle theMasterOfCurrentSlide =
+ theStudioSystem->GetSlideSystem()->GetMasterSlide(theCurrentSlide);
+
+ if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ }
+
+ if (inOwner == m_itemHandle // the action is added to current viewed instance
+ && (theCurrentSlide == inSlide // and is added to the current viewed slide
+ || (theMasterSlideOfAction == inSlide
+ && theMasterOfCurrentSlide == theMasterSlideOfAction))) {
+ // or it is added to the master of the current viewed slide
+ m_actionsModel->addAction(inAction);
+ }
+}
+
+void ActionView::OnActionDeleted(qt3dsdm::Qt3DSDMActionHandle inAction,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner)
+{
+ Q_UNUSED(inSlide);
+ Q_UNUSED(inOwner);
+
+ if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ }
+ m_actionsModel->removeAction(inAction);
+}
+
+void ActionView::OnActionModified(qt3dsdm::Qt3DSDMActionHandle inAction)
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ if (GetDoc()->GetStudioSystem()->GetActionCore()->HandleValid(inAction)) {
+ if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) {
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ if (!actionInfo.m_Instance.Valid()) {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ } else {
+ // Update the selection in an active browser dialog
+ if (m_activeBrowser == m_triggerObjectBrowser) {
+ const auto instanceHandle = GetBridge()->GetInstance(
+ actionInfo.m_Owner, actionInfo.m_TriggerObject);
+ m_triggerObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner);
+ } else if (m_activeBrowser == m_targetObjectBrowser) {
+ const auto instanceHandle = GetBridge()->GetInstance(
+ actionInfo.m_Owner, actionInfo.m_TargetObject);
+ m_targetObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner);
+ } else if (m_activeBrowser == m_eventsBrowser) {
+ m_eventsBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Event));
+ } else if (m_activeBrowser == m_handlerBrowser) {
+ m_handlerBrowser->selectAndExpand(
+ QString::fromStdWString(actionInfo.m_Handler));
+ }
+ }
+ }
+ m_actionsModel->updateAction(inAction);
+ emitActionChanged();
+ }
+}
+
+void ActionView::OnHandlerArgumentModified(qt3dsdm::Qt3DSDMHandlerArgHandle inHandlerArgument)
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ if (!m_fireEventsBrowser.isNull() && m_activeBrowser == m_fireEventsBrowser
+ && m_activeBrowser->isVisible()) {
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+
+ // m_fireEventsBrowser needs to be closed if another type of target handler is chosen.
+ // Other browsers will remain valid always as long as the action is selected.
+ if (actionInfo.m_Handler != L"Fire Event") {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ } else {
+ // Update the selection in an active browser dialog
+ const auto bridge = GetBridge();
+ const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner,
+ actionInfo.m_TargetObject);
+ qt3dsdm::TEventHandleList eventList;
+ bridge->GetEvents(instanceHandle, eventList);
+ qt3dsdm::SValue value;
+ GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(
+ m_fireEventsBrowser->handle(), value);
+ QString eventName;
+ for (Qt3DSDMEventHandle eventHandle : eventList) {
+ if (value == eventHandle.GetHandleValue()) {
+ qt3dsdm::SEventInfo eventInfo = bridge->GetEventInfo(eventHandle);
+ eventName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str());
+ if (eventName.isEmpty())
+ eventName = QString::fromWCharArray(eventInfo.m_Name.wide_str());
+ }
+ }
+ m_fireEventsBrowser->selectAndExpand(eventName);
+ }
+ }
+ emitActionChanged();
+}
+
+void ActionView::OnInstancePropertyValueChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ if (!m_itemHandle.Valid() || m_itemHandle != inInstance)
+ return;
+
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ if (inProperty == bridge->GetNameProperty())
+ Q_EMIT itemTextChanged();
+
+ emitActionChanged();
+}
+
+void ActionView::OnInstanceDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ // Clear the model on instance deletion
+ if (inInstance == m_itemHandle) {
+ qt3dsdm::Qt3DSDMInstanceHandle noInstance;
+ setItem(noInstance);
+ }
+}
+
+void ActionView::copyAction()
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ auto theTempAPFile =
+ GetDoc()->GetDocumentReader().CopyAction(m_actionsModel->actionAt(m_currentActionIndex),
+ GetDoc()->GetActiveSlide());
+ Qt3DSFile theFile(theTempAPFile);
+ CStudioClipboard::CopyActionToClipboard(theFile);
+ updateActionStates();
+}
+
+void ActionView::cutAction()
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ copyAction();
+ auto action = m_actionsModel->actionAt(m_currentActionIndex);
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Cut Action"))->DeleteAction(action);
+ updateActionStates();
+}
+
+void ActionView::pasteAction()
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ Qt3DSFile theTempAPFile = CStudioClipboard::GetActionFromClipboard();
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Paste Action"))
+ ->PasteAction(theTempAPFile.GetAbsolutePath(), m_itemHandle);
+ updateActionStates();
+}
+
+void ActionView::setTriggerObject(const qt3dsdm::SObjectRefType &object)
+{
+ auto action = m_actionsModel->actionAt(m_currentActionIndex);
+ if (!action.Valid())
+ return;
+
+ if (!m_triggerObjectBrowser.isNull() && m_triggerObjectBrowser->canCommit()) {
+ auto core = g_StudioApp.GetCore();
+ auto theBridge = GetBridge();
+
+ auto theCmd = new CCmdDataModelActionSetTriggerObject(GetDoc(), action, object);
+ const SActionInfo &theActionInfo
+ = GetDoc()->GetStudioSystem()->GetActionCore()->GetActionInfo(action);
+
+ Qt3DSDMInstanceHandle theBaseInstance = theActionInfo.m_Owner;
+ Qt3DSDMInstanceHandle theObjectInstance = theBridge->GetInstance(theBaseInstance, object);
+ Qt3DSDMInstanceHandle theOldInstance = theBridge->GetInstance(theBaseInstance,
+ theActionInfo.m_TargetObject);
+ // old instance and object instance could be the same, for example if user changes the type
+ // from Absolute to Path. In this case we don't need to reset handler or event.
+ if (theOldInstance != theObjectInstance) {
+ theCmd->ResetEvent(
+ theBridge->GetDefaultEvent(theObjectInstance, theActionInfo.m_Event));
+ }
+
+ core->ExecuteCommand(theCmd);
+ }
+ emitActionChanged();
+}
+
+void ActionView::setTargetObject(const qt3dsdm::SObjectRefType &object)
+{
+ auto action = m_actionsModel->actionAt(m_currentActionIndex);
+ if (!action.Valid())
+ return;
+
+ if (!m_targetObjectBrowser.isNull() && m_targetObjectBrowser->canCommit()) {
+ auto core = g_StudioApp.GetCore();
+ auto doc = GetDoc();
+ auto theBridge = GetBridge();
+
+ auto theCmd = new CCmdDataModelActionSetTargetObject(doc, action, object);
+ const SActionInfo &theActionInfo = doc->GetStudioSystem()->GetActionCore()->GetActionInfo(
+ action);
+
+ Qt3DSDMInstanceHandle theBaseInstance = theActionInfo.m_Owner;
+ Qt3DSDMInstanceHandle theObjectInstance = theBridge->GetInstance(theBaseInstance, object);
+ Qt3DSDMInstanceHandle theOldInstance = theBridge->GetInstance(theBaseInstance,
+ theActionInfo.m_TargetObject);
+ // old instance and object instance could be the same, for example if user changes the type
+ // from Absolute to Path. In this case we don't need to reset handler or event.
+ if (theOldInstance != theObjectInstance) {
+ theCmd->ResetHandler(
+ theBridge->GetDefaultHandler(theObjectInstance, theActionInfo.m_Handler));
+ }
+
+ core->ExecuteCommand(theCmd);
+ }
+ emitActionChanged();
+}
+
+void ActionView::setEvent(const Qt3DSDMEventHandle &event)
+{
+ if (!event.Valid())
+ return;
+
+ auto doc = GetDoc();
+ const auto action = m_actionsModel->actionAt(m_currentActionIndex);
+ CCmd *theCmd = new CCmdDataModelActionSetEvent(doc, action,
+ doc->GetStudioSystem()
+ ->GetActionMetaData()
+ ->GetEventInfo(event)
+ ->m_Name.wide_str());
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+}
+
+void ActionView::setHandler(const Qt3DSDMHandlerHandle &handler)
+{
+ if (!handler.Valid())
+ return;
+
+ auto doc = GetDoc();
+ const auto action = m_actionsModel->actionAt(m_currentActionIndex);
+ wstring handlerName(doc->GetStudioSystem()->GetActionMetaData()->GetHandlerInfo(handler)
+ ->m_Name.wide_str());
+ CCmdDataModelActionSetHandler *theCmd =
+ new CCmdDataModelActionSetHandler(doc, action, handlerName);
+ theCmd->ResetHandler(handlerName); // reset the handler args
+
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+}
+
+QVariant ActionView::handlerArgumentValue(int handle) const
+{
+ qt3dsdm::SValue value;
+ GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, value);
+ return value.toQVariant();
+}
+
+void ActionView::updateHandlerArguments()
+{
+ m_currentPropertyValueHandle = 0;
+ m_currentPropertyNameHandle = 0;
+ m_handlerArguments.clear();
+ const auto doc = GetDoc();
+ if (!doc->isValid() || !m_itemHandle.Valid())
+ return;
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ const auto bridge = GetBridge();
+ const auto handlerHandle = bridge->ResolveHandler(actionInfo);
+ IActionCore *actionCore = doc->GetStudioSystem()->GetActionCore();
+
+ if (handlerHandle.Valid()) {
+ auto newMetaData = doc->GetStudioSystem()->GetActionMetaData();
+
+ for (const auto &argHandle: actionInfo.m_HandlerArgs) {
+ const auto &argumentInfo = actionCore->GetHandlerArgumentInfo(argHandle);
+ Option<SMetaDataHandlerArgumentInfo> argMetaData(
+ newMetaData->FindHandlerArgumentByName(handlerHandle, argumentInfo.m_Name));
+
+ HandlerArgument argument;
+ argument.m_handle = argHandle;
+ argument.m_type = argMetaData->m_ArgType;
+ argument.m_name = QString::fromWCharArray(argumentInfo.m_Name.wide_str());
+ argument.m_value = argumentInfo.m_Value.toQVariant();
+ argument.m_completeType = argMetaData->m_CompleteType;
+ m_handlerArguments.append(QVariant::fromValue(argument));
+ }
+ }
+}
+
+void ActionView::emitActionChanged()
+{
+ m_actionChangedCompressionTimer.start();
+}
+
+void ActionView::setArgumentValue(int handle, const QVariant &value)
+{
+ if (!m_itemHandle.Valid())
+ return;
+
+ if (handle == 0)
+ return;
+
+ qt3dsdm::SValue sValue(value);
+ qt3dsdm::SValue oldValue;
+ GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue);
+
+ if (!Equals(oldValue, sValue)) {
+ CCmd *theCmd =
+ new CCmdDataModelActionSetArgumentValue(GetDoc(), handle, sValue);
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+ }
+
+ const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex);
+ if (actionInfo.m_Handler == L"Fire Event") {
+ if (value.toInt())
+ updateFiredEventFromHandle(value.toInt());
+ }
+}
+
+CDoc *ActionView::GetDoc()
+{
+ return g_StudioApp.GetCore()->GetDoc();
+}
+
+CClientDataModelBridge *ActionView::GetBridge()
+{
+ return GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+}
+
+void ActionView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_parentView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_tabOrderHandler"), tabOrderHandler());
+ rootContext()->setContextProperty(QStringLiteral("_mouseHelper"), &m_mouseHelper);
+ m_mouseHelper.setWidget(this);
+
+ QString shiftKey(QStringLiteral("Shift+"));
+#ifdef Q_OS_MACOS
+ shiftKey = "⇧";
+#endif
+ rootContext()->setContextProperty(QStringLiteral("_shiftKey"), shiftKey);
+ qmlRegisterUncreatableType<qt3dsdm::HandlerArgumentType>(
+ "Qt3DStudio", 1, 0, "HandlerArgumentType",
+ QStringLiteral("HandlerArgumentType is an enum container"));
+ qmlRegisterUncreatableType<qt3dsdm::DataModelDataType>(
+ "Qt3DStudio", 1, 0, "DataModelDataType",
+ QStringLiteral("DataModelDataType is an enum container"));
+ qmlRegisterUncreatableType<qt3dsdm::AdditionalMetaDataType>(
+ "Qt3DStudio", 1, 0, "AdditionalMetaDataType",
+ QStringLiteral("AdditionalMetaDataType is an enum container"));
+ qmlRegisterUncreatableType<PropertyInfo>(
+ "Qt3DStudio", 1, 0, "PropertyInfo",
+ QStringLiteral("PropertyInfo is not creatable in QML"));
+ qmlRegisterUncreatableType<qt3dsdm::CompleteMetaDataType>(
+ "Qt3DStudio", 1, 0, "CompleteMetaDataType",
+ QStringLiteral("CompleteMetaDataType is an enum container"));
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Action/ActionView.qml")));
+}
+
+QStringList ActionView::slideNames()
+{
+ if (!m_itemHandle.Valid())
+ return {};
+
+ std::list<Q3DStudio::CString> outSlideNames;
+ QStringList slideNames;
+ CClientDataModelBridge *theBridge = GetBridge();
+ const auto action = m_actionsModel->actionAt(m_currentActionIndex);
+
+ theBridge->GetSlideNamesOfAction(action, outSlideNames);
+
+ for (auto slideName : outSlideNames)
+ slideNames.append(slideName.toQString());
+
+ return slideNames;
+}
+
+int ActionView::slideNameToIndex(const QString &name)
+{
+ const auto slides = slideNames(); // KDAB_TODO cache it
+ return slides.indexOf(name);
+}
+
+bool ActionView::toolTipsEnabled()
+{
+ return CStudioPreferences::ShouldShowTooltips();
+}
+
+void ActionView::updateActionStates()
+{
+ bool hasValidAction = (m_currentActionIndex != -1) && m_itemHandle.Valid();
+ m_actionCopy->setEnabled(hasValidAction);
+ m_actionCut->setEnabled(hasValidAction);
+ m_actionDel->setEnabled(hasValidAction);
+ // Allow paste action even if item is not valid (list of actions is empty)
+ m_actionPaste->setEnabled(CStudioClipboard::CanPasteAction());
+}
+
+// m_propertyValueInvalid flag indicates that property value is changing and
+// may not be valid if queried at the moment. It is used to prevent QML errors
+// about invalid value types when changing property handlers.
+void ActionView::setPropertyValueInvalid(bool invalid)
+{
+ if (invalid != m_propertyValueInvalid) {
+ m_propertyValueInvalid = invalid;
+ Q_EMIT propertyValueInvalidChanged();
+ }
+}
+
+// This is used to set m_propertyValueInvalid to false asynchronously
+void ActionView::clearPropertyValueInvalid()
+{
+ setPropertyValueInvalid(false);
+}
+
+void ActionView::onAssetGraphChanged()
+{
+ // Changes to asset graph invalidate the object browser model, so close it if it is open
+ if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()
+ && (m_activeBrowser == m_targetObjectBrowser
+ || m_activeBrowser == m_triggerObjectBrowser)) {
+ m_activeBrowser->close();
+ m_activeBrowser.clear();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h
new file mode 100644
index 00000000..ab2976f3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h
@@ -0,0 +1,231 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ACTIONVIEW_H
+#define ACTIONVIEW_H
+
+#include <QtQuickWidgets/qquickwidget.h>
+#include <QtGui/qcolor.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qtimer.h>
+
+#include "Qt3DSCommonPrecompile.h"
+#include "DispatchListeners.h"
+#include "EventsBrowserView.h"
+#include "EventsModel.h"
+#include "ObjectBrowserView.h"
+#include "ObjectListModel.h"
+#include "PropertyModel.h"
+#include "SelectedValueImpl.h"
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMSignals.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMMetaDataTypes.h"
+#include "TabOrderHandler.h"
+#include "MouseHelper.h"
+
+class ActionModel;
+class CClientDataModelBridge;
+class CCore;
+class CDoc;
+class IObjectReferenceHelper;
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+struct HandlerArgument {
+ Q_PROPERTY(qt3dsdm::HandlerArgumentType::Value type MEMBER m_type FINAL)
+ Q_PROPERTY(QString name MEMBER m_name FINAL)
+ Q_PROPERTY(int handle MEMBER m_handle FINAL)
+ Q_PROPERTY(QVariant value MEMBER m_value FINAL)
+ Q_PROPERTY(qt3dsdm::CompleteMetaDataType::Enum completeType MEMBER m_completeType FINAL)
+
+ qt3dsdm::Qt3DSDMHandlerArgHandle m_handle;
+ qt3dsdm::HandlerArgumentType::Value m_type;
+ qt3dsdm::CompleteMetaDataType::Enum m_completeType;
+ QString m_name;
+ QVariant m_value;
+
+ Q_GADGET
+};
+
+Q_DECLARE_METATYPE(HandlerArgument)
+
+class ActionView : public QQuickWidget,
+ public CPresentationChangeListener,
+ public TabNavigable
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QAbstractItemModel *actionsModel READ actionsModel NOTIFY itemChanged FINAL)
+ Q_PROPERTY(QAbstractItemModel *propertyModel READ propertyModel NOTIFY propertyModelChanged FINAL)
+ Q_PROPERTY(QString itemIcon READ itemIcon NOTIFY itemChanged FINAL)
+ Q_PROPERTY(QString itemText READ itemText NOTIFY itemTextChanged FINAL)
+ Q_PROPERTY(QColor itemColor READ itemColor NOTIFY itemChanged FINAL)
+ Q_PROPERTY(bool hasItem MEMBER m_hasItem NOTIFY hasItemChanged FINAL)
+ Q_PROPERTY(QString triggerObjectName READ triggerObjectName NOTIFY actionChanged FINAL)
+ Q_PROPERTY(QString targetObjectName READ targetObjectName NOTIFY actionChanged FINAL)
+ Q_PROPERTY(QString eventName READ eventName NOTIFY actionChanged FINAL)
+ Q_PROPERTY(QString handlerName READ handlerName NOTIFY actionChanged FINAL)
+ Q_PROPERTY(QVariantList handlerArguments READ handlerArguments NOTIFY actionChanged FINAL)
+ Q_PROPERTY(PropertyInfo property READ property NOTIFY propertyChanged FINAL)
+ Q_PROPERTY(QString firedEvent MEMBER m_firedEvent NOTIFY firedEventChanged FINAL)
+ Q_PROPERTY(bool propertyValueInvalid READ isPropertyValueInvalid NOTIFY propertyValueInvalidChanged FINAL)
+
+public:
+ ActionView(const QSize &preferredSize, QWidget *parent = nullptr);
+ ~ActionView() override;
+
+ QSize sizeHint() const override;
+
+ void setItem(const qt3dsdm::Qt3DSDMInstanceHandle &handle);
+ QString itemIcon() const;
+ QString itemText() const;
+ QColor itemColor() const;
+ QAbstractItemModel *actionsModel() const;
+ QAbstractItemModel *propertyModel() const;
+ QString targetObjectName() const;
+ QString triggerObjectName() const;
+ QString eventName() const;
+ QString handlerName() const;
+ QVariantList handlerArguments() const;
+ PropertyInfo property() const;
+ bool isPropertyValueInvalid() const;
+
+ Q_INVOKABLE void setCurrentActionIndex(int index);
+ Q_INVOKABLE void setCurrentPropertyIndex(int handle, int index);
+ Q_INVOKABLE void addAction();
+ Q_INVOKABLE void deleteAction(int index);
+ Q_INVOKABLE QObject *showTriggerObjectBrowser(const QPoint &point);
+ Q_INVOKABLE QObject *showTargetObjectBrowser(const QPoint &point);
+ Q_INVOKABLE void showContextMenu(int x, int y);
+ Q_INVOKABLE QObject *showEventBrowser(const QPoint &point);
+ Q_INVOKABLE QObject *showHandlerBrowser(const QPoint &point);
+ Q_INVOKABLE QObject *showEventBrowserForArgument(int handle, const QPoint &point);
+ Q_INVOKABLE void setArgumentValue(int handle, const QVariant &value);
+ Q_INVOKABLE QStringList slideNames();
+ Q_INVOKABLE int slideNameToIndex(const QString &name);
+ Q_INVOKABLE bool toolTipsEnabled();
+
+ // CPresentationChangeListener
+ void OnNewPresentation() override;
+ void OnClosingPresentation() override;
+
+ // ISelectionChangeListener
+ void OnSelectionSet(Q3DStudio::SSelectedValue inSelectable);
+
+ // Action callback
+ void OnActionAdded(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner);
+ void OnActionDeleted(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner);
+ void OnActionModified(qt3dsdm::Qt3DSDMActionHandle inAction);
+ void OnHandlerArgumentModified(qt3dsdm::Qt3DSDMHandlerArgHandle inHandlerArgument);
+ void OnInstancePropertyValueChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void OnInstanceDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void OnTargetSelectionChanged();
+ void OnTriggerSelectionChanged();
+
+protected:
+ void focusInEvent(QFocusEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ bool event(QEvent *event) override;
+
+Q_SIGNALS:
+ void itemChanged();
+ void itemTextChanged();
+ void actionChanged();
+ void propertyModelChanged();
+ void propertyChanged();
+ void firedEventChanged();
+ void hasItemChanged();
+ void propertyValueInvalidChanged();
+ void dialogCurrentColorChanged(const QColor &newColor);
+
+private Q_SLOTS:
+ void copyAction();
+ void cutAction();
+ void pasteAction();
+
+private:
+ void setTriggerObject(const qt3dsdm::SObjectRefType &object);
+ void setTargetObject(const qt3dsdm::SObjectRefType &object);
+ void setEvent(const qt3dsdm::Qt3DSDMEventHandle &event);
+ void setHandler(const qt3dsdm::Qt3DSDMHandlerHandle &handler);
+ QVariant handlerArgumentValue(int handle) const;
+ void updateHandlerArguments();
+ void emitActionChanged();
+ void updateFiredEvent();
+ void resetFiredEvent();
+ void updateFiredEventFromHandle(int handle);
+ void updateActionStates();
+ void setPropertyValueInvalid(bool invalid);
+ void clearPropertyValueInvalid();
+ void onAssetGraphChanged();
+
+ static CDoc *GetDoc();
+ static CClientDataModelBridge *GetBridge();
+
+ void initialize();
+ QColor m_baseColor = QColor::fromRgb(75, 75, 75);
+ QColor m_selectColor = Qt::transparent;
+ qt3dsdm::Qt3DSDMInstanceHandle m_itemHandle;
+ IObjectReferenceHelper *m_objRefHelper = nullptr;
+ ActionModel *m_actionsModel = nullptr;
+ PropertyModel *m_propertyModel = nullptr;
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>>
+ m_connections; /// connections to the DataModel
+ QPointer<ObjectListModel> m_objectsModel;
+ QPointer<ObjectBrowserView> m_triggerObjectBrowser;
+ QPointer<ObjectBrowserView> m_targetObjectBrowser;
+ QPointer<EventsModel> m_eventsModel;
+ QPointer<EventsModel> m_handlersModel;
+ QPointer<EventsModel> m_fireEventsModel;
+ QPointer<EventsBrowserView> m_eventsBrowser;
+ QPointer<EventsBrowserView> m_handlerBrowser;
+ QPointer<EventsBrowserView> m_fireEventsBrowser;
+ int m_currentActionIndex = -1;
+ int m_currentPropertyIndex = -1;
+ qt3dsdm::Qt3DSDMHandlerArgHandle m_currentPropertyNameHandle;
+ qt3dsdm::Qt3DSDMHandlerArgHandle m_currentPropertyValueHandle;
+ QVariantList m_handlerArguments;
+ QTimer m_actionChangedCompressionTimer;
+ QString m_firedEvent;
+ MouseHelper m_mouseHelper;
+ QSize m_preferredSize;
+ bool m_hasItem = false;
+ QAction *m_actionDel;
+ QAction *m_actionCopy;
+ QAction *m_actionCut;
+ QAction *m_actionPaste;
+ bool m_propertyValueInvalid = true;
+ QColor m_currentColor;
+ QPointer<QWidget> m_activeBrowser = nullptr;
+};
+
+#endif // ACTIONVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml
new file mode 100644
index 00000000..a5b905b3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml
@@ -0,0 +1,468 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import Qt3DStudio 1.0
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+
+ Item {
+ id: focusEater
+ objectName: "focusEater"
+ // Used to eat keyboard focus when user clicks outside any property control
+ }
+
+ Flickable {
+ id: actionFlickable
+ ScrollBar.vertical: ScrollBar {
+ id: scrollBar
+ visible: size < 1.0
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ z: -10
+ onPressed: {
+ mouse.accepted = false
+ focusEater.forceActiveFocus();
+ }
+ }
+
+ anchors.fill: parent
+ contentHeight: contentColumn.height
+
+ property bool scrollToBottom: false
+
+ onContentHeightChanged: {
+ if (scrollToBottom) {
+ scrollToBottom = false;
+ if (contentHeight > height)
+ contentY = contentHeight - height;
+ }
+ }
+
+ Column {
+ id: contentColumn
+ width: parent.width
+ spacing: 4
+
+ RowLayout {
+ height: _controlBaseHeight + 8
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 4
+ anchors.rightMargin: 12
+
+ Image {
+ id: headerImage
+ source: _parentView.itemIcon !== "" ? _resDir + _parentView.itemIcon : ""
+ }
+
+ StyledLabel {
+ Layout.fillWidth: true
+ text: _parentView.itemText
+ color: _parentView.itemColor
+ }
+
+ StyledToolButton {
+ enabled: actionsList.currentIndex !== -1
+ enabledImage: "Action-Trash-Normal.png"
+ disabledImage: "Action-Trash-Disabled.png"
+ toolTipText: qsTr("Delete (Del)")
+
+ onClicked: _parentView.deleteAction(actionsList.currentIndex)
+ }
+
+ StyledToolButton {
+ enabledImage: "add.png"
+ disabledImage: "add-disabled.png"
+ toolTipText: qsTr("New Action (") + _shiftKey + "A)"
+ enabled: _parentView.hasItem
+
+ onClicked: _parentView.addAction()
+ }
+ }
+ ListView {
+ id: actionsList
+ width: parent.width
+ height: count == 0 ? _controlBaseHeight : count * _controlBaseHeight
+ clip: true
+
+ Connections {
+ target: _parentView
+ // Clear the action selection on item selection change
+ onItemChanged: actionsList.currentIndex = -1
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: parent.count == 0
+
+ acceptedButtons: Qt.RightButton
+
+ onClicked: {
+ if (mouse.button == Qt.RightButton) {
+ var updateMousePosition = mapToItem(actionsList, mouse.x, mouse.y)
+ _parentView.showContextMenu(
+ updateMousePosition.x, updateMousePosition.y);
+ }
+ }
+ }
+ boundsBehavior: Flickable.StopAtBounds
+ model: _parentView.actionsModel
+
+ delegate: Rectangle {
+ id: delegateItem
+ objectName: "actionListDelegate"
+
+ width: actionsList.width
+ height: _controlBaseHeight
+ color: model.index === actionsList.currentIndex ? _selectionColor
+ : "transparent"
+
+ Row {
+ x: 10
+ y: 5
+ height: parent.height
+ width: parent.width - x
+ spacing: 4
+
+ Image {
+ id: visibilityIcon
+
+ source: model.visible ? _resDir + "Toggle-HideShow.png"
+ : _resDir + "Toggle-HideShow-disabled.png"
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: model.visible = !model.visible
+ }
+ }
+
+ StyledLabel {
+ text: model.description
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+ onPressed: {
+ actionsList.forceActiveFocus();
+ }
+
+ onClicked: {
+ actionFlickable.scrollToBottom = false;
+ actionsList.currentIndex = model.index;
+ _parentView.setCurrentActionIndex(model.index);
+ if (mouse.button == Qt.LeftButton && mouse.x < visibilityIcon.width + 10)
+ model.visible = !model.visible;
+
+ if (mouse.button == Qt.RightButton) {
+ var updateMousePosition = mapToItem(actionsList, mouse.x, mouse.y)
+ _parentView.showContextMenu(updateMousePosition.x, updateMousePosition.y);
+ }
+ }
+ onDoubleClicked: {
+ actionFlickable.scrollToBottom = false;
+ if (mouse.button == Qt.LeftButton && mouse.x > visibilityIcon.width + 10) {
+ // Scroll down to bottom to show properties on double click
+ if (actionFlickable.contentHeight > actionFlickable.height) {
+ actionFlickable.contentY = (actionFlickable.contentHeight
+ - actionFlickable.height)
+ }
+ // Since loading new property fields takes a moment, we want
+ // to keep the view scrolled to bottom
+ // when the content height changes the next time.
+ actionFlickable.scrollToBottom = true;
+ }
+ }
+ }
+ }
+
+ onCountChanged: {
+ if (currentIndex >= count)
+ currentIndex = count - 1;
+ }
+
+ onCurrentIndexChanged: _parentView.setCurrentActionIndex(currentIndex);
+ }
+
+ StyledMenuSeparator {
+ leftPadding: 12
+ rightPadding: 12
+ }
+
+ Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: childrenRect.height
+ visible: actionsList.currentIndex !== -1
+ spacing: 4
+
+ RowLayout {
+ x: 12
+ StyledLabel {
+ text: qsTr("Trigger Object")
+ }
+ BrowserCombo {
+ value: _parentView.triggerObjectName
+ onShowBrowser: activeBrowser = _parentView.showTriggerObjectBrowser(
+ mapToGlobal(width, 0));
+ }
+ }
+
+ RowLayout {
+ x: 12
+ StyledLabel {
+ text: qsTr("Event")
+ }
+ BrowserCombo {
+ value: _parentView.eventName
+ onShowBrowser: activeBrowser = _parentView.showEventBrowser(
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ StyledMenuSeparator {
+ visible: actionsList.currentIndex !== -1
+ leftPadding: 12
+ rightPadding: 12
+ }
+
+ Column {
+ visible: actionsList.currentIndex !== -1
+ width: parent.width
+ height: childrenRect.height
+ spacing: 4
+
+ RowLayout {
+ x: 12
+ StyledLabel {
+ text: qsTr("Target Object")
+ }
+
+ BrowserCombo {
+ value: _parentView.targetObjectName
+ onShowBrowser: activeBrowser = _parentView.showTargetObjectBrowser(
+ mapToGlobal(width, 0))
+ }
+ }
+
+ RowLayout {
+ x: 12
+ StyledLabel {
+ text: qsTr("Handler")
+ }
+
+ BrowserCombo {
+ value: _parentView.handlerName
+ onShowBrowser: activeBrowser = _parentView.showHandlerBrowser(
+ mapToGlobal(width, 0))
+ }
+ }
+
+ Component {
+ id: genericHandlerComponent
+
+ HandlerGenericText {
+ label: parent && parent.argument.name ? parent.argument.name : ""
+ value: parent && parent.argument.value ? parent.argument.value : ""
+
+ onEditingFinished: {
+ if (parent)
+ _parentView.setArgumentValue(parent.argument.handle, value)
+ }
+ }
+ }
+
+ Component {
+ id: floatHandlerComponent
+
+ HandlerGenericText {
+ label: parent && parent.argument.name ? parent.argument.name : ""
+ value: parent && parent.argument.value ? parent.argument.value : 0.0
+ validator: DoubleValidator {
+ decimals: 3
+ notation: DoubleValidator.StandardNotation
+ }
+
+ onEditingFinished: {
+ if (parent)
+ _parentView.setArgumentValue(parent.argument.handle, value)
+ }
+ }
+ }
+
+ Component {
+ id: signalHandlerComponent
+
+ HandlerGenericText {
+ label: parent && parent.argument.name ? parent.argument.name : ""
+ value: parent && parent.argument.value ? parent.argument.value : ""
+
+ onEditingFinished: {
+ if (parent)
+ _parentView.setArgumentValue(parent.argument.handle, value);
+ }
+ }
+ }
+
+ Component {
+ id: eventHandlerComponent
+
+ HandlerFireEvent {
+ label: parent && parent.argument.name ? parent.argument.name : ""
+ value: _parentView.firedEvent === "" ? qsTr("[Unknown Event]")
+ : _parentView.firedEvent
+
+ onShowBrowser: {
+ if (parent && parent.argument.handle) {
+ activeBrowser = _parentView.showEventBrowserForArgument(
+ parent.argument.handle, mapToGlobal(width, 0))
+ }
+ }
+ }
+ }
+
+ Component {
+ id: slideHandlerComponent
+
+ HandlerGoToSlide {
+ slideModel: _parentView.slideNames()
+ defaultSlideIndex: parent && parent.argument.value ? _parentView.slideNameToIndex(parent.argument.value)
+ : 0
+
+ onActivated: {
+ if (parent && parent.argument.handle && currentSlide)
+ _parentView.setArgumentValue(parent.argument.handle, currentSlide)
+ }
+ }
+ }
+
+ Component {
+ id: checkboxHandlerComponent
+
+ HandlerGenericCheckbox {
+ label: parent && parent.argument.name ? parent.argument.name : ""
+ checked: parent && parent.argument.value ? parent.argument.value : false
+
+ onClicked: {
+ if (parent && parent.argument.handle)
+ _parentView.setArgumentValue(parent.argument.handle, !checked)
+ }
+ }
+ }
+
+ Component {
+ id: propertyHandlerComponent
+
+ HandlerProperty {
+ propertyModel: _parentView.propertyModel
+ defaultPropertyIndex: propertyModel ? propertyModel.defaultPropertyIndex : 0
+
+ onPropertySelected: {
+ if (parent && parent.argument.handle)
+ _parentView.setCurrentPropertyIndex(parent.argument.handle, index);
+ }
+ }
+ }
+
+ Repeater {
+ model: _parentView.handlerArguments.length
+
+ Loader {
+ x: 12
+
+ readonly property var argument:_parentView.handlerArguments[model.index]
+
+ onLoaded: {
+ // HandlerProperty does its own tab order handling
+ if (argument.type !== HandlerArgumentType.Property) {
+ // Dynamic actions use group 0.
+ // We assume there is always just one tabbable argument per action,
+ // and the rest are dependent types.
+ _tabOrderHandler.clear();
+ if (item.tabItem1 !== undefined) {
+ _tabOrderHandler.addItem(0, item.tabItem1)
+ if (item.tabItem2 !== undefined) {
+ _tabOrderHandler.addItem(0, item.tabItem2)
+ if (item.tabItem3 !== undefined)
+ _tabOrderHandler.addItem(0, item.tabItem3)
+ }
+ }
+ }
+ }
+
+ sourceComponent: {
+ const handlerType = argument.type;
+ switch (handlerType) {
+ case HandlerArgumentType.None:
+ switch (argument.completeType) {
+ case CompleteMetaDataType.Boolean:
+ return checkboxHandlerComponent;
+ case CompleteMetaDataType.Float:
+ return floatHandlerComponent;
+ default:
+ return genericHandlerComponent;
+ }
+ case HandlerArgumentType.Event:
+ return eventHandlerComponent;
+ case HandlerArgumentType.Property:
+ return propertyHandlerComponent;
+ case HandlerArgumentType.Dependent:
+ return null; // no UI for Dependent type, they are the value for a property
+ case HandlerArgumentType.Signal:
+ return signalHandlerComponent;
+ case HandlerArgumentType.Slide:
+ return slideHandlerComponent
+ default: console.warn("KDAB_TODO implement handler for type: ", handlerType)
+ }
+ return null;
+ }
+ }
+ }
+ }
+
+ StyledMenuSeparator {
+ visible: actionsList.count > 0 && actionsList.currentIndex !== -1
+ leftPadding: 12
+ rightPadding: 12
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml
new file mode 100644
index 00000000..e5cb3998
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ ListView {
+ id: eventsList
+
+ Layout.margins: 5
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ currentIndex: _eventsBrowserView.selection
+
+ model: _eventsBrowserView.model
+
+ delegate: Item {
+ id: delegateItem
+
+ readonly property bool isCategory: model.isCategory
+
+ width: parent.width
+ height: model.parentExpanded ? _controlBaseHeight : 0
+ visible: height > 0
+
+ Behavior on height {
+ NumberAnimation {
+ duration: 100
+ easing.type: Easing.OutQuad
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: parent.height
+ color: model.index === eventsList.currentIndex ? _selectionColor
+ : "transparent"
+ Row {
+ id: row
+ width: parent.width
+ height: parent.height
+ spacing: 5
+
+ Image {
+ id: arrow
+ anchors.verticalCenter: parent.verticalCenter
+ source: {
+ if (!delegateItem.isCategory)
+ return "";
+ model.expanded ? _resDir + "arrow_down.png"
+ : _resDir + "arrow.png";
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: model.expanded = !model.expanded
+ }
+ }
+
+ Image { // group icon
+ anchors.verticalCenter: parent.verticalCenter
+ source: model.icon
+ }
+
+ StyledLabel {
+ id: name
+ leftPadding: isCategory ? 0 : 45
+ anchors.verticalCenter: parent.verticalCenter
+ text: model.name
+ }
+ }
+
+ MouseArea {
+ id: delegateArea
+ anchors.fill: parent
+ anchors.leftMargin: arrow.width
+ hoverEnabled: true
+ onClicked: {
+ if (!delegateItem.isCategory)
+ eventsList.currentIndex = model.index;
+ }
+ onEntered: itemDescription.text = model.description
+ onExited: itemDescription.text = ""
+ onDoubleClicked: {
+ if (!delegateItem.isCategory) {
+ eventsList.currentIndex = model.index;
+ _eventsBrowserView.close();
+ } else {
+ model.expanded = !model.expanded
+ }
+ }
+ }
+ }
+
+ }
+ onCurrentIndexChanged: _eventsBrowserView.selection = currentIndex
+
+ Connections {
+ target: _eventsBrowserView
+ onSelectionChanged: {
+ if (eventsList.currentIndex !== _eventsBrowserView.selection)
+ eventsList.currentIndex = _eventsBrowserView.selection;
+ }
+ }
+ }
+
+ StyledMenuSeparator {
+ bottomPadding: 0
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: _controlBaseHeight + 4
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 2
+
+ color: _backgroundColor
+
+ StyledLabel {
+ id: itemDescription
+ leftPadding: 6
+ anchors.fill: parent
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp
new file mode 100644
index 00000000..b7151af2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "EventsBrowserView.h"
+
+#include "EventsModel.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+EventsBrowserView::EventsBrowserView(QWidget *parent) : QQuickWidget(parent)
+{
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &EventsBrowserView::initialize);
+}
+
+QAbstractItemModel *EventsBrowserView::model() const
+{
+ return m_model;
+}
+
+void EventsBrowserView::setModel(EventsModel *model)
+{
+ if (m_model != model) {
+ m_model = model;
+ Q_EMIT modelChanged();
+ }
+}
+
+qt3dsdm::CDataModelHandle EventsBrowserView::selectedHandle() const
+{
+ const auto handleId = m_model->handleForRow(m_selection);
+ return handleId;
+}
+
+void EventsBrowserView::selectAndExpand(const QString &event)
+{
+ // All categories are expanded by default, so let's just select
+ m_blockCommit = true;
+ setSelection(m_model->rowForEventName(event));
+ m_blockCommit = false;
+
+}
+
+void EventsBrowserView::setSelection(int index)
+{
+ auto handleId = m_model->handleForRow(index);
+ if (!handleId.Valid()) {
+ m_selection = -1;
+ Q_EMIT selectionChanged();
+ } else if (m_selection != index) {
+ m_selection = index;
+ Q_EMIT selectionChanged();
+ }
+}
+
+void EventsBrowserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ QTimer::singleShot(0, this, &EventsBrowserView::close);
+}
+
+void EventsBrowserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &EventsBrowserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void EventsBrowserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_eventsBrowserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"),
+ StudioUtils::resourceImageUrl());
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Action/EventsBrowser.qml")));
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h
new file mode 100644
index 00000000..f8a2b7fa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef EVENTSBROWSERVIEW_H
+#define EVENTSBROWSERVIEW_H
+
+#include <QQuickWidget>
+
+#include "Qt3DSDMHandles.h"
+
+class EventsModel;
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+class EventsBrowserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel *model READ model NOTIFY modelChanged FINAL)
+ Q_PROPERTY(int selection READ selection WRITE setSelection NOTIFY selectionChanged FINAL)
+public:
+ explicit EventsBrowserView(QWidget *parent = nullptr);
+
+ QAbstractItemModel *model() const;
+ void setModel(EventsModel *model);
+ qt3dsdm::CDataModelHandle selectedHandle() const;
+
+ void selectAndExpand(const QString &event);
+
+ int selection() const { return m_selection; }
+ void setSelection(int index);
+
+ void setHandle(int handle) { m_handle = handle; }
+ int handle() const { return m_handle; }
+
+ bool canCommit() const { return !m_blockCommit; }
+
+Q_SIGNALS:
+ void modelChanged();
+ void selectionChanged();
+
+protected:
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void initialize();
+ EventsModel *m_model = nullptr;
+ QColor m_baseColor = QColor::fromRgb(75, 75, 75);
+ QColor m_selectColor;
+ int m_selection = -1;
+ int m_handle = -1;
+ bool m_blockCommit = false;
+};
+
+#endif // EVENTSBROWSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp
new file mode 100644
index 00000000..981ab95a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp
@@ -0,0 +1,276 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "EventsModel.h"
+
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "Qt3DSDMStudioSystem.h"
+
+EventsModel::EventsModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+void EventsModel::setEventList(const qt3dsdm::TEventHandleList &eventList)
+{
+ beginResetModel();
+
+ m_rowCount = 0;
+ m_events.clear();
+ m_categories.clear();
+
+ auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ auto theBridge = studioSystem->GetClientDataModelBridge();
+ auto thePos = eventList.begin();
+ for (; thePos != eventList.end(); ++thePos) {
+ qt3dsdm::SEventInfo theEvent = theBridge->GetEventInfo(*thePos);
+
+ CategoryInfo category;
+ category.name = QString::fromWCharArray(theEvent.m_Category.wide_str());
+ if (!m_events.contains(category.name)) {
+ qt3dsdm::SCategoryInfo theCategoryMetaData = studioSystem->GetActionMetaData()
+ ->GetEventCategory(theEvent.m_Category);
+ category.icon = QString::fromWCharArray(theCategoryMetaData.m_Icon.wide_str());
+ category.highlightIcon = QString::fromWCharArray(theCategoryMetaData.m_HighlightIcon.wide_str());
+ category.description = QString::fromWCharArray(theCategoryMetaData.m_Description.wide_str());
+ m_categories.append(category);
+ m_rowCount++;
+ }
+
+ EventInfo eventInfo;
+ // Use the formal name to display, but if the formal name is not set, use the name instead
+ eventInfo.name = QString::fromWCharArray(theEvent.m_FormalName.wide_str());
+ if (eventInfo.name.isEmpty())
+ eventInfo.name = QString::fromWCharArray(theEvent.m_Name.wide_str());
+ eventInfo.handle = *thePos;
+
+ eventInfo.description = QString::fromWCharArray(theEvent.m_Description.wide_str());
+ m_events[category.name].append(eventInfo);
+ m_rowCount++;
+
+ //KDAB_TODO set the selection to the current event
+ }
+
+ endResetModel();
+}
+
+void EventsModel::setHandlerList(const qt3dsdm::THandlerHandleList &handlerList)
+{
+ beginResetModel();
+ m_rowCount = 0;
+ m_events.clear();
+ m_categories.clear();
+
+ auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ auto theBridge = studioSystem->GetClientDataModelBridge();
+ auto thePos = handlerList.begin();
+ for (; thePos != handlerList.end(); ++thePos) {
+ qt3dsdm::SHandlerInfo handlerInfo = theBridge->GetHandlerInfo(*thePos);
+
+ CategoryInfo category;
+ category.name = QString::fromWCharArray(handlerInfo.m_Category.wide_str());
+ if (!m_events.contains(category.name)) {
+ qt3dsdm::SCategoryInfo theCategoryMetaData = studioSystem->GetActionMetaData()
+ ->GetHandlerCategory(handlerInfo.m_Category);
+ category.icon = QString::fromWCharArray(theCategoryMetaData.m_Icon.wide_str());
+ category.highlightIcon = QString::fromWCharArray(theCategoryMetaData.m_HighlightIcon.wide_str());
+ category.description = QString::fromWCharArray(theCategoryMetaData.m_Description.wide_str());
+ m_categories.append(category);
+ m_rowCount++;
+ }
+
+ EventInfo eventInfo;
+ // Use the formal name to display, but if the formal name is not set, use the name instead
+ eventInfo.name = QString::fromWCharArray(handlerInfo.m_FormalName.wide_str());
+ if (eventInfo.name.isEmpty())
+ eventInfo.name = QString::fromWCharArray(handlerInfo.m_Name.wide_str());
+ eventInfo.handle = *thePos;
+
+ eventInfo.description = QString::fromWCharArray(handlerInfo.m_Description.wide_str());
+ m_events[category.name].append(eventInfo);
+ m_rowCount++;
+
+ //KDAB_TODO set the selection to the current event
+ }
+
+ endResetModel();
+}
+
+int EventsModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_rowCount;
+}
+
+QVariant EventsModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(), index.parent()))
+ return {};
+
+ const auto row = index.row();
+ auto category = categoryForRow(row);
+
+ bool isCategory = category.isValid();
+ EventInfo event;
+ if (!isCategory)
+ event = eventForRow(row);
+
+ switch (role) {
+ case NameRole:
+ return isCategory ? category.name : event.name;
+ case DescriptionRole:
+ return isCategory ? category.description: event.description;
+ case IconRole:
+ return isCategory ? StudioUtils::resourceImageUrl() + category.icon : QString();
+ case HighlightedIconRole:
+ return isCategory ? StudioUtils::resourceImageUrl() + category.highlightIcon : QString();
+ case ExpandedRole:
+ return isCategory ? category.expanded : false;
+ case ParentExpandedRole: {
+ if (isCategory)
+ return true;
+ for (int i = row - 1; i >= 0; i--) {
+ auto parentCategory = categoryForRow(i);
+ if (parentCategory.isValid())
+ return parentCategory.expanded;
+ }
+ return false;
+ }
+ case IsCategoryRole:
+ return isCategory;
+ }
+
+ return QVariant();
+}
+
+bool EventsModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (role == ExpandedRole) {
+ int catRow = categoryRowForRow(index.row());
+ if (catRow != -1) {
+ auto category = &m_categories[catRow];
+ category->expanded = value.toBool();
+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {});
+ return true;
+ }
+ }
+ return false;
+}
+
+QHash<int, QByteArray> EventsModel::roleNames() const
+{
+ auto names = QAbstractItemModel::roleNames();
+ names.insert(NameRole, "name");
+ names.insert(DescriptionRole, "description");
+ names.insert(IconRole, "icon");
+ names.insert(HighlightedIconRole, "highlightedIcon");
+ names.insert(IsCategoryRole, "isCategory");
+ names.insert(ExpandedRole, "expanded");
+ names.insert(ParentExpandedRole, "parentExpanded");
+
+ return names;
+}
+
+qt3dsdm::CDataModelHandle EventsModel::handleForRow(int row) const
+{
+ if (row < 0 || row >= m_rowCount)
+ return {};
+
+ auto event = eventForRow(row);
+ if (event.isValid())
+ return event.handle;
+
+ return {};
+}
+
+int EventsModel::rowForEventName(const QString &event) const
+{
+ int i = 0;
+ for (const auto &category: m_categories) {
+ i++;
+ const auto events = m_events[category.name];
+ for (int j = 0; j < events.size(); j++, i++) {
+ if (events[j].name == event)
+ return i;
+ }
+ }
+ return i;
+}
+
+EventsModel::CategoryInfo EventsModel::categoryForRow(int row) const
+{
+ int i = 0;
+ for (const auto &category: m_categories) {
+ if (i == row)
+ return category;
+ i += m_events[category.name].size();
+ i++;
+ }
+
+ return {};
+}
+
+int EventsModel::categoryRowForRow(int row) const
+{
+ int i = 0;
+ int catRow = 0;
+ for (const auto &category: m_categories) {
+ if (i == row)
+ return catRow;
+ i += m_events[category.name].size();
+ i++;
+ catRow++;
+ }
+
+ return -1;
+}
+
+EventsModel::EventInfo EventsModel::eventForRow(int row) const
+{
+ if (row == 0) // first line is not an event, but a category
+ return {};
+
+ int i = 0;
+ for (const auto &category: m_categories) {
+ i++;
+ const auto events = m_events[category.name];
+ const int index = (row - i);
+ if (row < i + events.size() && (index >= 0) ) {
+ return events[index];
+ }
+ i += events.size();
+ }
+
+ return {};
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h
new file mode 100644
index 00000000..b0f2f4fb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef EVENTSMODEL_H
+#define EVENTSMODEL_H
+
+#include <QAbstractListModel>
+
+#include "Qt3DSDMHandles.h"
+
+/** Model for both action events and action handlers */
+class EventsModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit EventsModel(QObject *parent = nullptr);
+
+ void setEventList(const qt3dsdm::TEventHandleList &eventList);
+ void setHandlerList(const qt3dsdm::THandlerHandleList &handlerList);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole) override;
+
+ enum Roles {
+ NameRole = Qt::DisplayRole,
+ DescriptionRole = Qt::UserRole + 1,
+ IconRole,
+ HighlightedIconRole,
+ ExpandedRole,
+ ParentExpandedRole,
+ IsCategoryRole
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ qt3dsdm::CDataModelHandle handleForRow(int row) const;
+ int rowForEventName(const QString &event) const;
+
+private:
+ struct EventInfo {
+ qt3dsdm::CDataModelHandle handle;
+ QString name;
+ QString description;
+
+ bool isValid() const { return handle.Valid(); }
+ };
+
+ struct CategoryInfo {
+ QString name;
+ QString icon;
+ QString description;
+ QString highlightIcon;
+ bool expanded = true;
+
+ bool isValid() const { return !name.isEmpty(); }
+ };
+
+ CategoryInfo categoryForRow(int row) const;
+ int categoryRowForRow(int row) const;
+ EventInfo eventForRow(int row) const;
+
+ QHash<QString, QVector<EventInfo> > m_events;
+ QVector<CategoryInfo> m_categories;
+ int m_rowCount = 0;
+};
+
+#endif // EVENTSMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml
new file mode 100644
index 00000000..fbab75cb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+ScrollView {
+ id: root
+ signal editingFinished
+ signal textChanged
+ property alias value: textArea.text
+ property Item tabItem1: textArea
+
+ clip: true
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ScrollBar.vertical.policy: ScrollBar.AsNeeded
+
+ TextArea {
+ id: textArea
+ property bool ignoreHotkeys: true
+
+ horizontalAlignment: TextInput.AlignLeft
+ verticalAlignment: TextInput.AlignTop
+ font.pixelSize: _fontSize
+ color: _textColor
+ selectionColor: _selectionColor
+ selectedTextColor: _textColor
+
+ topPadding: 6
+ bottomPadding: 6
+ rightPadding: 6
+
+ wrapMode: TextEdit.WrapAnywhere
+ background: Rectangle {
+ anchors.fill: parent
+ color: textArea.enabled ? _studioColor2 : "transparent"
+ border.width: textArea.activeFocus ? 1 : 0
+ border.color: textArea.activeFocus ? _selectionColor : _disabledColor
+ }
+
+ MouseArea {
+ id: mouseArea
+ property int clickedPos
+
+ anchors.fill: parent
+ preventStealing: true
+ onPressed: {
+ textArea.forceActiveFocus()
+ clickedPos = textArea.positionAt(mouse.x, mouse.y)
+ textArea.cursorPosition = clickedPos
+ }
+ onDoubleClicked: textArea.selectAll()
+ onPositionChanged: {
+ textArea.cursorPosition = textArea.positionAt(mouse.x, mouse.y)
+ textArea.select(clickedPos, textArea.cursorPosition)
+ }
+ }
+ onTextChanged: root.textChanged()
+ onEditingFinished: root.editingFinished()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml
new file mode 100644
index 00000000..16eac96e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias value: textField.text
+ property Item tabItem1: textfield
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("Signal Name")
+ }
+
+ StyledTextField {
+ id: textField
+ Layout.preferredWidth: _valueWidth
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml
new file mode 100644
index 00000000..5b2ca0f6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias value: comboField.value
+ property alias activeBrowser: comboField.activeBrowser
+
+ signal showBrowser
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("Event")
+ }
+
+ BrowserCombo {
+ id: comboField
+ Layout.preferredWidth: _valueWidth
+ value: qsTr("[Unknown Event]")
+ onShowBrowser: root.showBrowser()
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml
new file mode 100644
index 00000000..8c184409
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Layouts 1.3
+
+RowLayout {
+ id: root
+
+ property alias color: rect.color
+ property color selectedColor: "black"
+ property bool listenToColorChanges: false
+
+ signal colorSelected()
+ signal previewColorSelected()
+
+ Connections {
+ target: _parentView
+ onDialogCurrentColorChanged: {
+ if (root.listenToColorChanges) {
+ root.selectedColor = newColor;
+ root.previewColorSelected();
+ }
+ }
+ }
+
+ Rectangle {
+ id: rect
+
+ width: _valueWidth / 4
+ height: _controlBaseHeight
+
+ border {
+ width: 1
+ color: _studioColor2
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ anchors.fill: parent
+ onClicked: {
+ root.listenToColorChanges = true;
+ _inspectorModel.suspendMaterialRename(true);
+ root.selectedColor = _parentView.showColorDialog(rect.color, instance, handle);
+ root.listenToColorChanges = false;
+ _inspectorModel.suspendMaterialRename(false);
+ root.colorSelected();
+ }
+ }
+
+ Image {
+ id: img
+ // Source image size is 16x16 pixels
+ x: parent.width - 18
+ y: parent.height / 2 - 8
+ source: _resDir + "arrow_down.png"
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml
new file mode 100644
index 00000000..8446f761
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property bool checked: false
+ property alias label: labelField.text
+
+ signal clicked()
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("Pause")
+ }
+
+ Image {
+ source: _resDir + (checked ? "checkbox-checked.png" : "checkbox-unchecked.png")
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: root.clicked()
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml
new file mode 100644
index 00000000..cad079ee
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias color: handlerGenericColor.color
+ property alias selectedColor: handlerGenericColor.selectedColor
+
+ signal colorSelected()
+ signal previewColorSelected()
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("New Value")
+ }
+
+ HandlerGenericBaseColor {
+ id: handlerGenericColor
+
+ onColorSelected: root.colorSelected();
+ onPreviewColorSelected: root.previewColorSelected();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml
new file mode 100644
index 00000000..11ac38a5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property real desiredValue: Number(floatField.text)
+ property real value: 0
+ property int numberOfDecimal: 3
+ property Item tabItem1: floatField
+
+ signal editingFinished
+ signal previewValueChanged
+
+ onValueChanged: {
+ // FloatTextField can set its text internally, thus breaking the binding, so
+ // let's set the text value explicitly each time value changes
+ floatField.text = Number(value).toFixed(numberOfDecimal);
+ }
+
+ StyledLabel {
+ id: labelField
+ }
+
+ FloatTextField {
+ id: floatField
+ Layout.preferredWidth: _valueWidth
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml
new file mode 100644
index 00000000..738bf6a3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias value: textField.text
+ property alias validator: textField.validator
+ property Item tabItem1: textField
+
+ signal editingFinished
+
+ onValueChanged: {
+ textField.text = value;
+ }
+
+ StyledLabel {
+ id: labelField
+ }
+
+ StyledTextField {
+ id: textField
+ onEditingFinished: root.editingFinished();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml
new file mode 100644
index 00000000..6ad1564b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias slideModel: comboSlide.model
+ property string currentSlide
+ property int defaultSlideIndex: 0
+
+ signal activated()
+
+ onDefaultSlideIndexChanged: comboSlide.currentIndex = defaultSlideIndex
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("Slide")
+ }
+
+ StyledComboBox {
+ id: comboSlide
+ Layout.preferredWidth: _valueWidth
+ model: slideModel
+
+ onActivated: {
+ currentSlide = comboSlide.textAt(currentIndex);
+ root.activated();
+ }
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml
new file mode 100644
index 00000000..834b1083
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias value: multiLine.value
+ property alias tabItem1: multiLine.tabItem1
+
+ signal editingFinished
+ signal textChanged
+
+ onValueChanged: {
+ multiLine.value = value;
+ }
+
+ StyledLabel {
+ id: labelField
+ }
+
+ HandlerBaseMultilineText {
+ id: multiLine
+
+ Layout.preferredWidth: _valueWidth
+ Layout.preferredHeight: _controlBaseHeight * 3
+
+ onTextChanged: root.textChanged();
+ onEditingFinished: root.editingFinished();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml
new file mode 100644
index 00000000..a101488e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml
@@ -0,0 +1,290 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import Qt3DStudio 1.0
+import "../controls"
+
+ColumnLayout {
+ id: root
+
+ property alias propertyModel: propertyCombo.model
+ property int defaultPropertyIndex: 0
+
+ signal propertySelected(int index)
+
+ onDefaultPropertyIndexChanged: propertyCombo.currentIndex = defaultPropertyIndex
+
+ RowLayout {
+
+ Layout.fillWidth: true
+
+ StyledLabel {
+ text: qsTr("Property")
+ }
+
+ StyledComboBox {
+ id: propertyCombo
+ textRole: "name"
+ onActivated: root.propertySelected(currentIndex)
+ onModelChanged: currentIndex = root.defaultPropertyIndex
+ }
+ }
+
+ Component {
+ id: multiLineComponent
+
+ HandlerMultilineText {
+ readonly property var actionProperty: parent ? _parentView.property : null
+
+ label: parent ? parent.label : ""
+ value: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined ? propertyModel.value : ""
+ onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, value)
+ }
+ }
+
+ Component {
+ id: fontSizeComponent
+
+ HandlerPropertyCombo {
+ readonly property var actionProperty: parent ? _parentView.property : null
+ property var propertyValue: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined
+ ? propertyModel.value : ""
+
+ label: parent ? parent.label : ""
+ comboModel: ["8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26",
+ "28", "36", "48", "72", "96", "120"];
+
+ onValueChanged: _parentView.setArgumentValue(propertyModel.valueHandle, value)
+ onPropertyValueChanged: currentIndex = find(propertyValue)
+ }
+ }
+
+ Component {
+ id: xyzPropertyComponent
+
+ HandlerPropertyXYZ {
+ readonly property var propValue: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined
+ ? propertyModel.value : undefined
+ label: parent ? parent.label : ""
+ valueX: propValue !== undefined ? Number(propValue.x).toFixed(numberOfDecimal) : "0.000"
+ valueY: propValue !== undefined ? Number(propValue.y).toFixed(numberOfDecimal) : "0.000"
+ valueZ: propValue !== undefined ? Number(propValue.z).toFixed(numberOfDecimal) : "0.000"
+
+ onPropValueChanged: {
+ // FloatTextField can set its text internally, thus breaking the binding, so
+ // let's set the text value explicitly each time value changes
+ if (propValue !== undefined) {
+ valueX = Number(propValue.x).toFixed(numberOfDecimal);
+ valueY = Number(propValue.y).toFixed(numberOfDecimal);
+ valueZ = Number(propValue.z).toFixed(numberOfDecimal);
+ }
+ }
+
+ onEditingFinished: {
+ _parentView.setArgumentValue(propertyModel.valueHandle,
+ Qt.vector3d(valueX, valueY, valueZ), true);
+ }
+ }
+ }
+
+ Component {
+ id: sliderPropertyComponent
+
+ HandlerPropertySlider {
+ readonly property var actionProperty: parent ? _parentView.property : null
+
+ sliderMin: actionProperty ? actionProperty.min : 0
+ sliderMax: actionProperty ? actionProperty.max : 100
+ intSlider: actionProperty ? actionProperty.type === DataModelDataType.Long : false
+ value: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined ? propertyModel.value : sliderMin
+ label: parent ? parent.label : ""
+
+ // We don't need to care about preview for action sliders
+ onCommitValue: _parentView.setArgumentValue(propertyModel.valueHandle, desiredValue)
+ }
+ }
+
+ Component {
+ id: comboPropertyComponent
+
+ HandlerPropertyCombo {
+ readonly property var actionProperty: parent ? _parentView.property : null
+ property var propertyValue: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined
+ ? propertyModel.value : ""
+
+ label: parent ? parent.label : ""
+ comboModel: actionProperty ? actionProperty.possibleValues : null
+
+ onValueChanged: _parentView.setArgumentValue(propertyModel.valueHandle, value)
+ onPropertyValueChanged: currentIndex = find(propertyValue)
+ }
+ }
+
+ Component {
+ id: booleanComponent
+
+ HandlerGenericCheckbox {
+ label: parent ? parent.label : ""
+ checked: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined ? propertyModel.value : false
+
+ onClicked: {
+ _parentView.setArgumentValue(propertyModel.valueHandle, !checked)
+ }
+ }
+ }
+
+ Component {
+ id: colorBox
+
+ HandlerGenericColor {
+ readonly property var propValue: propertyModel && !_parentView.propertyValueInvalid
+ ? propertyModel.value : undefined
+
+ label: parent ? parent.label : ""
+ color: "black"
+ onColorSelected: {
+ color = selectedColor;
+ _parentView.setArgumentValue(propertyModel.valueHandle, selectedColor);
+ }
+ onPreviewColorSelected: color = selectedColor
+ onPropValueChanged: {
+ color = propValue ? Qt.rgba(propValue.x, propValue.y, propValue.z, 1)
+ : "black";
+ }
+ }
+ }
+
+ Component {
+ id: genericTextComponent
+
+ HandlerGenericText {
+ label: parent ? parent.label : ""
+ value: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined ? propertyModel.value : ""
+ onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, value)
+ }
+ }
+
+ Component {
+ id: floatPropertyComponent
+
+ HandlerGenericFloat {
+ label: parent ? parent.label : ""
+ value: propertyModel && !_parentView.propertyValueInvalid
+ && propertyModel.value !== undefined
+ ? Number(propertyModel.value).toFixed(numberOfDecimal) : 0
+
+ onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, desiredValue)
+ }
+ }
+
+ Loader {
+ readonly property string label: qsTr("New Value")
+ readonly property var actionProperty: _parentView.property
+
+ Layout.fillWidth: true
+
+ onLoaded: {
+ _tabOrderHandler.clear();
+ if (item.tabItem1 !== undefined) {
+ _tabOrderHandler.addItem(0, item.tabItem1)
+ if (item.tabItem2 !== undefined) {
+ _tabOrderHandler.addItem(0, item.tabItem2)
+ if (item.tabItem3 !== undefined)
+ _tabOrderHandler.addItem(0, item.tabItem3)
+ }
+ }
+ }
+
+ sourceComponent: {
+ // KDAB_TODO Handle additionaltype
+ switch (actionProperty.type) {
+ case DataModelDataType.Float:
+ switch (actionProperty.additionalType) {
+ case AdditionalMetaDataType.FontSize:
+ return fontSizeComponent;
+ case AdditionalMetaDataType.Range:
+ return sliderPropertyComponent;
+ default:
+ return floatPropertyComponent;
+ }
+ case DataModelDataType.Long:
+ return sliderPropertyComponent;
+ case DataModelDataType.Float3:
+ switch (actionProperty.additionalType) {
+ case AdditionalMetaDataType.None:
+ case AdditionalMetaDataType.Rotation:
+ return xyzPropertyComponent;
+ default:
+ console.warn("KDAB_TODO implement property handler for additional " +
+ "typeDataModelDataType.Float3: ", actionProperty.additionalType);
+ return xyzPropertyComponent;
+ }
+ case DataModelDataType.Float4:
+ if (actionProperty.additionalType === AdditionalMetaDataType.Color)
+ return colorBox;
+ break;
+
+ case DataModelDataType.String:
+ switch (actionProperty.additionalType) {
+ case AdditionalMetaDataType.StringList:
+ return comboPropertyComponent;
+ case AdditionalMetaDataType.MultiLine:
+ return multiLineComponent;
+ case AdditionalMetaDataType.Font:
+ return comboPropertyComponent;
+ case AdditionalMetaDataType.Import:
+ case AdditionalMetaDataType.Renderable:
+ case AdditionalMetaDataType.String:
+ return genericTextComponent;
+ default:
+ console.warn("KDAB_TODO implement property handler for additional type: ",
+ actionProperty.additionalType)
+ return null;
+ }
+ case DataModelDataType.Bool:
+ return booleanComponent;
+ case DataModelDataType.None:
+ return null;
+ default: console.warn("KDAB_TODO implement property handler for type: ",
+ actionProperty.type)
+
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml
new file mode 100644
index 00000000..7019dff2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml
@@ -0,0 +1,241 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+/*
+* Use for: Opacity, Edge Tesselation Value, Inner Tesselation Value ...
+* For the latter two set sliderMax to 64
+*/
+
+Row {
+ id: root
+
+ property real value: 0 // This is the value coming from backend
+ property alias desiredValue: slider.value // This is value adjusted by user
+ property alias sliderMin: slider.from
+ property alias sliderMax: slider.to
+ property real sliderDecimals: -1
+ property bool intSlider: false
+ property int decimalSlider: sliderDecimals >= 0 ? sliderDecimals
+ : Math.min(precision(slider.stepSize), 3)
+ property Item tabItem1: textField
+
+ signal previewValue // Indicates desiredValue contains a preview value
+ signal commitValue // Indicates desiredValue contains a final value to be committed
+
+ spacing: 5
+ width: _valueWidth
+
+ function doCommitValue() {
+ wheelCommitTimer.stop();
+ if (rateLimiter.running)
+ rateLimiter.stop();
+ textField.setTextFieldValue();
+ root.commitValue();
+ }
+
+ // get the number of decimals in a float/double
+ function precision(a) {
+ if (!isFinite(a)) return 0;
+ var e = 1, p = 0;
+ while (Math.round(a * e) / e !== a) { e *= 10; p++; }
+ return p;
+ }
+
+ onValueChanged: {
+ slider.value = value;
+ textField.setTextFieldValue();
+ }
+
+ Keys.onPressed: {
+ if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
+ event.accepted = true
+ var delta = 1.0;
+ if (intSlider) {
+ if (event.key === Qt.Key_Down)
+ delta = -delta;
+ slider.value = Number(slider.value + delta).toFixed(0);
+ } else {
+ if (event.modifiers === Qt.ControlModifier)
+ delta = 0.1;
+ else if (event.modifiers === Qt.ShiftModifier)
+ delta = 10.0;
+ if (event.key === Qt.Key_Down)
+ delta = -delta;
+ slider.value = Number(slider.value + delta).toFixed(doubleValidator.decimals);
+ }
+ wheelCommitTimer.stop();
+ if (!rateLimiter.running)
+ rateLimiter.start();
+ textField.setTextFieldValue();
+ }
+ }
+
+ Slider {
+ id: slider
+
+ leftPadding: 0
+
+ background: Rectangle {
+ x: slider.leftPadding
+ y: slider.topPadding + slider.availableHeight / 2 - height / 2
+ implicitWidth: _valueWidth - textField.width - 5
+ implicitHeight: 6
+ height: implicitHeight
+ radius: 2
+ color: _studioColor2
+ }
+ handle: Rectangle {
+ x: slider.leftPadding + slider.visualPosition * slider.availableWidth
+ y: slider.topPadding + slider.availableHeight / 2 - height / 2
+ implicitWidth: 6
+ implicitHeight: 12
+ color: _studioColor3
+ radius: 2
+ }
+
+ from: 0
+ to: 100
+ stepSize: root.intSlider ? 1 : sliderStepFromRange(slider.to, slider.from, 100)
+ snapMode: root.intSlider ? Slider.SnapAlways : Slider.NoSnap
+
+ function sliderStepFromRange(top, bottom, steps) {
+ return ((top - bottom) / steps);
+ }
+
+ onMoved: {
+ wheelCommitTimer.stop();
+ if (!rateLimiter.running)
+ rateLimiter.start();
+ textField.setTextFieldValue();
+ }
+
+ // onPressedChanged is triggered both mouse clicks and arrow keys, so adjusting with arrow
+ // keys will create undo point for each tick slider moves (even when holding the key down)
+ onPressedChanged: {
+ if (!pressed)
+ root.doCommitValue();
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.NoButton
+
+ onWheel: {
+ var delta = (wheel.angleDelta.x != 0) ? wheel.angleDelta.x
+ : wheel.angleDelta.y;
+
+ if (delta > 0)
+ slider.increase();
+ else
+ slider.decrease();
+ if (!rateLimiter.running)
+ rateLimiter.start();
+ textField.setTextFieldValue();
+
+ // Leaving a transaction open can interfere with other editor functionality,
+ // so commit the wheel transaction after a brief delay
+ wheelCommitTimer.restart();
+ }
+ Timer {
+ id: wheelCommitTimer
+ interval: 1000
+ onTriggered: {
+ root.doCommitValue();
+ }
+ }
+ }
+ }
+
+ Timer {
+ id: rateLimiter
+ interval: 10
+ onTriggered: {
+ root.previewValue();
+ }
+ }
+
+ DoubleValidator {
+ id: doubleValidator
+
+ decimals: decimalSlider
+ bottom: slider.from
+ top: slider.to
+ locale: "C"
+ }
+
+ IntValidator {
+ id: intValidator
+
+ bottom: slider.from
+ top: slider.to
+ }
+
+ StyledTextField {
+ id: textField
+
+ height: _controlBaseHeight
+ width: 55
+ text: intSlider ? slider.value.toFixed(0) : slider.value.toFixed(decimalSlider)
+
+ validator: intSlider ? intValidator : doubleValidator
+
+ onTextEdited: {
+ if (!intSlider && text.search(",")) {
+ text = text.replace(",",".")
+ }
+ if (intSlider) {
+ // handle limiting integer values when entered value is less than
+ // minimum value since IntValidator doesn't handle this
+ if (text.length >= sliderMin.toString().length && text < sliderMin)
+ text = text.substring(0, text.length - 1)
+ }
+ }
+
+ onEditingFinished: {
+ if (text > sliderMax)
+ text = sliderMax
+ else if (text < sliderMin)
+ text = sliderMin
+ slider.value = text
+ root.doCommitValue();
+ }
+
+ function setTextFieldValue() {
+ text = intSlider ? slider.value.toFixed(0) : slider.value.toFixed(decimalSlider)
+ }
+ onActiveFocusChanged: {
+ if (!activeFocus)
+ setTextFieldValue()
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml
new file mode 100644
index 00000000..4406703f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+// Used for: Tiling
+
+RowLayout {
+ id: root
+
+ property alias valueX: textFieldX.text
+ property alias valueY: textFieldY.text
+ property int numberOfDecimal: 3
+ property Item tabItem1: textFieldX
+ property Item tabItem2: textFieldY
+
+ signal editingFinished
+ signal previewValueChanged
+
+ spacing: 0
+
+ StyledLabel {
+ Layout.preferredWidth: 10
+ text: qsTr("X")
+ color: _xAxisColor
+ }
+
+ FloatTextField {
+ id: textFieldX
+ Layout.preferredWidth: (_valueWidth - 40) / 2
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+
+ Item { width: 20 }
+
+ StyledLabel {
+ Layout.preferredWidth: 10
+ text: qsTr("Y")
+ color: _yAxisColor
+ }
+
+ FloatTextField {
+ id: textFieldY
+ Layout.preferredWidth: (_valueWidth - 40) / 2
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml
new file mode 100644
index 00000000..50440dba
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+/* Use for: Position, Rotation, Scale, Pivot ... */
+
+RowLayout {
+ id: root
+
+ property alias valueX: textFieldX.text
+ property alias valueY: textFieldY.text
+ property alias valueZ: textFieldZ.text
+ property int numberOfDecimal: 3
+ property Item tabItem1: textFieldX
+ property Item tabItem2: textFieldY
+ property Item tabItem3: textFieldZ
+
+ signal editingFinished
+ signal previewValueChanged
+ transformOrigin: Item.Center
+ spacing: 0
+
+ StyledLabel {
+ Layout.preferredWidth: 10
+ text: qsTr("X")
+ color: _xAxisColor
+ }
+
+ FloatTextField {
+ id: textFieldX
+ Layout.preferredWidth: (_valueWidth - 50) / 3
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+
+ Item { width: 10 }
+
+ StyledLabel {
+ Layout.preferredWidth: 10
+ text: qsTr("Y")
+ color: _yAxisColor
+ }
+
+ FloatTextField {
+ id: textFieldY
+ Layout.preferredWidth: (_valueWidth - 50) / 3
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+
+ Item { width: 10 }
+
+ StyledLabel {
+ Layout.preferredWidth: 10
+ text: qsTr("Z")
+ color: _zAxisColor
+ }
+
+ FloatTextField {
+ id: textFieldZ
+ Layout.preferredWidth: (_valueWidth - 50) / 3
+ decimalValue: numberOfDecimal
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml
new file mode 100644
index 00000000..37e76aa9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+/* Use for Tesselation mode, Horizontal alignment, Vertical alignment ... */
+
+RowLayout {
+ id: root
+
+ property alias label: labelField.text
+ property alias comboModel : comboBox.model
+ property alias comboTextRole: comboBox.textRole
+ property alias currentIndex: comboBox.currentIndex
+ property string value
+
+ function find(text) {
+ return comboBox.find(text);
+ }
+
+ StyledLabel {
+ id: labelField
+ text: qsTr("New Value")
+ }
+
+ StyledComboBox {
+ id: comboBox
+
+ Layout.fillWidth: true
+ onActivated: value = comboBox.textAt(currentIndex)
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml
new file mode 100644
index 00000000..db6f88f2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+/*
+* Use for: Opacity, Edge Tesselation Value, Inner Tesselation Value ...
+* For the latter two set sliderMax to 64
+*/
+
+GridLayout {
+ id: root
+
+ property alias value: propertySlider.value
+ property alias desiredValue: propertySlider.desiredValue
+ property alias sliderMin: propertySlider.sliderMin
+ property alias sliderMax: propertySlider.sliderMax
+ property alias label: labelItem.text
+ property alias intSlider: propertySlider.intSlider
+ property alias decimalSlider: propertySlider.decimalSlider
+ property alias tabItem1: propertySlider.tabItem1
+
+ signal previewValue
+ signal commitValue
+
+ columns: 3
+
+ StyledLabel {
+ id: labelItem
+ text: label
+ }
+
+ HandlerPropertyBaseSlider {
+ id: propertySlider
+ // proxy the signal upwards
+ onCommitValue: root.commitValue()
+ onPreviewValue: root.previewValue()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml
new file mode 100644
index 00000000..6571f1d0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+/* Use for: Position, Rotation, Scale, Pivot ... */
+
+RowLayout {
+ id: root
+
+ property alias valueX: propertyXYZ.valueX
+ property alias valueY: propertyXYZ.valueY
+ property alias valueZ: propertyXYZ.valueZ
+ property alias label: labelItem.text
+ property alias tabItem1: propertyXYZ.tabItem1
+ property alias tabItem2: propertyXYZ.tabItem2
+ property alias tabItem3: propertyXYZ.tabItem3
+ property alias numberOfDecimal: propertyXYZ.numberOfDecimal
+
+ signal editingFinished
+ signal previewValueChanged
+
+ StyledLabel {
+ id: labelItem
+ Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+ text: qsTr("New Value")
+ }
+
+ HandlerPropertyBaseXYZ {
+ id: propertyXYZ
+ Layout.alignment: Qt.AlignRight
+
+ onEditingFinished: root.editingFinished()
+ onPreviewValueChanged: root.previewValueChanged()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp
new file mode 100644
index 00000000..8024204d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "PropertyModel.h"
+
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+
+#include "Qt3DSDMActionCore.h"
+#include "Qt3DSDMActionInfo.h"
+#include "Qt3DSDMDataCore.h"
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSDMStudioSystem.h"
+
+
+PropertyModel::PropertyModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+void PropertyModel::setAction(const qt3dsdm::Qt3DSDMActionHandle &action)
+{
+ beginResetModel();
+ m_action = action;
+ m_valueHandle = 0;
+ m_nameHandle = 0;
+ m_properties.clear();
+
+ if (action.Valid()) {
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto studioSystem = doc->GetStudioSystem();
+ auto propertySystem = studioSystem->GetPropertySystem();
+ auto bridge = studioSystem->GetClientDataModelBridge();
+
+ auto actionInfo = studioSystem->GetActionCore()->GetActionInfo(action);
+
+ qt3dsdm::IMetaData &metaData(*studioSystem->GetActionMetaData());
+ qt3dsdm::TMetaDataPropertyHandleList metaProperties;
+ const auto instance = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject);
+ if (instance.Valid()) {
+ metaData.GetMetaDataProperties(instance, metaProperties);
+
+ for (const auto &metaProperty: metaProperties) {
+ auto propertyMetaInfo = metaData.GetMetaDataPropertyInfo(metaProperty);
+ if (propertyMetaInfo->m_IsHidden == false) {
+ PropertyInfo property;
+ property.m_handle = propertyMetaInfo->m_Property;
+ property.m_name = QString::fromWCharArray(
+ propertySystem->GetFormalName(instance,
+ property.m_handle).wide_str());
+ property.m_nameId = QString::fromWCharArray(
+ propertySystem->GetName(property.m_handle).wide_str());
+ property.m_type = propertyMetaInfo->GetDataType();
+ property.m_additionalType = propertyMetaInfo->GetAdditionalType();
+
+ const auto additionalMetaDataType =
+ propertySystem->GetAdditionalMetaDataType(instance, property.m_handle);
+ switch (additionalMetaDataType) {
+ case qt3dsdm::AdditionalMetaDataType::Range: {
+ const qt3dsdm::TMetaDataData &metaDataData =
+ propertySystem->GetAdditionalMetaDataData(instance,
+ property.m_handle);
+ qt3dsdm::SMetaDataRange minMax =
+ qt3dsdm::get<qt3dsdm::SMetaDataRange>(metaDataData);
+ property.m_min = minMax.m_min;
+ property.m_max = minMax.m_max;
+ break;
+ }
+ case qt3dsdm::AdditionalMetaDataType::StringList: {
+ const qt3dsdm::TMetaDataData &metaDataData =
+ propertySystem->GetAdditionalMetaDataData(instance,
+ property.m_handle);
+ auto values = qt3dsdm::get<qt3dsdm::TMetaDataStringList>(metaDataData);
+ QStringList possibleValues;
+ for (const auto &value: values)
+ possibleValues.append(QString::fromWCharArray(value.wide_str()));
+ property.m_possibleValues = possibleValues;
+ break;
+ }
+ case qt3dsdm::AdditionalMetaDataType::Font: {
+ std::vector<QString> fontNames;
+ doc->GetProjectFonts(fontNames);
+ QStringList possibleValues;
+ for (const auto &fontName: fontNames)
+ possibleValues.append(fontName);
+ property.m_possibleValues = possibleValues;
+ break;
+ }
+ default:
+ break;
+ }
+ // Skip Name, we don't want to allow changing that
+ // TODO: To be localized when/if we add support for metadata localization
+ if (property.m_name != QLatin1String("Name"))
+ m_properties.append(property);
+ }
+ }
+ }
+ }
+ endResetModel();
+
+ Q_EMIT valueHandleChanged();
+}
+
+void PropertyModel::setNameHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &handle)
+{
+ m_nameHandle = handle;
+}
+
+void PropertyModel::setValueHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &handle)
+{
+ if (m_valueHandle != handle) {
+ m_valueHandle = handle;
+ updateDefaultPropertyIndex();
+ updateValue();
+ Q_EMIT valueHandleChanged();
+ }
+}
+
+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 (!hasIndex(index.row(), index.column(), index.parent()))
+ return {};
+
+ const auto property = m_properties.at(index.row());
+
+ switch (role)
+ {
+ case NameRole:
+ return property.m_name;
+ case HandleRole:
+ return property.m_handle.GetHandleValue();
+ default:
+ return {};
+ }
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> PropertyModel::roleNames() const
+{
+ auto names = QAbstractItemModel::roleNames();
+ names.insert(NameRole, "name");
+ names.insert(HandleRole, "handle");
+
+ return names;
+}
+
+PropertyInfo PropertyModel::property(int index) const
+{
+ if (index < 0 || index >= m_properties.size())
+ return {};
+ return m_properties[index];
+}
+
+int PropertyModel::valueHandle() const
+{
+ return m_valueHandle;
+}
+
+QVariant PropertyModel::value() const
+{
+ return m_value;
+}
+
+void PropertyModel::updateDefaultPropertyIndex()
+{
+ if (!m_nameHandle.Valid()) {
+ m_defaultPropertyIndex = -1;
+ Q_EMIT defaultPropertyIndexChanged();
+ return;
+ }
+
+ qt3dsdm::SValue sValue;
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto studioSystem = doc->GetStudioSystem();
+ studioSystem->GetActionCore()->GetHandlerArgumentValue(m_nameHandle, sValue);
+
+ if (sValue.getType() != qt3dsdm::DataModelDataType::String) {
+ m_defaultPropertyIndex = -1;
+ Q_EMIT defaultPropertyIndexChanged();
+ return;
+ }
+
+ auto propertyName = qt3dsdm::get<QString>(sValue);
+ auto iter = std::find_if(m_properties.constBegin(), m_properties.constEnd(),
+ [&propertyName](const PropertyInfo &info)
+ {
+ return (info.m_nameId == propertyName);
+ });
+
+ auto index = std::distance(m_properties.constBegin(), iter);
+
+ if (m_defaultPropertyIndex != index) {
+ m_defaultPropertyIndex = index;
+ Q_EMIT defaultPropertyIndexChanged();
+ }
+}
+
+int PropertyModel::defaultPropertyIndex() const
+{
+ return m_defaultPropertyIndex;
+}
+
+void PropertyModel::updateValue()
+{
+ const auto oldValue = m_value;
+ if (!m_valueHandle.Valid()) {
+ m_value.clear();
+ } else {
+ qt3dsdm::SValue sValue;
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto studioSystem = doc->GetStudioSystem();
+ studioSystem->GetActionCore()->GetHandlerArgumentValue(m_valueHandle, sValue);
+ m_value = sValue.toQVariant();
+ }
+ if (oldValue != m_value)
+ Q_EMIT valueChanged();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h
new file mode 100644
index 00000000..a833752b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PROPERTYMODEL_H
+#define PROPERTYMODEL_H
+
+#include <QAbstractListModel>
+
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMDataTypes.h"
+#include "Qt3DSDMMetaDataTypes.h"
+
+struct PropertyInfo {
+ Q_PROPERTY(QString name MEMBER m_name CONSTANT FINAL)
+ Q_PROPERTY(float min MEMBER m_min CONSTANT FINAL)
+ Q_PROPERTY(float max MEMBER m_max CONSTANT FINAL)
+ Q_PROPERTY(qt3dsdm::DataModelDataType::Value type MEMBER m_type CONSTANT FINAL)
+ Q_PROPERTY(qt3dsdm::AdditionalMetaDataType::Value additionalType MEMBER m_additionalType CONSTANT FINAL)
+ Q_PROPERTY(QStringList possibleValues MEMBER m_possibleValues CONSTANT FINAL)
+
+ qt3dsdm::Qt3DSDMPropertyHandle m_handle;
+ QString m_name;
+ QString m_nameId;
+ qt3dsdm::DataModelDataType::Value m_type;
+ qt3dsdm::AdditionalMetaDataType::Value m_additionalType;
+ QStringList m_possibleValues;
+ float m_min = 0.0f;
+ float m_max = 0.0f;
+
+ Q_GADGET
+};
+
+class PropertyModel : public QAbstractListModel
+{
+ Q_PROPERTY(int valueHandle READ valueHandle NOTIFY valueHandleChanged FINAL)
+ Q_PROPERTY(QVariant value READ value NOTIFY valueChanged FINAL)
+ Q_PROPERTY(int defaultPropertyIndex READ defaultPropertyIndex NOTIFY defaultPropertyIndexChanged FINAL)
+ Q_OBJECT
+
+public:
+ explicit PropertyModel(QObject *parent = nullptr);
+
+ enum Roles {
+ NameRole = Qt::DisplayRole,
+ HandleRole = Qt::UserRole + 1
+ };
+
+ void setAction(const qt3dsdm::Qt3DSDMActionHandle &action);
+ void setNameHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &valueHandle);
+ void setValueHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &valueHandle);
+
+ 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;
+
+ PropertyInfo property(int index) const;
+ qt3dsdm::Qt3DSDMActionHandle action() const { return m_action; }
+ int valueHandle() const;
+
+ QVariant value() const;
+ int defaultPropertyIndex() const;
+
+Q_SIGNALS:
+ void valueHandleChanged();
+ void valueChanged();
+ void defaultPropertyIndexChanged();
+
+private:
+ void updateValue();
+ void updateDefaultPropertyIndex();
+
+ QVector<PropertyInfo> m_properties;
+ qt3dsdm::Qt3DSDMActionHandle m_action;
+ qt3dsdm::Qt3DSDMHandlerArgHandle m_nameHandle;
+ qt3dsdm::Qt3DSDMHandlerArgHandle m_valueHandle;
+ int m_defaultPropertyIndex = -1;
+ QVariant m_value;
+};
+
+Q_DECLARE_METATYPE(PropertyInfo)
+
+#endif // PROPERTYMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp
new file mode 100644
index 00000000..8694fda7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "BasicObjectsModel.h"
+#include "DropSource.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+
+#include <QCoreApplication>
+#include <QDataStream>
+#include <QMimeData>
+
+BasicObjectsModel::BasicObjectsModel(QObject *parent) : QAbstractListModel(parent)
+{
+ initialize();
+}
+
+void BasicObjectsModel::initialize()
+{
+ m_ObjectItems = InitializeObjectModel();
+}
+
+const QVector<BasicObjectItem> BasicObjectsModel::InitializeObjectModel()
+{
+ return {
+ {tr("Rectangle"), "Asset-Rectangle-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_RECT},
+ {tr("Sphere"), "Asset-Sphere-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_SPHERE},
+ {tr("Cube"), "Asset-Cube-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_BOX},
+ {tr("Cylinder"), "Asset-Cylinder-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_CYLINDER},
+ {tr("Cone"), "Asset-Cone-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_CONE},
+ {tr("Component"), "Asset-Component-Normal.png"_L1, OBJTYPE_COMPONENT, PRIMITIVETYPE_UNKNOWN},
+ {tr("Group"), "Asset-Group-Normal.png"_L1, OBJTYPE_GROUP, PRIMITIVETYPE_UNKNOWN},
+ {tr("Text"), "Asset-Text-Normal.png"_L1, OBJTYPE_TEXT, PRIMITIVETYPE_UNKNOWN},
+ {tr("Camera"), "Asset-Camera-Normal.png"_L1, OBJTYPE_CAMERA, PRIMITIVETYPE_UNKNOWN},
+ {tr("Light"), "Asset-Light-Normal.png"_L1, OBJTYPE_LIGHT, PRIMITIVETYPE_UNKNOWN},
+ // Hide alias until it will be replaced with prefabs
+ //{tr("Alias"), "Asset-Alias-Normal.png"_L1, OBJTYPE_ALIAS, PRIMITIVETYPE_UNKNOWN},
+ };
+}
+
+// Returns meshes part of basic objects
+const QVector<BasicObjectItem> BasicObjectsModel::BasicMeshesModel()
+{
+ return InitializeObjectModel().mid(0, 5);
+}
+
+QVariant BasicObjectsModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(),index.parent()))
+ return {};
+
+ const auto row = index.row();
+
+ switch (role) {
+ case NameRole: return m_ObjectItems.at(row).name();
+ case IconRole: return StudioUtils::resourceImageUrl() +
+ m_ObjectItems.at(row).icon();
+ case ObjectTypeRole: return m_ObjectItems.at(row).objectType();
+ case PrimitiveTypeRole: return m_ObjectItems.at(row).primitiveType();
+ }
+
+ return {};
+}
+
+int BasicObjectsModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return m_ObjectItems.count();
+}
+
+QHash<int, QByteArray> BasicObjectsModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(NameRole, "name");
+ names.insert(IconRole, "icon");
+
+ return names;
+}
+
+Qt::ItemFlags BasicObjectsModel::flags(const QModelIndex &index) const {
+ if (index.isValid())
+ return Qt::ItemIsDragEnabled;
+
+ return QAbstractListModel::flags(index);
+}
+
+QStringList BasicObjectsModel::mimeTypes() const
+{
+ return { m_MimeType };
+}
+
+QMimeData *BasicObjectsModel::mimeData(const QModelIndexList &indexes) const
+{
+
+ const auto row = indexes.first().row(); // we support only one item for D&D
+ auto object = m_ObjectItems.at(row);
+
+ auto *data = CDropSourceFactory::Create(object.GetFlavor(), &object);
+ return data;
+}
+
+BasicObjectItem::~BasicObjectItem()
+{
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h
new file mode 100644
index 00000000..2e2faa83
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BASICOBJECTSMODEL_H
+#define BASICOBJECTSMODEL_H
+
+#include <QAbstractListModel>
+
+#include "IDragable.h"
+#include "StudioObjectTypes.h"
+
+class BasicObjectItem : public IDragable {
+
+public:
+ BasicObjectItem() {}
+ BasicObjectItem(const QString &name, const QString &icon,
+ EStudioObjectType objectType, EPrimitiveType primitiveType)
+ : m_name(name), m_icon(icon)
+ , m_objectType(objectType), m_primitiveType(primitiveType)
+ { }
+
+ virtual ~BasicObjectItem();
+
+ QString name() const { return m_name; }
+ QString icon() const { return m_icon; }
+
+ EStudioObjectType objectType() const { return m_objectType; }
+ EPrimitiveType primitiveType() const { return m_primitiveType; }
+
+ void setName(const QString &name) { m_name = name; }
+ void setIcon(const QString &icon) { m_icon = icon; }
+ void setObjectType(EStudioObjectType type) { m_objectType = type; }
+ void setPrimitveType(EPrimitiveType type) { m_primitiveType = type; }
+
+ long GetFlavor() const override {return QT3DS_FLAVOR_BASIC_OBJECTS;}
+
+private:
+ QString m_name;
+ QString m_icon;
+ EStudioObjectType m_objectType;
+ EPrimitiveType m_primitiveType;
+};
+
+class BasicObjectsModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ BasicObjectsModel(QObject *parent = nullptr);
+
+ enum Roles {
+ NameRole = Qt::DisplayRole,
+ IconRole = Qt::DecorationRole,
+ ObjectTypeRole = Qt::UserRole + 1,
+ PrimitiveTypeRole
+ };
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ QStringList mimeTypes() const override;
+ QMimeData *mimeData(const QModelIndexList &indexes) const override;
+
+ static const QVector<BasicObjectItem> BasicMeshesModel();
+
+private:
+ void initialize();
+ static const QVector<BasicObjectItem> InitializeObjectModel();
+
+ QVector<BasicObjectItem> m_ObjectItems;
+
+ const QString m_MimeType = QLatin1String("application/x-basic-object");
+};
+
+#endif // BASICOBJECTSMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp
new file mode 100644
index 00000000..e23423fe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "BasicObjectsView.h"
+#include "BasicObjectsModel.h"
+#include "CColor.h"
+#include "Literals.h"
+#include "StudioPreferences.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qdrag.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlfile.h>
+#include <QtQuick/qquickitem.h>
+
+BasicObjectsView::BasicObjectsView(QWidget *parent) : QQuickWidget(parent)
+ , m_ObjectsModel(new BasicObjectsModel(this))
+
+{
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &BasicObjectsView::initialize);
+}
+
+QSize BasicObjectsView::sizeHint() const
+{
+ return {150, 500};
+}
+
+void BasicObjectsView::startDrag(QQuickItem *item, int row)
+{
+ item->grabMouse(); // Grab to make sure we can ungrab after the drag
+ const auto index = m_ObjectsModel->index(row);
+
+ QDrag drag(this);
+ drag.setMimeData(m_ObjectsModel->mimeData({index}));
+ drag.setPixmap(QPixmap(QQmlFile::urlToLocalFileOrQrc(
+ index.data(BasicObjectsModel::IconRole).toUrl())));
+ drag.exec(Qt::CopyAction);
+ QTimer::singleShot(0, item, &QQuickItem::ungrabMouse);
+}
+
+void BasicObjectsView::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+ QQuickWidget::mousePressEvent(event);
+}
+
+void BasicObjectsView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_basicObjectsModel"), m_ObjectsModel);
+ rootContext()->setContextProperty(QStringLiteral("_basicObjectsView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/BasicObjects/BasicObjectsView.qml")));
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h
new file mode 100644
index 00000000..19e8ddc3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BASICOBJECTSVIEW_H
+#define BASICOBJECTSVIEW_H
+
+#include <QQuickWidget>
+
+class BasicObjectsModel;
+QT_FORWARD_DECLARE_CLASS(QQuickItem)
+
+class BasicObjectsView : public QQuickWidget
+{
+ Q_OBJECT
+public:
+ explicit BasicObjectsView(QWidget *parent = nullptr);
+
+ QSize sizeHint() const override;
+
+ Q_INVOKABLE void startDrag(QQuickItem *item, int row);
+
+protected:
+ void mousePressEvent(QMouseEvent *event) override;
+
+private:
+ void initialize();
+
+ BasicObjectsModel *m_ObjectsModel = nullptr;
+ QColor m_BaseColor = QColor::fromRgb(75, 75, 75);
+};
+
+#endif // BASICOBJECTSVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml
new file mode 100644
index 00000000..5f469a5a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import "../controls"
+
+Rectangle {
+
+ color: _backgroundColor
+
+ ListView {
+ anchors {
+ fill: parent
+ leftMargin: 8
+ }
+ boundsBehavior: Flickable.StopAtBounds
+
+ model: _basicObjectsModel
+ spacing: 2
+
+ ScrollBar.vertical: ScrollBar {}
+
+ delegate: Item {
+ height: contentRow.height
+ width: contentRow.width
+ Item {
+ id: dragItem
+ anchors.fill: parent
+
+ MouseArea {
+ id: dragArea
+ property bool dragStarted: false
+ property point pressPoint
+
+ anchors.fill: parent
+ onPressed: {
+ pressPoint = Qt.point(mouse.x, mouse.y);
+ dragStarted = false;
+ }
+ onPositionChanged: {
+ if (!dragStarted && (Math.abs(mouse.x - pressPoint.x) > 4
+ || Math.abs(mouse.y - pressPoint.y) > 4)) {
+ dragStarted = true;
+ _basicObjectsView.startDrag(dragItem, index);
+ }
+ }
+ }
+ }
+ Row {
+ id: contentRow
+ spacing: 4
+ Image {
+ id: assetIcon
+ width: 24
+ height: 24
+ fillMode: Image.Pad
+ source: model.icon
+ }
+ StyledLabel {
+ y: (assetIcon.height - height) / 2
+ text: model.name
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml
new file mode 100644
index 00000000..71462f48
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: item
+
+ signal clicked(string filePath)
+ signal doubleClicked(string filePath)
+
+ width: parent.width
+ height: 20
+ color: isCurrentFile ? _selectionColor : "transparent"
+
+ Row {
+ x: depth * 28 - (item.width <= _valueWidth ? 14 : 0)
+ anchors.verticalCenter: item.verticalCenter
+
+ Image {
+ source: _resDir + (expanded ? "arrow_down.png" : "arrow.png")
+ opacity: isExpandable ? 1 : 0
+
+ MouseArea {
+ visible: listView.model && isExpandable
+ anchors.fill: parent
+ onClicked: {
+ if (expanded)
+ listView.model.collapse(index)
+ else
+ listView.model.expand(index)
+ }
+ }
+ }
+
+ Image {
+ source: listView.model ? fileIcon : ""
+ width: 16
+ height: 16
+ }
+
+ StyledLabel {
+ text: listView.model ? fileName : ""
+ color: _textColor
+ leftPadding: 5
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: {
+ if (isSelectable) {
+ listView.model.setCurrentFile(filePath);
+ item.clicked(filePath);
+ }
+ }
+ onDoubleClicked: {
+ if (isSelectable) {
+ listView.model.setCurrentFile(filePath);
+ item.doubleClicked(filePath);
+ } else if (isExpandable) {
+ if (expanded)
+ listView.model.collapse(index);
+ else
+ listView.model.expand(index);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp
new file mode 100644
index 00000000..3a1a008b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp
@@ -0,0 +1,630 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QtCore/qset.h>
+
+#include "Qt3DSCommonPrecompile.h"
+
+#include "ChooserModelBase.h"
+#include "Core.h"
+#include "Dispatch.h"
+#include "Doc.h"
+#include "StudioUtils.h"
+#include "Qt3DSFileTools.h"
+#include "ImportUtils.h"
+#include "StudioApp.h"
+
+ChooserModelBase::ChooserModelBase(QObject *parent) : QAbstractListModel(parent)
+ , m_model(new QFileSystemModel(this))
+{
+ connect(m_model, &QAbstractItemModel::rowsInserted, this, &ChooserModelBase::modelRowsInserted);
+ connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ChooserModelBase::modelRowsRemoved);
+ connect(m_model, &QAbstractItemModel::layoutChanged, this, &ChooserModelBase::modelLayoutChanged);
+
+ g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this);
+
+ rebuild();
+}
+
+ChooserModelBase::~ChooserModelBase()
+{
+ g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this);
+}
+
+QHash<int, QByteArray> ChooserModelBase::roleNames() const
+{
+ auto modelRoleNames = m_model->roleNames();
+ modelRoleNames.insert(IsExpandableRole, "isExpandable");
+ modelRoleNames.insert(DepthRole, "depth");
+ modelRoleNames.insert(ExpandedRole, "expanded");
+ modelRoleNames.insert(IsSelectableRole, "isSelectable");
+ modelRoleNames.insert(IsCurrentFile, "isCurrentFile");
+ return modelRoleNames;
+}
+
+int ChooserModelBase::rowCount(const QModelIndex &) const
+{
+ return getFixedItems().count() + m_items.count();
+}
+
+QVariant ChooserModelBase::data(const QModelIndex &index, int role) const
+{
+ const int row = index.row();
+
+ const auto fixedItems = getFixedItems();
+ const int fixedItemCount = fixedItems.count();
+
+ if (row < fixedItemCount) {
+ const auto &item = fixedItems.at(row);
+
+ switch (role) {
+ case Qt::DecorationRole:
+ if (!item.iconSource.isEmpty()) {
+ return StudioUtils::resourceImageUrl() + item.iconSource;
+ } else {
+ return StudioUtils::resourceImageUrl()
+ + CStudioObjectTypes::GetNormalIconName(item.iconType);
+ }
+
+ case IsExpandableRole:
+ return false;
+
+ case DepthRole:
+ return 0;
+
+ case ExpandedRole:
+ return false;
+
+ case IsSelectableRole:
+ return true;
+
+ case IsCurrentFile:
+ return item.name == m_currentFile;
+
+ default:
+ return item.name;
+ }
+ } else {
+ const auto &item = m_items.at(row - fixedItemCount);
+
+ switch (role) {
+ case Qt::DecorationRole: {
+ QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return StudioUtils::resourceImageUrl() + getIconName(path);
+ }
+
+ case IsExpandableRole: {
+ QFileInfo fileInfo(item.index.data(QFileSystemModel::FilePathRole).toString());
+ return fileInfo.isDir();
+ }
+
+ case DepthRole:
+ return item.depth;
+
+ case ExpandedRole:
+ return item.expanded;
+
+ case IsSelectableRole: {
+ QFileInfo fileInfo(item.index.data(QFileSystemModel::FilePathRole).toString());
+ return fileInfo.isFile();
+ }
+
+ case IsCurrentFile: {
+ QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return path == m_currentFile;
+ }
+
+ case QFileSystemModel::FileNameRole: {
+ QString displayName = specialDisplayName(item);
+ if (displayName.isEmpty())
+ displayName = m_model->data(item.index, QFileSystemModel::FileNameRole).toString();
+ return displayName;
+ }
+
+ default:
+ return m_model->data(item.index, role).toString();
+ }
+ }
+}
+
+void ChooserModelBase::setCurrentFile(const QString &path)
+{
+ const auto fixedItems = getFixedItems();
+ const int fixedItemCount = fixedItems.count();
+ const auto getFixedItemIndex = [fixedItemCount, &fixedItems](const QString &path) -> int {
+ for (int i = 0; i < fixedItemCount; ++i) {
+ const auto &item = fixedItems.at(i);
+ if (item.name == path)
+ return i;
+ }
+ return -1;
+ };
+ int fixedItemIndex = getFixedItemIndex(path);
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const QDir documentDir(doc->GetDocumentDirectory());
+ const QString fullPath = fixedItemIndex == -1 ? QDir::cleanPath(documentDir.filePath(path))
+ : path;
+
+ if (fullPath != m_currentFile) {
+ const auto fileRow = [this, getFixedItemIndex, fixedItemCount](const QString &path) -> int
+ {
+ const int fixedItemIndex = getFixedItemIndex(path);
+ if (fixedItemIndex != -1)
+ return fixedItemIndex;
+
+ const int itemCount = m_items.count();
+
+ for (int i = 0; i < itemCount; ++i) {
+ const auto &item = m_items.at(i);
+
+ if (item.index.data(QFileSystemModel::FilePathRole).toString() == path)
+ return fixedItemCount + i;
+ }
+
+ return -1;
+ };
+
+ int previousRow = fileRow(m_currentFile);
+ int currentRow = fileRow(fullPath);
+
+ m_currentFile = fullPath;
+
+ if (previousRow != -1)
+ Q_EMIT dataChanged(index(previousRow), index(previousRow));
+
+ if (currentRow != -1)
+ Q_EMIT dataChanged(index(currentRow), index(currentRow));
+ }
+
+ // expand parent folder if current file is hidden
+ auto matched = m_model->match(m_rootIndex, QFileSystemModel::FilePathRole, path, 1,
+ Qt::MatchExactly|Qt::MatchRecursive);
+ if (!matched.isEmpty()) {
+ auto modelIndex = matched.first();
+ if (modelIndexRow(modelIndex) == -1)
+ expand(m_model->parent(modelIndex));
+ }
+}
+
+void ChooserModelBase::expand(const QModelIndex &modelIndex)
+{
+ if (modelIndex == m_rootIndex)
+ return;
+
+ int row = modelIndexRow(modelIndex);
+ if (row == -1) {
+ QModelIndex parentIndex = m_model->parent(modelIndex);
+ expand(parentIndex);
+
+ row = modelIndexRow(modelIndex);
+ Q_ASSERT(row != -1);
+ }
+
+ if (!m_items.at(row).expanded)
+ expand(row + getFixedItems().count());
+}
+
+void ChooserModelBase::setRootPath(const QString &path)
+{
+ // Delete the old model. If the new project is in a totally different directory tree, not
+ // doing this will result in unexplicable crashes when trying to parse something that should
+ // not be parsed.
+ disconnect(m_model, &QAbstractItemModel::rowsInserted,
+ this, &ChooserModelBase::modelRowsInserted);
+ disconnect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, &ChooserModelBase::modelRowsRemoved);
+ disconnect(m_model, &QAbstractItemModel::layoutChanged,
+ this, &ChooserModelBase::modelLayoutChanged);
+ delete m_model;
+ m_model = new QFileSystemModel(this);
+ connect(m_model, &QAbstractItemModel::rowsInserted,
+ this, &ChooserModelBase::modelRowsInserted);
+ connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, &ChooserModelBase::modelRowsRemoved);
+ connect(m_model, &QAbstractItemModel::layoutChanged,
+ this, &ChooserModelBase::modelLayoutChanged);
+
+ setRootIndex(m_model->setRootPath(path));
+}
+
+void ChooserModelBase::setRootIndex(const QModelIndex &rootIndex)
+{
+ if (rootIndex != m_rootIndex) {
+ clearModelData();
+ m_rootIndex = rootIndex;
+ showModelTopLevelItems();
+ }
+}
+
+void ChooserModelBase::clearModelData()
+{
+ if (!m_items.isEmpty()) {
+ const auto fixedItemCount = getFixedItems().count();
+ beginRemoveRows({}, fixedItemCount, fixedItemCount + m_items.count() - 1);
+ m_items.clear();
+ endRemoveRows();
+ }
+}
+
+void ChooserModelBase::showModelTopLevelItems()
+{
+ int rowCount = m_model->rowCount(m_rootIndex);
+
+ if (rowCount == 0) {
+ if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex))
+ m_model->fetchMore(m_rootIndex);
+ } else {
+ showModelChildItems(m_rootIndex, 0, rowCount - 1);
+
+ for (int i = 0; i < rowCount; ++i) {
+ const auto &childIndex = m_model->index(i, 0, m_rootIndex);
+ if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex))
+ m_model->fetchMore(childIndex);
+ }
+ }
+}
+
+void ChooserModelBase::showModelChildItems(const QModelIndex &parentIndex, int start, int end)
+{
+ QVector<QModelIndex> rowsToInsert;
+ for (int i = start; i <= end; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (isVisible(childIndex))
+ rowsToInsert.append(childIndex);
+ }
+
+ const int insertCount = rowsToInsert.count();
+
+ if (insertCount != 0) {
+ TreeItem *parent;
+ int depth, startRow;
+
+ if (parentIndex == m_rootIndex) {
+ parent = nullptr;
+ depth = 0;
+ startRow = 0;
+ } else {
+ const int parentRow = modelIndexRow(parentIndex);
+ Q_ASSERT(parentRow != -1 && isVisible(parentIndex));
+ parent = &m_items[parentRow];
+ depth = parent->depth + 1;
+ startRow = parentRow + parent->childCount + 1;
+ }
+
+ const int fixedItemCount = getFixedItems().count();
+ beginInsertRows({}, startRow + fixedItemCount, startRow + fixedItemCount + insertCount - 1);
+
+ for (auto it = rowsToInsert.rbegin(); it != rowsToInsert.rend(); ++it)
+ m_items.insert(startRow, { *it, depth, false, parent, 0 });
+
+ for (; parent != nullptr; parent = parent->parent)
+ parent->childCount += insertCount;
+
+ endInsertRows();
+ }
+}
+
+void ChooserModelBase::expand(int row)
+{
+ const int fixedItemCount = getFixedItems().count();
+ Q_ASSERT(row >= fixedItemCount && row < fixedItemCount + m_items.count());
+
+ auto &item = m_items[row - fixedItemCount];
+ Q_ASSERT(item.expanded == false);
+
+ const auto &modelIndex = item.index;
+
+ const int rowCount = m_model->rowCount(modelIndex);
+ if (rowCount == 0) {
+ if (m_model->hasChildren(modelIndex) && m_model->canFetchMore(modelIndex))
+ m_model->fetchMore(modelIndex);
+ } else {
+ showModelChildItems(modelIndex, 0, rowCount - 1);
+ }
+
+ item.expanded = true;
+ Q_EMIT dataChanged(index(row), index(row));
+}
+
+void ChooserModelBase::collapse(int row)
+{
+ const int fixedItemCount = getFixedItems().count();
+ Q_ASSERT(row >= fixedItemCount && row < fixedItemCount + m_items.count());
+
+ auto &item = m_items[row - fixedItemCount];
+ Q_ASSERT(item.expanded == true);
+
+ const int childCount = item.childCount;
+
+ if (childCount > 0) {
+ beginRemoveRows({}, row + 1, row + childCount);
+
+ auto first = std::begin(m_items) + row - fixedItemCount + 1;
+ m_items.erase(first, first + childCount);
+
+ for (auto parent = &item; parent != nullptr; parent = parent->parent)
+ parent->childCount -= childCount;
+
+ endRemoveRows();
+ }
+
+ item.expanded = false;
+ Q_EMIT dataChanged(index(row), index(row));
+}
+
+void ChooserModelBase::OnNewPresentation()
+{
+ rebuild();
+}
+
+int ChooserModelBase::modelIndexRow(const QModelIndex &modelIndex) const
+{
+ auto it = std::find_if(std::begin(m_items), std::end(m_items),
+ [&modelIndex](const TreeItem &item)
+ {
+ return item.index == modelIndex;
+ });
+
+ return it != std::end(m_items) ? std::distance(std::begin(m_items), it) : -1;
+}
+
+bool ChooserModelBase::isExpanded(const QModelIndex &modelIndex) const
+{
+ if (modelIndex == m_rootIndex) {
+ return true;
+ } else {
+ const int row = modelIndexRow(modelIndex);
+ return row != -1 && m_items.at(row).expanded;
+ }
+}
+
+EStudioObjectType ChooserModelBase::getIconType(const QString &path) const
+{
+ return Q3DStudio::ImportUtils::GetObjectFileTypeForFile(path).m_IconType;
+}
+
+QString ChooserModelBase::specialDisplayName(const ChooserModelBase::TreeItem &item) const
+{
+ Q_UNUSED(item)
+ return {};
+}
+
+QString ChooserModelBase::getIconName(const QString &path) const
+{
+ QString iconName;
+
+ QFileInfo fileInfo(path);
+ if (fileInfo.isFile()) {
+ EStudioObjectType type = getIconType(path);
+ if (type != OBJTYPE_UNKNOWN)
+ iconName = CStudioObjectTypes::GetNormalIconName(type);
+ else
+ iconName = QStringLiteral("Objects-Layer-Normal.png");
+ } else {
+ iconName = QStringLiteral("Objects-Folder-Normal.png");
+ }
+
+ return iconName;
+}
+
+bool ChooserModelBase::isVisible(const QModelIndex &modelIndex) const
+{
+ QString path = modelIndex.data(QFileSystemModel::FilePathRole).toString();
+ QFileInfo fileInfo(path);
+
+ if (fileInfo.isFile()) {
+ return isVisible(path);
+ } else {
+ return hasVisibleChildren(modelIndex);
+ }
+}
+
+bool ChooserModelBase::hasVisibleChildren(const QModelIndex &modelIndex) const
+{
+ int rowCount = m_model->rowCount(modelIndex);
+
+ for (int i = 0; i < rowCount; ++i) {
+ const auto &childIndex = m_model->index(i, 0, modelIndex);
+
+ if (m_model->hasChildren(childIndex)) {
+ if (hasVisibleChildren(childIndex))
+ return true;
+ } else {
+ QString path = childIndex.data(QFileSystemModel::FilePathRole).toString();
+ QFileInfo fileInfo(path);
+ if (fileInfo.isFile() && isVisible(path))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ChooserModelBase::modelRowsInserted(const QModelIndex &parentIndex, int start, int end)
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ if (isExpanded(parentIndex)) {
+ showModelChildItems(parentIndex, start, end);
+ } else {
+ if (modelIndexRow(parentIndex) == -1) {
+ // parent wasn't inserted in model yet, check if any of the new rows is visible
+
+ bool visible = false;
+
+ for (int i = start; i <= end; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ QString path = childIndex.data(QFileSystemModel::FilePathRole).toString();
+ QFileInfo fileInfo(path);
+ if (fileInfo.isFile() && isVisible(path)) {
+ visible = true;
+ break;
+ }
+ }
+
+ // if any of the new rows is visible, insert parent folder index into model
+
+ if (visible) {
+ QModelIndex index = parentIndex, parent = m_model->parent(parentIndex);
+
+ while (parent != m_rootIndex && modelIndexRow(parent) == -1) {
+ index = parent;
+ parent = m_model->parent(parent);
+ }
+
+ if (isExpanded(parent) && modelIndexRow(index) == -1) {
+ const int row = index.row();
+ showModelChildItems(parent, row, row);
+ }
+ }
+ }
+
+ // if one of the new rows is the current file expand parent folder
+
+ bool containsCurrent = false;
+
+ for (int i = start; i <= end; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (childIndex.data(QFileSystemModel::FilePathRole).toString() == m_currentFile) {
+ containsCurrent = true;
+ break;
+ }
+ }
+
+ if (containsCurrent)
+ expand(parentIndex);
+ }
+
+ // fetch children so we're notified when files are added or removed
+
+ for (int i = start; i <= end; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex))
+ m_model->fetchMore(childIndex);
+ }
+}
+
+void ChooserModelBase::modelRowsRemoved(const QModelIndex &parentIndex, int start, int end)
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ if (isExpanded(parentIndex)) {
+ const auto fixedItems = getFixedItems();
+
+ const auto removeRow = [this, &fixedItems](int row)
+ {
+ const auto &item = m_items.at(row);
+
+ const int fixedItemCount = fixedItems.count();
+ beginRemoveRows({}, row + fixedItemCount, row + fixedItemCount + item.childCount);
+
+ for (auto parent = item.parent; parent != nullptr; parent = parent->parent)
+ parent->childCount -= 1 + item.childCount;
+
+ m_items.erase(std::begin(m_items) + row, std::begin(m_items) + row + item.childCount + 1);
+
+ endRemoveRows();
+ };
+
+ // remove rows
+
+ for (int i = start; i <= end; ++i) {
+ const int row = modelIndexRow(m_model->index(i, 0, parentIndex));
+ if (row != -1)
+ removeRow(row);
+ }
+
+ // also remove folder row if there are no more visible children
+
+ QModelIndex index = parentIndex;
+
+ while (index != m_rootIndex && !hasVisibleChildren(index)) {
+ const int row = modelIndexRow(index);
+ Q_ASSERT(row != -1);
+ removeRow(row);
+ index = m_model->parent(index);
+ }
+ }
+}
+
+void ChooserModelBase::modelLayoutChanged()
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ QSet<QPersistentModelIndex> expandedItems;
+ for (const auto &item : m_items) {
+ if (item.expanded)
+ expandedItems.insert(item.index);
+ }
+
+ const std::function<int(const QModelIndex &, TreeItem *)> insertChildren =
+ [this, &expandedItems, &insertChildren](const QModelIndex &parentIndex, TreeItem *parent)
+ {
+ Q_ASSERT(parentIndex == m_rootIndex || isVisible(parentIndex));
+
+ const int rowCount = m_model->rowCount(parentIndex);
+ const int depth = parent == nullptr ? 0 : parent->depth + 1;
+
+ int childCount = 0;
+
+ for (int i = 0; i < rowCount; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (isVisible(childIndex)) {
+ const bool expanded = expandedItems.contains(childIndex);
+ m_items.append({ childIndex, depth, expanded, parent, 0 });
+ auto &item = m_items.last();
+ if (expanded) {
+ item.childCount = insertChildren(childIndex, &item);
+ childCount += item.childCount;
+ }
+ ++childCount;
+ }
+ }
+
+ return childCount;
+ };
+
+ const int itemCount = m_items.count();
+
+ m_items.clear();
+ m_items.reserve(itemCount);
+
+ insertChildren(m_rootIndex, nullptr);
+ Q_ASSERT(m_items.count() == itemCount);
+
+ const int fixedItemCount = getFixedItems().count();
+ Q_EMIT dataChanged(index(fixedItemCount), index(fixedItemCount + itemCount - 1));
+}
+
+void ChooserModelBase::rebuild()
+{
+ setRootPath(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h
new file mode 100644
index 00000000..01553ca4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef CHOOSERMODELBASE_H
+#define CHOOSERMODELBASE_H
+
+#include "DispatchListeners.h"
+#include "StudioObjectTypes.h"
+
+#include <QFileSystemModel>
+#include <QAbstractListModel>
+#include <QList>
+#include <QVector>
+
+QT_FORWARD_DECLARE_CLASS(QFileSystemModel)
+
+class ChooserModelBase : public QAbstractListModel, public CPresentationChangeListener
+{
+ Q_OBJECT
+
+public:
+ explicit ChooserModelBase(QObject *parent = nullptr);
+ ~ChooserModelBase();
+
+ enum {
+ IsExpandableRole = QFileSystemModel::FilePermissions + 1,
+ DepthRole,
+ ExpandedRole,
+ IsSelectableRole,
+ IsCurrentFile
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = {}) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ Q_INVOKABLE void expand(int row);
+ Q_INVOKABLE void collapse(int row);
+
+ Q_INVOKABLE void setCurrentFile(const QString &path);
+ static QString noneString() { return tr("[None]"); }
+
+ // CPresentationChangeListener
+ void OnNewPresentation() override;
+
+Q_SIGNALS:
+ void modelChanged(QAbstractItemModel *model);
+
+protected:
+ EStudioObjectType getIconType(const QString &path) const;
+
+ virtual bool isVisible(const QString &path) const = 0;
+
+ struct FixedItem
+ {
+ EStudioObjectType iconType;
+ QString iconSource;
+ QString name;
+ };
+
+ struct TreeItem {
+ QPersistentModelIndex index;
+ int depth;
+ bool expanded;
+ TreeItem *parent;
+ int childCount;
+ };
+
+ virtual const QVector<FixedItem> getFixedItems() const = 0;
+ virtual QString specialDisplayName(const TreeItem &item) const;
+
+private:
+ void setRootPath(const QString &path);
+ void setRootIndex(const QModelIndex &rootIndex);
+ void clearModelData();
+ void showModelTopLevelItems();
+ void showModelChildItems(const QModelIndex &parentItem, int start, int end);
+ int modelIndexRow(const QModelIndex &modelIndex) const;
+ bool isExpanded(const QModelIndex &modelIndex) const;
+ QString getIconName(const QString &path) const;
+ bool isVisible(const QModelIndex &modelIndex) const;
+ bool hasVisibleChildren(const QModelIndex &modelIndex) const;
+ void expand(const QModelIndex &modelIndex);
+
+ void modelRowsInserted(const QModelIndex &parent, int start, int end);
+ void modelRowsRemoved(const QModelIndex &parent, int start, int end);
+ void modelRowsMoved(const QModelIndex &parent, int start, int end);
+ void modelLayoutChanged();
+
+ void rebuild();
+
+ QFileSystemModel *m_model;
+ QPersistentModelIndex m_rootIndex;
+ QList<TreeItem> m_items;
+ QString m_currentFile;
+};
+
+#endif // CHOOSERMODELBASE_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml
new file mode 100644
index 00000000..3681a102
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml
@@ -0,0 +1,212 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+
+ border.color: _studioColor3
+
+ StyledLabel {
+ id: title
+ color: _dataInputColor
+ text: qsTr("Select Controlling Data Input")
+ leftPadding: 8
+ height: 20
+ }
+
+ StyledMenuSeparator {
+ id: separator
+ anchors.top: title.bottom
+ leftPadding: 8
+ rightPadding: 8
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.topMargin: 30
+ spacing: 10
+ RowLayout {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ StyledComboBox {
+ id: filterCombo
+ readonly property int numOfFixedChoices: 2
+ Layout.leftMargin: 8
+ Layout.preferredWidth: 150
+
+ // Data type list must match with EDataType enum so we can use enum
+ // index directly without going through string -> int table lookup
+ model: [qsTr("[Compatible types]"), qsTr("[All types]"), qsTr("Boolean"),
+ qsTr("Float"), qsTr("Ranged Number"), qsTr("String"), qsTr("Variant"),
+ qsTr("Vector2"), qsTr("Vector3")]
+
+ onCurrentIndexChanged: _parentView.setTypeFilter(currentIndex - numOfFixedChoices);
+
+ MouseArea {
+ id: filterBoxMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ // pass through mouse click to Combobox
+ onPressed: {
+ mouse.accepted = false;
+ }
+ }
+
+ StyledTooltip {
+ text: qsTr("Filter the list by Data Input type or\n"
+ + "by compatibility with current property")
+ enabled: filterBoxMouseArea.containsMouse && !filterCombo.popup.activeFocus
+ }
+ Connections {
+ target: _parentView
+ // Filter type can be changed also from cpp side
+ onFilterChanged: {
+ filterCombo.currentIndex = _parentView.typeFilter
+ + filterCombo.numOfFixedChoices;
+ }
+ }
+ }
+
+ StyledTextField {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.preferredWidth: 200
+ Layout.fillWidth: true
+ id: searchField
+ placeholderText: qsTr("[search]")
+ horizontalAlignment: TextInput.AlignLeft
+
+ property string value
+
+ rightPadding: clearText.width + 2
+
+ onTextChanged: _parentView.setSearchString(text);
+
+ MouseArea {
+ id: searchMouseArea
+ anchors.fill: parent
+ propagateComposedEvents: true
+ hoverEnabled: true
+ onClicked: {
+ searchField.forceActiveFocus();
+ }
+ }
+
+ StyledTooltip {
+ id: searchTt
+ text: qsTr("Search for Data Input")
+ enabled: searchMouseArea.containsMouse && !searchField.focus
+ }
+
+ Image {
+ anchors { verticalCenter: parent.verticalCenter; right: parent.right; }
+ id: clearText
+ fillMode: Image.PreserveAspectFit
+ smooth: true;
+ source: _resDir + "add.png"
+ rotation: 45
+
+ MouseArea {
+ id: clear
+ anchors {
+ horizontalCenter: parent.horizontalCenter;
+ verticalCenter: parent.verticalCenter
+ }
+ height: clearText.height; width: clearText.height
+ onClicked: {
+ searchField.text = ""
+ searchField.forceActiveFocus()
+ }
+ }
+ }
+ }
+ }
+
+ ListView {
+ id: listView
+ Layout.leftMargin: 8
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ boundsBehavior: Flickable.StopAtBounds
+ spacing: 4
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _dataInputSelectModel
+
+ delegate: Row {
+ height: 20
+ Image {
+ // do not show item icon for fixed items
+ visible: index >= _dataInputSelectModel.fixedItemCount
+ source: index === _parentView.selected
+ ? _dataInputSelectModel.getActiveIconPath()
+ : _dataInputSelectModel.getInactiveIconPath();
+ }
+ StyledLabel {
+ leftPadding: 5
+ text: model.display
+ width: listView.width / 2;
+ color: (index >= _dataInputSelectModel.fixedItemCount)
+ && (index === _parentView.selected)
+ ? _dataInputColor : _textColor;
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: _parentView.setSelection(index)
+ }
+ }
+ StyledLabel {
+ leftPadding: 5
+ visible: index >= _dataInputSelectModel.fixedItemCount
+ text: "(" + model.datatype + ")"
+ color: (index >= _dataInputSelectModel.fixedItemCount)
+ && (index === _parentView.selected)
+ ? _dataInputColor : _textColor;
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: _parentView.setSelection(index)
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml
new file mode 100644
index 00000000..b9ef07ab
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 10
+ ListView {
+ id: listView
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 4
+
+ boundsBehavior: Flickable.StopAtBounds
+ spacing: 4
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _fileChooserModel
+
+ delegate: ChooserDelegate {
+ onClicked: {
+ _fileChooserView.fileSelected(_fileChooserView.handle,
+ _fileChooserView.instance, filePath);
+ }
+ onDoubleClicked: {
+ _fileChooserView.fileSelected(_fileChooserView.handle,
+ _fileChooserView.instance, filePath);
+ _fileChooserView.hide();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp
new file mode 100644
index 00000000..46e3b3a6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "FileChooserModel.h"
+
+FileChooserModel::FileChooserModel(QObject *parent)
+ : ChooserModelBase(parent)
+{
+
+}
+
+FileChooserModel::~FileChooserModel()
+{
+}
+
+bool FileChooserModel::isVisible(const QString &path) const
+{
+ return getIconType(path) == OBJTYPE_GROUP;
+}
+
+const QVector<ChooserModelBase::FixedItem> FileChooserModel::getFixedItems() const
+{
+ static const QVector<FixedItem> items = { { OBJTYPE_GROUP, "", noneString() } };
+ return items;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h
new file mode 100644
index 00000000..c019c4a1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FILECHOOSERMODEL_H
+#define FILECHOOSERMODEL_H
+
+#include "ChooserModelBase.h"
+
+class FileChooserModel : public ChooserModelBase
+{
+ Q_OBJECT
+
+public:
+ explicit FileChooserModel(QObject *parent = nullptr);
+ virtual ~FileChooserModel();
+private:
+ bool isVisible(const QString &path) const override;
+ const QVector<FixedItem> getFixedItems() const override;
+};
+
+#endif // IMAGECHOOSERMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp
new file mode 100644
index 00000000..fc4612c9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "FileChooserView.h"
+#include "FileChooserModel.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMValue.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "StudioPreferences.h"
+
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtCore/qtimer.h>
+
+FileChooserView::FileChooserView(QWidget *parent)
+ : QQuickWidget(parent)
+ , m_model(new FileChooserModel(this))
+{
+ setWindowTitle(tr("Imports"));
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &FileChooserView::initialize);
+}
+
+void FileChooserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"),
+ StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_fileChooserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_fileChooserModel"), m_model);
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/FileChooser.qml")));
+}
+
+void FileChooserView::setHandle(int handle)
+{
+ m_handle = handle;
+}
+
+int FileChooserView::handle() const
+{
+ return m_handle;
+}
+
+void FileChooserView::setInstance(int instance)
+{
+ m_instance = instance;
+}
+
+int FileChooserView::instance() const
+{
+ return m_instance;
+}
+
+void FileChooserView::updateSelection()
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(m_instance, m_handle, value);
+
+ QString valueStr = qt3dsdm::get<QString>(value);
+ if (valueStr.isEmpty())
+ valueStr = ChooserModelBase::noneString();
+
+ m_model->setCurrentFile(valueStr);
+}
+
+void FileChooserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ // Don't lose focus because of progress dialog pops up which happens e.g. when importing mesh
+ // in response to file selection
+ if (g_StudioApp.isOnProgress()) {
+ if (!m_focusOutTimer) {
+ m_focusOutTimer = new QTimer(this);
+ connect(m_focusOutTimer, &QTimer::timeout, [this]() {
+ // Periodically check if progress is done to refocus the chooser view
+ if (!g_StudioApp.isOnProgress()) {
+ m_focusOutTimer->stop();
+ m_focusOutTimer->deleteLater();
+ m_focusOutTimer = nullptr;
+ this->activateWindow();
+ }
+ });
+ m_focusOutTimer->start(250);
+ }
+ } else {
+ QTimer::singleShot(0, this, &FileChooserView::close);
+ }
+}
+
+void FileChooserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &FileChooserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void FileChooserView::showEvent(QShowEvent *event)
+{
+ updateSelection();
+ QQuickWidget::showEvent(event);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h
new file mode 100644
index 00000000..6b99343e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FILECHOOSERVIEW_H
+#define FILECHOOSERVIEW_H
+
+#include <QtQuickWidgets/qquickwidget.h>
+#include <QtCore/qtimer.h>
+
+class FileChooserModel;
+
+class FileChooserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(int instance READ instance)
+ Q_PROPERTY(int handle READ handle)
+
+public:
+ explicit FileChooserView(QWidget *parent = nullptr);
+
+ void setHandle(int handle);
+ int handle() const;
+
+ void setInstance(int instance);
+ int instance() const;
+
+ void updateSelection();
+
+Q_SIGNALS:
+ void fileSelected(int handle, int instance, const QString &name);
+
+protected:
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void showEvent(QShowEvent *event) override;
+ void initialize();
+ int m_handle = -1;
+ int m_instance = -1;
+ FileChooserModel *m_model = nullptr;
+ QTimer *m_focusOutTimer = nullptr;
+};
+
+#endif // IMAGECHOOSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp
new file mode 100644
index 00000000..b10e4c1e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp
@@ -0,0 +1,351 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "GuideInspectable.h"
+#include "InspectableBase.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSDMGuides.h"
+#include "InspectorGroup.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMDataTypes.h"
+#include "IInspectableItem.h"
+#include "Qt3DSDMValue.h"
+
+typedef std::function<qt3dsdm::SValue()> TGetterFunc;
+typedef std::function<void(qt3dsdm::SValue)> TSetterFunc;
+typedef std::function<void()> TCommitFunc;
+typedef std::function<void()> TCancelFunc;
+
+struct SInspectableDataInfo
+{
+ QString m_Name;
+ QString m_FormalName;
+ QString m_Description;
+ TGetterFunc m_Getter;
+ TSetterFunc m_Setter;
+ TCommitFunc m_Commit;
+ TCancelFunc m_Cancel;
+
+ SInspectableDataInfo(const QString &name, const QString &formalName,
+ const QString &description, TGetterFunc getter, TSetterFunc setter,
+ TCommitFunc commit, TCancelFunc inCancel)
+ : m_Name(name)
+ , m_FormalName(formalName)
+ , m_Description(description)
+ , m_Getter(getter)
+ , m_Setter(setter)
+ , m_Commit(commit)
+ , m_Cancel(inCancel)
+ {
+ }
+};
+
+struct SComboAttItem : public IInspectableAttributeItem
+{
+ SInspectableDataInfo m_BaseInspectableInfo;
+ qt3dsdm::TMetaDataStringList m_MetaDataTypes;
+ SComboAttItem(const SInspectableDataInfo &inInfo, const qt3dsdm::TMetaDataStringList &inTypes)
+ : m_BaseInspectableInfo(inInfo)
+ , m_MetaDataTypes(inTypes)
+ {
+ }
+ qt3dsdm::HandlerArgumentType::Value GetInspectableSubType() const override
+ {
+ return qt3dsdm::HandlerArgumentType::Property;
+ }
+ QString GetInspectableName() const override { return m_BaseInspectableInfo.m_Name; }
+ QString GetInspectableFormalName() const override
+ {
+ return m_BaseInspectableInfo.m_FormalName;
+ }
+ QString GetInspectableDescription() const override
+ {
+ return m_BaseInspectableInfo.m_Description;
+ }
+
+ qt3dsdm::SValue GetInspectableData() const override { return m_BaseInspectableInfo.m_Getter(); }
+ void SetInspectableData(const qt3dsdm::SValue &inValue) override
+ {
+ m_BaseInspectableInfo.m_Setter(inValue);
+ m_BaseInspectableInfo.m_Commit();
+ }
+
+ void ChangeInspectableData(const qt3dsdm::SValue &inValue) override
+ {
+ m_BaseInspectableInfo.m_Setter(inValue);
+ }
+ void CancelInspectableData() override { m_BaseInspectableInfo.m_Cancel(); }
+
+ float GetInspectableMin() const override { return 0; }
+ float GetInspectableMax() const override { return 0; }
+ qt3dsdm::TMetaDataStringList GetInspectableList() const override { return m_MetaDataTypes; }
+ qt3dsdm::DataModelDataType::Value GetInspectableType() const override
+ {
+ return qt3dsdm::DataModelDataType::String;
+ }
+ qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const override
+ {
+ return qt3dsdm::AdditionalMetaDataType::StringList;
+ }
+};
+
+struct SFloatIntItem : public IInspectableAttributeItem
+{
+ SInspectableDataInfo m_BaseInspectableInfo;
+ qt3dsdm::DataModelDataType::Value m_DataType;
+ float m_Min;
+ float m_Max;
+ SFloatIntItem(const SInspectableDataInfo &inInfo, qt3dsdm::DataModelDataType::Value inType,
+ float inMin = 0, float inMax = 0)
+ : m_BaseInspectableInfo(inInfo)
+ , m_DataType(inType)
+ , m_Min(inMin)
+ , m_Max(inMax)
+ {
+ }
+ qt3dsdm::HandlerArgumentType::Value GetInspectableSubType() const override
+ {
+ return qt3dsdm::HandlerArgumentType::Property;
+ }
+ QString GetInspectableName() const override { return m_BaseInspectableInfo.m_Name; }
+ QString GetInspectableFormalName() const override
+ {
+ return m_BaseInspectableInfo.m_FormalName;
+ }
+ QString GetInspectableDescription() const override
+ {
+ return m_BaseInspectableInfo.m_Description;
+ }
+
+ qt3dsdm::SValue GetInspectableData() const override { return m_BaseInspectableInfo.m_Getter(); }
+ void SetInspectableData(const qt3dsdm::SValue &inValue) override
+ {
+ m_BaseInspectableInfo.m_Setter(inValue);
+ m_BaseInspectableInfo.m_Commit();
+ }
+
+ void ChangeInspectableData(const qt3dsdm::SValue &inValue) override
+ {
+ m_BaseInspectableInfo.m_Setter(inValue);
+ }
+ void CancelInspectableData() override { m_BaseInspectableInfo.m_Cancel(); }
+
+ float GetInspectableMin() const override { return m_Min; }
+ float GetInspectableMax() const override { return m_Max; }
+ qt3dsdm::TMetaDataStringList GetInspectableList() const override
+ {
+ return qt3dsdm::TMetaDataStringList();
+ }
+ qt3dsdm::DataModelDataType::Value GetInspectableType() const override { return m_DataType; }
+ qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const override
+ {
+ if (m_Max > 0)
+ return qt3dsdm::AdditionalMetaDataType::Range;
+ else
+ return qt3dsdm::AdditionalMetaDataType::None;
+ }
+};
+
+GuideInspectable::GuideInspectable(qt3dsdm::Qt3DSDMGuideHandle inGuide)
+ : m_Guide(inGuide)
+ , m_Editor(*g_StudioApp.GetCore()->GetDoc())
+{
+}
+
+Q3DStudio::IDocumentReader &GuideInspectable::Reader() const
+{
+ return g_StudioApp.GetCore()->GetDoc()->GetDocumentReader();
+}
+
+EStudioObjectType GuideInspectable::getObjectType() const
+{
+ return OBJTYPE_GUIDE;
+}
+
+Q3DStudio::CString GuideInspectable::getName()
+{
+ return L"Guide";
+}
+
+long GuideInspectable::getGroupCount() const
+{
+ return 1;
+}
+
+CInspectorGroup *GuideInspectable::getGroup(long)
+{
+ TCommitFunc theCommiter = std::bind(&GuideInspectable::Commit, this);
+ TCancelFunc theCanceler = std::bind(&GuideInspectable::Rollback, this);
+ m_Properties.push_back(
+ std::make_shared<SFloatIntItem>(
+ SInspectableDataInfo(QStringLiteral("Position"), QObject::tr("Position"),
+ QObject::tr("Position of the guide"),
+ std::bind(&GuideInspectable::GetPosition, this),
+ std::bind(&GuideInspectable::SetPosition, this,
+ std::placeholders::_1),
+ theCommiter, theCanceler),
+ qt3dsdm::DataModelDataType::Float));
+ qt3dsdm::TMetaDataStringList theComboItems;
+ theComboItems.push_back(L"Horizontal");
+ theComboItems.push_back(L"Vertical");
+
+ m_Properties.push_back(
+ std::make_shared<SComboAttItem>(
+ SInspectableDataInfo(QStringLiteral("Orientation"), QObject::tr("Orientation"),
+ QObject::tr("Orientation of the guide"),
+ std::bind(&GuideInspectable::GetDirection, this),
+ std::bind(&GuideInspectable::SetDirection, this,
+ std::placeholders::_1),
+ theCommiter, theCanceler),
+ theComboItems));
+
+ m_Properties.push_back(
+ std::make_shared<SFloatIntItem>(
+ SInspectableDataInfo(QStringLiteral("Width"), QObject::tr("Width"),
+ QObject::tr("Width of the guide"),
+ std::bind(&GuideInspectable::GetWidth, this),
+ std::bind(&GuideInspectable::SetWidth, this,
+ std::placeholders::_1),
+ theCommiter, theCanceler),
+ qt3dsdm::DataModelDataType::Long, 1.0f, 50.0f));
+
+ CInspectorGroup *theNewGroup = new CInspectorGroup(QObject::tr("Basic"));
+ return theNewGroup;
+}
+
+bool GuideInspectable::isValid() const
+{
+ return Reader().IsGuideValid(m_Guide);
+}
+
+bool GuideInspectable::isMaster() const
+{
+ return true;
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle GuideInspectable::getInstance() const
+{
+ return 0; // guide has no instance
+}
+
+void GuideInspectable::SetDirection(const qt3dsdm::SValue &inValue)
+{
+ qt3dsdm::TDataStrPtr theData = inValue.getData<qt3dsdm::TDataStrPtr>();
+ qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide));
+ if (theData) {
+ if (qt3dsdm::AreEqual(theData->GetData(), L"Horizontal"))
+ theSetter.m_Direction = qt3dsdm::GuideDirections::Horizontal;
+ else if (qt3dsdm::AreEqual(theData->GetData(), L"Vertical"))
+ theSetter.m_Direction = qt3dsdm::GuideDirections::Vertical;
+ }
+ Editor().UpdateGuide(m_Guide, theSetter);
+ FireRefresh();
+}
+
+qt3dsdm::SValue GuideInspectable::GetDirection()
+{
+ switch (Reader().GetGuideInfo(m_Guide).m_Direction) {
+ case qt3dsdm::GuideDirections::Horizontal:
+ return std::make_shared<qt3dsdm::CDataStr>(L"Horizontal");
+ case qt3dsdm::GuideDirections::Vertical:
+ return std::make_shared<qt3dsdm::CDataStr>(L"Vertical");
+ default:
+ return std::make_shared<qt3dsdm::CDataStr>(L"");
+ }
+}
+
+void GuideInspectable::SetPosition(const qt3dsdm::SValue &inValue)
+{
+ float thePos = inValue.getData<float>();
+ qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide));
+ theSetter.m_Position = thePos;
+ Editor().UpdateGuide(m_Guide, theSetter);
+ FireRefresh();
+}
+
+qt3dsdm::SValue GuideInspectable::GetPosition()
+{
+ qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide));
+ return theSetter.m_Position;
+}
+
+void GuideInspectable::SetWidth(const qt3dsdm::SValue &inValue)
+{
+ auto theData = inValue.getData<qt3ds::QT3DSI32>();
+
+ qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide));
+ theSetter.m_Width = theData;
+ Editor().UpdateGuide(m_Guide, theSetter);
+ FireRefresh();
+
+}
+
+qt3dsdm::SValue GuideInspectable::GetWidth()
+{
+ qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide));
+ return theSetter.m_Width;
+}
+
+Q3DStudio::IDocumentEditor &GuideInspectable::Editor()
+{
+ return m_Editor.EnsureEditor(QObject::tr("Set Property"), __FILE__, __LINE__);
+}
+
+void GuideInspectable::Commit()
+{
+ m_Editor.CommitEditor();
+}
+
+void GuideInspectable::Rollback()
+{
+ m_Editor.RollbackEditor();
+}
+
+void GuideInspectable::FireRefresh()
+{
+ m_Editor.FireImmediateRefresh(qt3dsdm::Qt3DSDMInstanceHandle());
+}
+
+void GuideInspectable::Destroy()
+{
+ m_Editor.EnsureEditor(QObject::tr("Delete Guide"), __FILE__, __LINE__).DeleteGuide(m_Guide);
+ m_Editor.CommitEditor();
+}
+
+bool GuideInspectable::isHorizontal() const
+{
+ return Reader().GetGuideInfo(m_Guide).m_Direction == qt3dsdm::GuideDirections::Horizontal;
+}
+
+const std::vector<std::shared_ptr<IInspectableAttributeItem>> &
+GuideInspectable::properties() const
+{
+ return m_Properties;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h
new file mode 100644
index 00000000..7c9c0c77
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef GUIDEINSPECTABLE_H
+#define GUIDEINSPECTABLE_H
+
+#include "Qt3DSDMHandles.h"
+#include "InspectableBase.h"
+#include "IDocumentEditor.h"
+#include "IInspectableItem.h"
+
+class GuideInspectable : public CInspectableBase
+{
+public:
+ GuideInspectable(qt3dsdm::Qt3DSDMGuideHandle inGuide);
+
+ Q3DStudio::IDocumentReader &Reader() const;
+ // Interface
+ EStudioObjectType getObjectType() const override;
+ Q3DStudio::CString getName() override;
+ long getGroupCount() const override;
+ CInspectorGroup *getGroup(long) override;
+ bool isValid() const override;
+ bool isMaster() const override;
+ qt3dsdm::Qt3DSDMInstanceHandle getInstance() const override;
+
+ // Implementation to get/set properties
+
+ void SetDirection(const qt3dsdm::SValue &inValue);
+ void SetPosition(const qt3dsdm::SValue &inValue);
+ void SetWidth(const qt3dsdm::SValue &inValue);
+
+ qt3dsdm::SValue GetDirection();
+ qt3dsdm::SValue GetPosition();
+ qt3dsdm::SValue GetWidth();
+
+ Q3DStudio::IDocumentEditor &Editor();
+ void Commit();
+ void Rollback();
+ void FireRefresh();
+ void Destroy();
+
+ bool isHorizontal() const;
+
+ const std::vector<std::shared_ptr<IInspectableAttributeItem>> &properties() const;
+
+private:
+ qt3dsdm::Qt3DSDMGuideHandle m_Guide;
+ Q3DStudio::CUpdateableDocumentEditor m_Editor;
+ std::vector<std::shared_ptr<IInspectableAttributeItem>> m_Properties;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml
new file mode 100644
index 00000000..b1514d03
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.2
+import "../controls"
+
+RowLayout {
+ id: root
+
+ signal showBrowser
+ property string value: ""
+ property alias activeBrowser: browser.activeBrowser
+
+ BrowserCombo {
+ id: browser
+ Layout.preferredWidth: _valueWidth
+ Layout.fillWidth: true
+ value: root.value === "" ? qsTr("Select...") : root.value
+ onShowBrowser: root.showBrowser()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml
new file mode 100644
index 00000000..15857584
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.2
+import "../controls"
+
+RowLayout {
+ id: root
+
+ signal showBrowser
+ property alias value: browser.value
+ property alias activeBrowser: browser.activeBrowser
+
+ BrowserCombo {
+ id: browser
+ Layout.preferredWidth: _valueWidth
+ Layout.fillWidth: true
+ onShowBrowser: root.showBrowser()
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h
new file mode 100644
index 00000000..9ed87f73
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef __IINSPECTABLEITEM_H__
+#define __IINSPECTABLEITEM_H__
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "Qt3DSDMDataTypes.h"
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMActionInfo.h"
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSString.h"
+
+//==============================================================================
+// Forwards
+//==============================================================================
+class CStudioApp;
+class IInspectableItem;
+
+//==============================================================================
+// Abstract Base Classes
+//==============================================================================
+
+enum EInspectableItemTypes {
+ INSPECTABLEITEMTYPE_VANILLA = 1,
+ INSPECTABLEITEMTYPE_PROPERTY,
+ INSPECTABLEITEMTYPE_DEPENDENT,
+ INSPECTABLEITEMTYPE_SLIDE,
+ INSPECTABLEITEMTYPE_OBJECTREFERENCE,
+ INSPECTABLEITEMTYPE_EVENTSOURCE,
+ INSPECTABLEITEMTYPE_ACTION,
+ INSPECTABLEITEMTYPE_CONDITIONS,
+};
+
+//==============================================================================
+/**
+ * @class IInspectableItemChangeListener
+ * @brief Listener class for inspectable item changes.
+ */
+class IInspectableItemChangeListener
+{
+public:
+ virtual void OnInspectablePropertyChanged(IInspectableItem *inProperty) = 0;
+};
+
+class IInspectableObject
+{
+public:
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableBaseInstance() = 0;
+ virtual void SetInspectableObject(const qt3dsdm::SObjectRefType &) = 0;
+ virtual qt3dsdm::SObjectRefType GetInspectableObject() = 0;
+};
+
+class IInspectableEvent
+{
+public:
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableInstance() = 0;
+ virtual qt3dsdm::Qt3DSDMEventHandle GetInspectableEvent() = 0;
+ virtual void SetInspectableEvent(const qt3dsdm::Qt3DSDMEventHandle &inEventHandle) = 0;
+};
+
+class IInspectableTargetSection : public IInspectableObject
+{
+public:
+ virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0;
+};
+
+class IInspectableEventSection : public IInspectableObject, public IInspectableEvent
+{
+public:
+ virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0;
+};
+
+class IInspectableHandlerSection
+{
+public:
+ virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0;
+ virtual qt3dsdm::Qt3DSDMHandlerHandle GetInspectableHandler() = 0;
+ virtual void SetInspectableHandler(const qt3dsdm::Qt3DSDMHandlerHandle &inHandlerHandle) = 0;
+
+ virtual qt3dsdm::THandlerHandleList GetInspectableHandlerList() = 0;
+ virtual long GetArgumentCount() = 0;
+ virtual IInspectableItem *GetArgument(long inIndex) = 0;
+ virtual Q3DStudio::CString GetInspectableDescription() = 0;
+};
+
+//==============================================================================
+/**
+ * @class IInspectableItem
+ * @brief Abstract base class for inspectable items.
+ */
+class IInspectableItem
+{
+public:
+ virtual ~IInspectableItem() {}
+ virtual EInspectableItemTypes GetInspectableKind() { return INSPECTABLEITEMTYPE_VANILLA; }
+
+ virtual qt3dsdm::HandlerArgumentType::Value
+ GetInspectableSubType() const = 0; // TODO : Make this method name correct
+ virtual QString GetInspectableName() const = 0;
+ virtual QString GetInspectableFormalName() const = 0;
+ virtual QString GetInspectableDescription() const = 0;
+
+ virtual qt3dsdm::SValue GetInspectableData() const = 0;
+ virtual void SetInspectableData(const qt3dsdm::SValue &) = 0;
+
+ // TODO: Remove from here onwards after cleaning up the rest of the UI classes
+ // This is the non-commital version of SetInspectableData, which must be called
+ // after ChangeInspectableData to commit the action.
+ virtual bool GetInspectableReadOnly() const { return false; }
+
+ virtual void ChangeInspectableData(const qt3dsdm::SValue & /*inAttr*/){};
+ virtual void CancelInspectableData(){}
+
+ virtual void AddInspectableChangeListener(IInspectableItemChangeListener * /*inListener*/){};
+ virtual void RemoveInspectableChangeListener(IInspectableItemChangeListener * /*inListener*/){};
+};
+
+//==============================================================================
+/**
+ * Property specialization
+ */
+class IInspectablePropertyItem : public IInspectableItem
+{
+public:
+ EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_PROPERTY; }
+ virtual void GetInspectablePropertyList(qt3dsdm::TPropertyHandleList &outList) = 0;
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableInstance() = 0;
+};
+
+//==============================================================================
+/**
+ * Attribute specialization
+ */
+class IInspectableAttributeItem : public IInspectableItem
+{
+public:
+ EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_DEPENDENT; }
+ virtual float GetInspectableMin() const = 0;
+ virtual float GetInspectableMax() const = 0;
+ virtual qt3dsdm::TMetaDataStringList GetInspectableList() const = 0;
+ virtual qt3dsdm::DataModelDataType::Value GetInspectableType() const = 0;
+ virtual qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const = 0;
+};
+
+//==============================================================================
+/**
+ * Slide specialization
+ */
+class IInspectableSlideItem : public IInspectableItem
+{
+public:
+ EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_SLIDE; }
+ virtual void GetSlideNames(std::list<Q3DStudio::CString> &outSlideNames) = 0;
+};
+
+//==============================================================================
+/**
+ * ObjectReference specialiaztion
+ */
+class IInspectableObjectRefItem : public IInspectableObject, public IInspectableItem
+{
+public:
+ EInspectableItemTypes GetInspectableKind() override
+ {
+ return INSPECTABLEITEMTYPE_OBJECTREFERENCE;
+ }
+};
+
+//==============================================================================
+/**
+ * Event specialization
+ */
+class IInspectableEventItem : public IInspectableEvent, public IInspectableItem
+{
+public:
+ EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_EVENTSOURCE; }
+};
+
+#endif // #ifndef __IINSPECTABLEITEM_H__
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml
new file mode 100644
index 00000000..6cfab327
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 10
+ ListView {
+ id: listView
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 4
+
+ boundsBehavior: Flickable.StopAtBounds
+ spacing: 4
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _imageChooserModel
+
+ delegate: ChooserDelegate {
+ onClicked: {
+ _imageChooserView.imageSelected(_imageChooserView.handle,
+ _imageChooserView.instance, filePath);
+ }
+ onDoubleClicked: {
+ _imageChooserView.imageSelected(_imageChooserView.handle,
+ _imageChooserView.instance, filePath);
+ _imageChooserView.hide();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp
new file mode 100644
index 00000000..dd7ed605
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ImageChooserModel.h"
+#include "StudioApp.h"
+#include "ProjectFile.h"
+#include "Core.h"
+
+ImageChooserModel::ImageChooserModel(bool showQmls, QObject *parent)
+ : ChooserModelBase(parent)
+ , m_showQmls(showQmls)
+{
+ connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::presentationIdChanged,
+ this, &ImageChooserModel::handlePresentationIdChange);
+}
+
+ImageChooserModel::~ImageChooserModel()
+{
+}
+
+bool ImageChooserModel::isVisible(const QString &path) const
+{
+ return getIconType(path) == OBJTYPE_IMAGE
+ || !g_StudioApp.getPresentationId(path).isEmpty()
+ || (m_showQmls && !g_StudioApp.getQmlId(path).isEmpty());
+}
+
+const QVector<ChooserModelBase::FixedItem> ImageChooserModel::getFixedItems() const
+{
+ static const QVector<FixedItem> items = { { OBJTYPE_IMAGE, "", noneString() } };
+ return items;
+}
+
+QString ImageChooserModel::specialDisplayName(const ChooserModelBase::TreeItem &item) const
+{
+ // Renderable items display the id instead of file name
+ const QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return g_StudioApp.getRenderableId(path);
+}
+
+void ImageChooserModel::handlePresentationIdChange(const QString &path, const QString &id)
+{
+ Q_UNUSED(path)
+ Q_UNUSED(id)
+
+ // Simply update the filename for all rows
+ Q_EMIT dataChanged(index(0), index(rowCount() - 1), {QFileSystemModel::FileNameRole});
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h
new file mode 100644
index 00000000..ceb34b29
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef IMAGECHOOSERMODEL_H
+#define IMAGECHOOSERMODEL_H
+
+#include "ChooserModelBase.h"
+
+class ImageChooserModel : public ChooserModelBase
+{
+ Q_OBJECT
+
+public:
+ explicit ImageChooserModel(bool showQmls, QObject *parent = nullptr);
+ virtual ~ImageChooserModel();
+
+private:
+ bool isVisible(const QString &path) const override;
+ const QVector<FixedItem> getFixedItems() const override;
+ QString specialDisplayName(const TreeItem &item) const override;
+ void handlePresentationIdChange(const QString &path, const QString &id);
+
+ bool m_showQmls = false;
+};
+
+#endif // IMAGECHOOSERMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp
new file mode 100644
index 00000000..e8180f94
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ImageChooserView.h"
+#include "ImageChooserModel.h"
+#include "StudioPreferences.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMValue.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+ImageChooserView::ImageChooserView(QWidget *parent)
+ : QQuickWidget(parent)
+ , m_model(new ImageChooserModel(true, this))
+{
+ setWindowTitle(tr("Images"));
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &ImageChooserView::initialize);
+}
+
+void ImageChooserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"),
+ StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_imageChooserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_imageChooserModel"), m_model);
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/ImageChooser.qml")));
+}
+
+void ImageChooserView::setHandle(int handle)
+{
+ m_handle = handle;
+}
+
+int ImageChooserView::handle() const
+{
+ return m_handle;
+}
+
+void ImageChooserView::setInstance(int instance)
+{
+ m_instance = instance;
+}
+
+int ImageChooserView::instance() const
+{
+ return m_instance;
+}
+
+QString ImageChooserView::currentDataModelPath() const
+{
+ QString cleanPath;
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(m_instance, m_handle, value);
+
+ const auto guid = qt3dsdm::get<qt3dsdm::SLong4>(value);
+
+ const auto imageInstance = doc->GetDocumentReader().GetInstanceForGuid(guid);
+ if (imageInstance.Valid()) {
+ const QString path = doc->GetDocumentReader().GetSourcePath(imageInstance).toQString();
+
+ // If path is renderable id, we need to resolve the actual path
+ const QString renderablePath = g_StudioApp.getRenderableAbsolutePath(path);
+
+ if (renderablePath.isEmpty())
+ cleanPath = path;
+ else
+ cleanPath = renderablePath;
+
+ cleanPath = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(cleanPath));
+ } else {
+ cleanPath = ChooserModelBase::noneString();
+ }
+
+ return cleanPath;
+}
+
+void ImageChooserView::updateSelection()
+{
+ m_model->setCurrentFile(currentDataModelPath());
+}
+
+bool ImageChooserView::isFocused() const
+{
+ return hasFocus();
+}
+
+void ImageChooserView::focusInEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusInEvent(event);
+ emit focusChanged();
+}
+
+void ImageChooserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ emit focusChanged();
+ QTimer::singleShot(0, this, &QQuickWidget::close);
+}
+
+void ImageChooserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &ImageChooserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void ImageChooserView::showEvent(QShowEvent *event)
+{
+ updateSelection();
+ QQuickWidget::showEvent(event);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h
new file mode 100644
index 00000000..bbf8eff5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef IMAGECHOOSERVIEW_H
+#define IMAGECHOOSERVIEW_H
+
+#include <QQuickWidget>
+
+class ImageChooserModel;
+
+class ImageChooserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged)
+ Q_PROPERTY(int instance READ instance)
+ Q_PROPERTY(int handle READ handle)
+
+public:
+ explicit ImageChooserView(QWidget *parent = nullptr);
+
+ void setHandle(int handle);
+ int handle() const;
+
+ void setInstance(int instance);
+ int instance() const;
+ QString currentDataModelPath() const;
+
+ void updateSelection();
+
+Q_SIGNALS:
+ void imageSelected(int handle, int instance, const QString &name);
+
+protected:
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+Q_SIGNALS:
+ void focusChanged();
+
+private:
+ void showEvent(QShowEvent *event) override;
+ void initialize();
+ bool isFocused() const;
+
+ int m_handle = -1;
+ int m_instance = -1;
+ ImageChooserModel *m_model = nullptr;
+};
+
+#endif // IMAGECHOOSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h
new file mode 100644
index 00000000..fa21b57e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef INSPECTABLEBASE_H
+#define INSPECTABLEBASE_H
+
+class CInspectorGroup;
+
+#include "StudioObjectTypes.h"
+#include "Qt3DSString.h"
+#include "Qt3DSDMHandles.h"
+
+ // Parent class of Inspectable types that will appear in the Inspector Palette.
+class CInspectableBase
+{
+public:
+ CInspectableBase() {}
+ virtual ~CInspectableBase() {}
+
+ virtual EStudioObjectType getObjectType() const = 0;
+ virtual Q3DStudio::CString getName() = 0;
+ virtual long getGroupCount() const = 0;
+ virtual CInspectorGroup *getGroup(long inIndex) = 0;
+ virtual bool isValid() const = 0;
+ virtual bool isMaster() const = 0;
+ virtual qt3dsdm::Qt3DSDMInstanceHandle getInstance() const = 0;
+};
+
+#endif // INSPECTABLEBASE_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp
new file mode 100644
index 00000000..53519573
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp
@@ -0,0 +1,1973 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "InspectorControlModel.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "InspectorGroup.h"
+#include "Qt3DSDMInspectorGroup.h"
+#include "Qt3DSDMInspectorRow.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMDataCore.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSDMSignals.h"
+#include "CmdDataModelDeanimate.h"
+#include "GuideInspectable.h"
+#include "Qt3DSDMDataTypes.h"
+#include "IObjectReferenceHelper.h"
+#include "SlideSystem.h"
+#include "Qt3DSDMMaterialInspectable.h"
+#include "ClientDataModelBridge.h"
+#include "IDocumentReader.h"
+#include "IStudioRenderer.h"
+#include "Dialogs.h"
+#include "Dispatch.h"
+#include "VariantsGroupModel.h"
+#include "StudioProjectSettings.h"
+#include "Literals.h"
+
+#include <QtCore/qfileinfo.h>
+
+static QStringList renderableItems()
+{
+ QStringList renderables;
+ renderables.push_back(QObject::tr("No renderable item"));
+ const CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ Q3DStudio::CString docDir = Q3DStudio::CString::fromQString(doc->GetDocumentDirectory());
+
+ for (SubPresentationRecord r : qAsConst(g_StudioApp.m_subpresentations))
+ renderables.push_back(r.m_id);
+
+ // second step, find the renderable plugins.
+ {
+ Q3DStudio::CFilePath pluginDir
+ = Q3DStudio::CFilePath::CombineBaseAndRelative(docDir, "plugins");
+ if (pluginDir.Exists() && pluginDir.IsDirectory()) {
+ std::vector<Q3DStudio::CFilePath> dirFiles;
+ pluginDir.ListFilesAndDirectories(dirFiles);
+ for (size_t idx = 0, end = dirFiles.size(); idx < end; ++idx) {
+ if (dirFiles[idx].IsFile()) {
+ Q3DStudio::CFilePath relPath =
+ Q3DStudio::CFilePath::GetRelativePathFromBase(docDir, dirFiles[idx]);
+ renderables.push_back(relPath.toQString());
+ }
+ }
+ }
+ }
+ std::sort(renderables.begin() + 1, renderables.end());
+ return renderables;
+}
+
+static std::pair<bool, bool> getSlideCharacteristics(qt3dsdm::Qt3DSDMInstanceHandle instance,
+ const qt3dsdm::ISlideCore &slideCore,
+ const qt3dsdm::ISlideSystem &slideSystem)
+{
+ // Get the slide from the instance.
+ qt3dsdm::Qt3DSDMSlideHandle slide = slideCore.GetSlideByInstance(instance);
+ qt3dsdm::Qt3DSDMSlideHandle master = slideSystem.GetMasterSlide(slide);
+ int index = int(slideSystem.GetSlideIndex(slide));
+ int count = int(slideSystem.GetSlideCount(master));
+ bool hasNextSlide = index > 0 && index < count - 1;
+ bool hasPreviousSlide = index > 1;
+ return std::make_pair(hasNextSlide, hasPreviousSlide);
+}
+
+InspectorControlModel::InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_UpdatableEditor(*g_StudioApp.GetCore()->GetDoc())
+ , m_variantsModel(variantsModel)
+{
+ m_modifiedProperty.first = 0;
+ m_modifiedProperty.second = 0;
+}
+
+void InspectorControlModel::setInspectable(CInspectableBase *inInspectable)
+{
+ // Commit any pending transactions on selection change
+ m_UpdatableEditor.CommitEditor();
+ m_modifiedProperty.first = 0;
+ m_modifiedProperty.second = 0;
+ m_previouslyCommittedValue = {};
+
+ if (m_inspectableBase != inInspectable) {
+ m_inspectableBase = inInspectable;
+ rebuildTree();
+ }
+}
+
+CInspectableBase *InspectorControlModel::inspectable() const
+{
+ return m_inspectableBase;
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle InspectorControlModel::getReferenceMaterial(
+ CInspectableBase *inspectable) const
+{
+ if (inspectable)
+ return getBridge()->getMaterialReference(inspectable->getInstance());
+
+ return 0;
+}
+
+void InspectorControlModel::notifyPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ if (!getBridge()->IsSceneGraphInstance(inInstance))
+ return;
+
+ if (inProperty == getBridge()->getVariantsProperty(inInstance)) {
+ // variants model is updated upon edit but this is needed to handle undoing
+ m_variantsModel->refresh();
+ return;
+ }
+
+ bool changed = false;
+ for (int row = 0; row < m_groupElements.count(); ++row) {
+ auto group = m_groupElements[row];
+ for (int p = 0; p < group.controlElements.count(); ++p) {
+ QVariant& element = group.controlElements[p];
+ InspectorControlBase *property = element.value<InspectorControlBase *>();
+ qt3dsdm::Qt3DSDMInstanceHandle imageInstance;
+ if (property->m_dataType == qt3dsdm::DataModelDataType::Long4
+ && property->m_property.Valid()) {
+ imageInstance = doc->GetDocumentReader().GetImageInstanceForProperty(
+ property->m_instance, property->m_property);
+ }
+ if (property->m_property == inProperty || imageInstance == inInstance) {
+ updatePropertyValue(property);
+ changed = true;
+ }
+ }
+ }
+ if (changed)
+ Q_EMIT dataChanged(index(0), index(rowCount() - 1));
+}
+
+bool InspectorControlModel::hasInstanceProperty(long instance, int handle)
+{
+ for (const auto &group : qAsConst(m_groupElements)) {
+ for (const auto &element : qAsConst(group.controlElements)) {
+ InspectorControlBase *property = element.value<InspectorControlBase *>();
+ if (property->m_property == qt3dsdm::CDataModelHandle(handle)
+ && property->m_instance == qt3dsdm::CDataModelHandle(instance)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool InspectorControlModel::isInsideMaterialContainer() const
+{
+ return isInsideMaterialContainer(m_inspectableBase);
+}
+
+bool InspectorControlModel::isInsideMaterialContainer(CInspectableBase *inspectable) const
+{
+ if (!inspectable || !inspectable->getInstance())
+ return false;
+
+ return getBridge()->isInsideMaterialContainer(inspectable->getInstance());
+}
+
+bool InspectorControlModel::isDefaultMaterial() const
+{
+ if (m_inspectableBase)
+ return getBridge()->isDefaultMaterial(m_inspectableBase->getInstance());
+
+ return false;
+}
+
+bool InspectorControlModel::isAnimatableMaterial() const
+{
+ return isAnimatableMaterial(m_inspectableBase);
+}
+
+bool InspectorControlModel::isAnimatableMaterial(CInspectableBase *inspectable) const
+{
+ if (!inspectable)
+ return false;
+
+ return inspectable->getObjectType() & (OBJTYPE_CUSTOMMATERIAL | OBJTYPE_MATERIAL);
+}
+
+bool InspectorControlModel::isBasicMaterial() const
+{
+ return isBasicMaterial(m_inspectableBase);
+}
+
+bool InspectorControlModel::isBasicMaterial(CInspectableBase *inspectable) const
+{
+ if (!inspectable)
+ return false;
+
+ if (inspectable->getObjectType() == OBJTYPE_REFERENCEDMATERIAL) {
+ const auto instance = inspectable->getInstance();
+ if (!instance.Valid())
+ return false;
+
+ const auto refMaterial = getBridge()->getMaterialReference(instance);
+ if (refMaterial.Valid() && getBridge()->isInsideMaterialContainer(refMaterial))
+ return true;
+ }
+
+ return false;
+}
+
+bool InspectorControlModel::isMaterial() const
+{
+ if (m_inspectableBase)
+ return m_inspectableBase->getObjectType() & OBJTYPE_IS_MATERIAL;
+
+ return false;
+}
+
+void InspectorControlModel::addMaterial()
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance();
+ if (!instance.Valid())
+ return;
+
+ QString path = doc->getSceneEditor()->getMaterialDirectoryPath() + QStringLiteral("/Material");
+ QString extension = QStringLiteral(".materialdef");
+
+ auto absPath = path + extension;
+ int i = 1;
+ while (QFileInfo(absPath).exists()) {
+ i++;
+ absPath = path + QString::number(i) + extension;
+ }
+
+ qt3dsdm::Qt3DSDMInstanceHandle newMaterial;
+ {
+ newMaterial = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {})
+ ->getOrCreateMaterial(absPath, false);
+ }
+ // Several aspects of the editor are not updated correctly
+ // if the data core is changed without a transaction
+ // The above scope completes the transaction for creating a new material
+ // Next the added undo has to be popped from the stack
+ // TODO: Find a way to update the editor fully without a transaction
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+
+ saveIfMaterial(newMaterial);
+
+ Q3DStudio::ScopedDocumentEditor sceneEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type")));
+ doc->SelectDataModelObject(newMaterial);
+
+ const auto type = getBridge()->GetObjectType(instance);
+ if (type == OBJTYPE_REFERENCEDMATERIAL) {
+ sceneEditor->setMaterialReferenceByPath(instance, absPath);
+ const auto relPath = doc->GetRelativePathToDoc(absPath);
+ sceneEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath));
+ sceneEditor->SetName(instance, getBridge()->GetName(newMaterial, true));
+
+ doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue(
+ instance, getBridge()->GetNameProperty());
+ }
+}
+
+void InspectorControlModel::duplicateMaterial()
+{
+ qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance();
+ if (!instance.Valid())
+ return;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto type = m_inspectableBase->getObjectType();
+
+ if (type & ~OBJTYPE_IS_MATERIAL)
+ return;
+
+ auto material = instance;
+ if (type == OBJTYPE_REFERENCEDMATERIAL)
+ material = getReferenceMaterial(m_inspectableBase);
+
+ if (material.Valid()) {
+ const auto sceneEditor = doc->getSceneEditor();
+ auto originalMaterialName = sceneEditor->GetName(material).toQString()
+ + QStringLiteral(" Copy");
+ int slashIndex = originalMaterialName.lastIndexOf(QLatin1Char('/'));
+ if (slashIndex != -1)
+ originalMaterialName = originalMaterialName.mid(slashIndex + 1);
+ auto materialName = originalMaterialName;
+ int i = 1;
+ auto absPath = sceneEditor->getMaterialFilePath(materialName);
+ while (QFileInfo(absPath).exists()) {
+ i++;
+ materialName = originalMaterialName + QString::number(i);
+ absPath = sceneEditor->getMaterialFilePath(materialName);
+ }
+
+ qt3dsdm::Qt3DSDMInstanceHandle duplicate;
+ {
+ Q3DStudio::ScopedDocumentEditor scopedEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {}));
+ duplicate = scopedEditor->getOrCreateMaterial(materialName, false);
+ scopedEditor->copyMaterialProperties(material, duplicate);
+ }
+ // Several aspects of the editor are not updated correctly
+ // if the data core is changed without a transaction
+ // The above scope completes the transaction for creating a new material
+ // Next the added undo has to be popped from the stack
+ // TODO: Find a way to update the editor fully without a transaction
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+
+ saveIfMaterial(duplicate);
+
+ Q3DStudio::ScopedDocumentEditor scopedEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type")));
+ doc->SelectDataModelObject(duplicate);
+
+ if (type == OBJTYPE_REFERENCEDMATERIAL) {
+ scopedEditor->setMaterialReferenceByPath(instance, absPath);
+ const auto relPath = doc->GetRelativePathToDoc(absPath);
+ scopedEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath));
+ scopedEditor->SetName(instance, getBridge()->GetName(duplicate, true));
+ doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue(
+ instance, getBridge()->GetNameProperty());
+ }
+ }
+}
+
+void InspectorControlModel::updateMaterialValues(const QStringList &values, int elementIndex,
+ bool updatingShaders)
+{
+ // Find if there are any material items and update the values of those
+ int startIndex = 0;
+ bool isReferenced = !isAnimatableMaterial() && updatingShaders;
+ if (isReferenced && m_groupElements.count() > 0)
+ startIndex = m_groupElements.count() - 1; // Update the last group for referenced materials
+ for (int row = startIndex; row < m_groupElements.count(); ++row) {
+ const CInspectorGroup *inspectorGroup = m_inspectableBase->getGroup(row);
+ const auto group = dynamic_cast<const Qt3DSDMInspectorGroup *>(inspectorGroup);
+ const auto materialGroup = dynamic_cast<const Qt3DSDMMaterialInspectorGroup *>(group);
+ if (materialGroup && (materialGroup->isMaterialGroup() || isReferenced)) {
+ if (m_groupElements[row].controlElements.size()) {
+ auto item = m_groupElements[row].controlElements[elementIndex]
+ .value<InspectorControlBase *>();
+ item->m_values = values;
+ Q_EMIT item->valuesChanged();
+ // Changing values resets the selected index, so pretend the value has also changed
+ Q_EMIT item->valueChanged();
+ }
+ }
+ }
+}
+
+void InspectorControlModel::updateShaderValues()
+{
+ int index = 0;
+ if (isAnimatableMaterial() && !isInsideMaterialContainer())
+ index = 1;
+ updateMaterialValues(shaderValues(), index, true);
+}
+
+void InspectorControlModel::updateMatDataValues()
+{
+ int index = 0;
+ if (!isInsideMaterialContainer())
+ index = 1;
+ updateMaterialValues(matDataValues(), index);
+}
+
+void InspectorControlModel::setMaterials(std::vector<Q3DStudio::CFilePath> &materials)
+{
+ m_materials.clear();
+ for (Q3DStudio::CFilePath &path : materials) {
+ const QString absolutePath = g_StudioApp.GetCore()->GetDoc()->GetResolvedPathToDoc(path);
+ const QString name = g_StudioApp.GetCore()->GetDoc()->GetDocumentReader()
+ .GetCustomMaterialName(absolutePath).toQString();
+
+ m_materials.push_back({name, path.toQString()});
+ }
+
+ if (!isDefaultMaterial())
+ updateShaderValues();
+}
+
+void InspectorControlModel::setMatDatas(const std::vector<Q3DStudio::CFilePath> &matDatas)
+{
+ m_matDatas.clear();
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ bool isDocModified = doc->isModified();
+ const auto sceneEditor = doc->getSceneEditor();
+ if (!sceneEditor)
+ return;
+
+ bool newMaterialSelected = false;
+ for (const Q3DStudio::CFilePath &path : matDatas) {
+ bool isNewFile = true;
+ for (auto &oldPath : m_cachedMatDatas) {
+ if (path.toQString() == oldPath.toQString()) {
+ isNewFile = false;
+ break;
+ }
+ }
+
+ const QString relativePath = path.toQString();
+ const Q3DStudio::CFilePath absolutePath
+ = Q3DStudio::CFilePath::CombineBaseAndRelative(doc->GetDocumentDirectory(), path);
+
+ QString name;
+ QMap<QString, QString> values;
+ QMap<QString, QMap<QString, QString>> textureValues;
+ sceneEditor->getMaterialInfo(
+ absolutePath.toQString(), name, values, textureValues);
+
+ m_matDatas.push_back({name, relativePath, values, textureValues});
+
+ bool needRewrite = false;
+ if (values.contains(QStringLiteral("path"))) {
+ const QString oldPath = values[QStringLiteral("path")];
+ needRewrite = oldPath != absolutePath.toQString();
+ if (!QFileInfo(oldPath).exists()) {
+ const auto instance = sceneEditor->getMaterial(oldPath);
+ if (instance.Valid()) {
+ const QString oldName = sceneEditor->GetName(instance).toQString();
+ const QString newName = sceneEditor
+ ->getMaterialNameFromFilePath(relativePath);
+ const QString actualPath = sceneEditor
+ ->getFilePathFromMaterialName(oldName);
+ if (actualPath == oldPath) {
+ doc->queueMaterialRename(oldName, newName);
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Name"))
+ ->setMaterialNameByPath(instance, relativePath);
+ }
+ }
+ }
+ }
+
+ auto material = sceneEditor->getMaterial(relativePath);
+ if (isNewFile && !newMaterialSelected && !material.Valid()) {
+ {
+ material = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString())
+ ->getOrCreateMaterial(relativePath, false);
+ }
+ // Several aspects of the editor are not updated correctly
+ // if the data core is changed without a transaction
+ // The above scope completes the transaction for creating a new material
+ // Next the added undo has to be popped from the stack
+ // TODO: Find a way to update the editor fully without a transaction
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+ }
+
+ if (material.Valid())
+ sceneEditor->setMaterialValues(relativePath, values, textureValues);
+
+ if (isNewFile && !newMaterialSelected) {
+ doc->SelectDataModelObject(material);
+ newMaterialSelected = true;
+ }
+
+ if (needRewrite && material.Valid())
+ sceneEditor->writeMaterialFile(material, name, false, absolutePath.toQString());
+ }
+
+ if (isBasicMaterial())
+ updateMatDataValues();
+
+ sceneEditor->removeDeletedFromMaterialContainer();
+ // Modified flag has to be restored because of the hidden transaction
+ doc->SetModifiedFlag(isDocModified);
+
+ m_cachedMatDatas = matDatas;
+}
+
+QString InspectorControlModel::getBasicMaterialString() const
+{
+ return QObject::tr("Basic Material");
+}
+
+QString InspectorControlModel::getAnimatableMaterialString() const
+{
+ return QObject::tr("Animatable Material");
+}
+
+QString InspectorControlModel::getReferencedMaterialString() const
+{
+ return QObject::tr("Referenced Material");
+}
+
+QString InspectorControlModel::getStandardMaterialString() const
+{
+ return QObject::tr("Standard");
+}
+
+QString InspectorControlModel::getDefaultMaterialString() const
+{
+ return QObject::tr("Default");
+}
+
+bool InspectorControlModel::isGroupCollapsed(int groupIdx) const
+{
+ if (m_inspectableBase) {
+ auto instance = m_inspectableBase->getInstance();
+ if (instance && groupIdx > -1 && groupIdx < m_groupElements.size()
+ && m_collapseMap.contains(instance)) {
+ return m_collapseMap[instance].contains(groupIdx);
+ }
+ }
+
+ return false;
+}
+
+void InspectorControlModel::updateGroupCollapseState(int groupIdx, bool isCollapsed)
+{
+ if (m_inspectableBase) {
+ auto instance = m_inspectableBase->getInstance();
+ if (instance && groupIdx > -1 && groupIdx < m_groupElements.size()) {
+ if (isCollapsed)
+ m_collapseMap[instance][groupIdx] = true;
+ else
+ m_collapseMap[instance].remove(groupIdx);
+ }
+ }
+}
+
+void InspectorControlModel::updateFontValues(InspectorControlBase *element) const
+{
+ // Find if there are any font items and update the values of those
+ QVector<InspectorControlBase *> fontElements;
+ if (element) {
+ fontElements.append(element);
+ } else {
+ for (int row = 0; row < m_groupElements.count(); ++row) {
+ auto group = m_groupElements[row];
+ for (int p = 0; p < group.controlElements.count(); ++p) {
+ QVariant &element = group.controlElements[p];
+ InspectorControlBase *property = element.value<InspectorControlBase *>();
+ if (property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font)
+ fontElements.append(property);
+ }
+ }
+ }
+
+ if (fontElements.size()) {
+ std::vector<QString> fontNames;
+ g_StudioApp.GetCore()->GetDoc()->GetProjectFonts(fontNames);
+ QStringList possibleValues;
+ for (const auto &fontName : fontNames)
+ possibleValues.append(fontName);
+ for (auto fontElement : qAsConst(fontElements)) {
+ fontElement->m_values = possibleValues;
+ Q_EMIT fontElement->valuesChanged();
+ // Changing values resets the selected index, so pretend the value has also changed
+ Q_EMIT fontElement->valueChanged();
+ }
+ }
+}
+
+QStringList InspectorControlModel::materialTypeValues() const
+{
+ QStringList values;
+ values.push_back(getBasicMaterialString());
+ values.push_back(getAnimatableMaterialString());
+ values.push_back(getReferencedMaterialString());
+ return values;
+}
+
+QStringList InspectorControlModel::shaderValues() const
+{
+ QStringList values;
+ values.push_back(getStandardMaterialString());
+ for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx)
+ values.push_back(m_materials[matIdx].m_name);
+ return values;
+}
+
+QStringList InspectorControlModel::matDataValues() const
+{
+ QStringList values;
+ QStringList names;
+ const QString defaultMaterialShownName = getDefaultMaterialString();
+ values.push_back(defaultMaterialShownName);
+ names.push_back(defaultMaterialShownName);
+ for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) {
+ QString shownName = m_matDatas[matIdx].m_name;
+ int slashIndex = shownName.lastIndexOf(QLatin1Char('/'));
+ if (slashIndex != -1)
+ shownName = shownName.mid(slashIndex + 1);
+ if (names.contains(shownName))
+ shownName += QLatin1String(" (") + m_matDatas[matIdx].m_relativePath + QLatin1Char(')');
+ else
+ names.push_back(shownName);
+ values.push_back(shownName);
+ }
+ return values;
+}
+
+InspectorControlBase *InspectorControlModel::createMaterialTypeItem(
+ Qt3DSDMInspectable *inspectable, int groupIndex)
+{
+ InspectorControlBase *item = new InspectorControlBase;
+ item->m_instance = inspectable->GetGroupInstance(groupIndex);
+
+ item->m_title = tr("Material Type");
+ item->m_dataType = qt3dsdm::DataModelDataType::StringRef;
+ item->m_propertyType = qt3dsdm::AdditionalMetaDataType::None;
+ item->m_tooltip = tr("Type of material being used");
+ item->m_animatable = false;
+
+ const QStringList values = materialTypeValues();
+ item->m_values = values;
+
+ QString sourcePath = getBridge()->GetSourcePath(item->m_instance);
+
+ switch (inspectable->getObjectType()) {
+ case OBJTYPE_MATERIAL:
+ case OBJTYPE_CUSTOMMATERIAL:
+ item->m_value = getAnimatableMaterialString();
+ break;
+
+ case OBJTYPE_REFERENCEDMATERIAL:
+ item->m_value = getReferencedMaterialString();
+ if (sourcePath == getBridge()->getDefaultMaterialName())
+ item->m_value = getBasicMaterialString();
+ for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) {
+ if (QString::compare(m_matDatas[matIdx].m_relativePath,
+ sourcePath, Qt::CaseInsensitive) == 0) {
+ item->m_value = getBasicMaterialString();
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return item;
+}
+
+InspectorControlBase *InspectorControlModel::createShaderItem(
+ Qt3DSDMInspectable *inspectable, int groupIndex)
+{
+ InspectorControlBase *item = new InspectorControlBase;
+ item->m_instance = inspectable->GetGroupInstance(groupIndex);
+
+ item->m_title = tr("Shader");
+ item->m_dataType = qt3dsdm::DataModelDataType::StringRef;
+ item->m_propertyType = qt3dsdm::AdditionalMetaDataType::Renderable;
+ item->m_tooltip = tr("Shader being used");
+ item->m_animatable = false;
+
+ const QStringList values = shaderValues();
+ item->m_values = values;
+
+ QString sourcePath = getBridge()->GetSourcePath(item->m_instance);
+
+ item->m_value = values[0];
+ for (int matIdx = 0, end = int(m_materials.size()); matIdx < end; ++matIdx) {
+ if (m_materials[matIdx].m_relativePath == sourcePath)
+ item->m_value = values[matIdx + 1];
+ }
+
+ return item;
+}
+
+InspectorControlBase *InspectorControlModel::createMatDataItem(
+ Qt3DSDMInspectable *inspectable, int groupIndex)
+{
+ InspectorControlBase *item = new InspectorControlBase;
+ item->m_instance = inspectable->GetGroupInstance(groupIndex);
+
+ item->m_title = tr("Source Material");
+ item->m_dataType = qt3dsdm::DataModelDataType::StringRef;
+ item->m_propertyType = qt3dsdm::AdditionalMetaDataType::ObjectRef;
+ item->m_tooltip = tr("Source material definitions used");
+ item->m_animatable = false;
+
+ const QStringList values = matDataValues();
+ item->m_values = values;
+
+ QString sourcePath = getBridge()->GetSourcePath(item->m_instance);
+
+ item->m_value = getDefaultMaterialString();
+ for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) {
+ if (QString::compare(m_matDatas[matIdx].m_relativePath,
+ sourcePath, Qt::CaseInsensitive) == 0) {
+ item->m_value = values[matIdx + 1]; // + 1 for Default basic material
+ }
+ }
+
+ return item;
+}
+
+InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable,
+ Q3DStudio::Qt3DSDMInspectorRow *row,
+ int groupIndex)
+{
+ return createItem(inspectable, row->GetMetaDataPropertyInfo(), groupIndex);
+}
+
+InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable,
+ const qt3dsdm::SMetaDataPropertyInfo &metaProperty,
+ int groupIndex)
+{
+ const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ if (metaProperty.m_IsHidden)
+ return nullptr;
+
+ Q3DStudio::CString title;
+ title.Assign(metaProperty.m_FormalName.c_str());
+ if (title.IsEmpty())
+ title.Assign(metaProperty.m_Name.c_str());
+
+ // Hide name for basic materials
+ if (title == "Name" && isBasicMaterial())
+ return nullptr;
+
+ InspectorControlBase *item = new InspectorControlBase;
+ item->m_property = metaProperty.m_Property;
+ item->m_instance = inspectable->GetGroupInstance(groupIndex);
+ item->m_metaProperty = metaProperty;
+
+ item->m_title = title.toQString();
+
+ const auto propertySystem = studio->GetPropertySystem();
+ item->m_dataType = propertySystem->GetDataType(metaProperty.m_Property);
+ item->m_propertyType = static_cast<qt3dsdm::AdditionalMetaDataType::Value>
+ (propertySystem->GetAdditionalMetaDataType(item->m_instance, metaProperty.m_Property));
+ item->m_tooltip = Q3DStudio::CString(metaProperty.m_Description.c_str()).toQString();
+ // \n is parsed as \\n from the material and effect files. Replace them to fix multi-line
+ // tooltips
+ item->m_tooltip.replace(QLatin1String("\\n"), QLatin1String("\n"));
+
+ item->m_animatable = metaProperty.m_Animatable &&
+ studio->GetAnimationSystem()->IsPropertyAnimatable(item->m_instance,
+ metaProperty.m_Property);
+ // If a property is animatable, it should be controllable in addition to
+ // properties explicitly set as controllable in metadata
+ item->m_controllable = item->m_animatable || metaProperty.m_Controllable;
+
+ // disable IBL Override for reference materials
+ if (item->m_title == QLatin1String("IBL Override")
+ && getBridge()->GetObjectType(item->m_instance) == OBJTYPE_REFERENCEDMATERIAL) {
+ item->m_enabled = false;
+ }
+ auto signalProvider = studio->GetFullSystemSignalProvider();
+ if (item->m_animatable) {
+ item->m_animated = studio->GetAnimationSystem()->IsPropertyAnimated(item->m_instance,
+ metaProperty.m_Property);
+
+ // Update the Animate Toggle on undo/redo
+ item->m_connections.push_back(signalProvider->ConnectAnimationCreated(
+ std::bind(&InspectorControlModel::updateAnimateToggleState,
+ this, item)));
+
+ item->m_connections.push_back(signalProvider->ConnectAnimationDeleted(
+ std::bind(&InspectorControlModel::updateAnimateToggleState,
+ this, item)));
+ }
+
+ if (item->m_controllable) {
+ // Set the name of current controller
+ item->m_controller = currentControllerValue(item->m_instance, item->m_property);
+ // Update UI icon state and tooltip
+ updateControlledToggleState(item);
+ item->m_connections.push_back(signalProvider->ConnectControlledToggled(
+ std::bind(&InspectorControlModel::updateControlledToggleState,
+ this, item)));
+ }
+
+ // synchronize the value itself
+ updatePropertyValue(item);
+ return item;
+}
+
+qt3dsdm::SValue InspectorControlModel::currentPropertyValue(long instance, int handle) const
+{
+ auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem();
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(instance, handle, value);
+
+ return value;
+}
+
+QString InspectorControlModel::currentControllerValue(long instance, int handle) const
+{
+ return g_StudioApp.GetCore()->GetDoc()->GetCurrentController(instance, handle);
+}
+
+void InspectorControlModel::updateControlledToggleState(InspectorControlBase* inItem) const
+{
+ if (inItem->m_instance) {
+ const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ // toggle if controlledproperty contains the name of this property
+ qt3dsdm::SValue currPropVal = currentPropertyValue(
+ inItem->m_instance, studio->GetPropertySystem()->GetAggregateInstancePropertyByName(
+ inItem->m_instance, qt3dsdm::TCharStr(L"controlledproperty")));
+ Q3DStudio::CString currPropValStr;
+ if (!currPropVal.empty())
+ currPropValStr = qt3dsdm::get<qt3dsdm::TDataStrPtr>(currPropVal)->GetData();
+ // Restore original tool tip from metadata when turning control off
+ if (!currPropValStr.size()) {
+ inItem->m_controlled = false;
+ inItem->m_controller = "";
+ } else {
+ Q3DStudio::CString propName
+ = studio->GetPropertySystem()->GetName(inItem->m_property).c_str();
+ // Search specifically for whitespace followed with registered property name.
+ // This avoids finding datainput with same name as the property, as datainput
+ // name is always prepended with "$"
+ long propNamePos = currPropValStr.find(" " + propName);
+ if ((propNamePos == currPropValStr.ENDOFSTRING)
+ && (propNamePos != 0)) {
+ inItem->m_controlled = false;
+ inItem->m_controller = "";
+ } else {
+ inItem->m_controlled = true;
+ // controller name is prepended with "$" to differentiate from property
+ // with same name. Reverse find specifically for $.
+ long posCtrlr = currPropValStr.substr(0, propNamePos).ReverseFind("$");
+
+ // this is the first controller - property pair in controlledproperty
+ if (posCtrlr < 0)
+ posCtrlr = 0;
+
+ // remove $ from controller name for showing it in UI
+ posCtrlr++;
+ const QString ctrlName = currPropValStr.substr(
+ posCtrlr, propNamePos - posCtrlr).toQString();
+
+ inItem->m_controller = ctrlName;
+ }
+ }
+
+ Q_EMIT inItem->tooltipChanged();
+ // Emit signal always to trigger updating of controller name in UI
+ // also when user switches from one controller to another
+ Q_EMIT inItem->controlledChanged();
+ }
+}
+
+void InspectorControlModel::updateAnimateToggleState(InspectorControlBase* inItem)
+{
+ const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ bool animated = studio->GetAnimationSystem()->IsPropertyAnimated(inItem->m_instance,
+ inItem->m_property);
+ if (animated != inItem->m_animated) {
+ inItem->m_animated = animated;
+ Q_EMIT inItem->animatedChanged();
+ }
+}
+
+bool InspectorControlModel::isTreeRebuildRequired(CInspectableBase* inspectBase)
+{
+ if (inspectBase != m_inspectableBase || !inspectBase)
+ return true;
+
+ long theCount = m_inspectableBase->getGroupCount();
+ auto refMaterial = getReferenceMaterial(inspectBase);
+ if (refMaterial != m_refMaterial)
+ return true;
+ long refMaterialGroupCount = 0;
+ if (refMaterial.Valid())
+ refMaterialGroupCount = 1; // Only the last group of the refMaterial is used
+
+ if (m_groupElements.size() != theCount + refMaterialGroupCount)
+ return true;
+
+ for (long theIndex = 0; theIndex < theCount; ++theIndex) {
+ const CInspectorGroup *theInspectorGroup = m_inspectableBase->getGroup(theIndex);
+ if (m_groupElements.at(theIndex).groupTitle != theInspectorGroup->GetName())
+ return true;
+ }
+
+ return false;
+}
+
+bool InspectorControlModel::isGroupRebuildRequired(CInspectableBase *inspectable,
+ int theIndex) const
+{
+ Q_ASSERT(theIndex < m_groupElements.size());
+ const CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex);
+ const auto existingGroup = m_groupElements.at(theIndex);
+ if (existingGroup.groupTitle != theInspectorGroup->GetName())
+ return true;
+
+ if (const auto cdmInspectable = dynamic_cast<Qt3DSDMInspectable *>(inspectable)) {
+ int existingIndex = 0;
+ if (const auto group = dynamic_cast<const Qt3DSDMInspectorGroup *>(theInspectorGroup)) {
+ const auto materialGroup = dynamic_cast<const Qt3DSDMMaterialInspectorGroup *>(group);
+ if (materialGroup && materialGroup->isMaterialGroup()) {
+ auto i = existingGroup.controlElements.at(existingIndex++).value<InspectorControlBase*>();
+ if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex))
+ return true;
+ if (!isInsideMaterialContainer())
+ existingIndex++; // Add material type dropdown to existing elements
+ }
+
+ if ((existingGroup.controlElements.size() - existingIndex) != group->GetRows().size())
+ return true;
+
+ for (const auto row : group->GetRows()) {
+ auto i = existingGroup.controlElements.at(existingIndex++).value<InspectorControlBase*>();
+ if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex))
+ return true;
+
+ if (i->m_property != row->GetMetaDataPropertyInfo().m_Property)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+CClientDataModelBridge *InspectorControlModel::getBridge() const
+{
+ return g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+}
+
+auto InspectorControlModel::computeTree(CInspectableBase *inspectBase)
+ -> QVector<GroupInspectorControl>
+{
+ QVector<GroupInspectorControl> result;
+
+ if (inspectBase) {
+ qt3dsdm::Qt3DSDMInstanceHandle instance = inspectBase->getInstance();
+ bool isMatFromFile = instance.Valid() && getBridge()->isInsideMaterialContainer(instance);
+ long groupCount = inspectBase->getGroupCount();
+ for (long idx = 0; idx < groupCount; ++idx)
+ result.append(computeGroup(inspectBase, idx, isMatFromFile, false));
+
+ if (isDefaultMaterial() && result.size() > 0) {
+ result[result.size() - 1].groupInfo = tr("\nDefault material cannot be edited.\n\n"
+ "Create new or import material, then apply.");
+ }
+
+ //Show original material properties for referenced materials
+ auto refMaterial = getReferenceMaterial(inspectBase);
+ if (refMaterial.Valid()) {
+ auto refMaterialInspectable = g_StudioApp.getInspectableFromInstance(refMaterial);
+ if (refMaterialInspectable) {
+ QString materialSrcPath;
+ if (instance.Valid())
+ materialSrcPath = getBridge()->GetSourcePath(instance);
+
+ if (materialSrcPath != getBridge()->getDefaultMaterialName()
+ && getBridge()->GetSourcePath(refMaterial)
+ != getBridge()->getDefaultMaterialName()) {
+ result.append(computeGroup(refMaterialInspectable,
+ refMaterialInspectable->getGroupCount() - 1,
+ true, true));
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+auto InspectorControlModel::computeGroup(CInspectableBase *inspectable, int theIndex,
+ bool disableAnimation, bool isReference)
+ -> GroupInspectorControl
+{
+ CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex);
+ GroupInspectorControl result;
+ result.groupTitle = theInspectorGroup->GetName();
+ result.groupInfo.clear();
+
+ if (isReference)
+ result.groupTitle += tr(" (Reference)");
+
+ if (const auto cdmInspectable = dynamic_cast<Qt3DSDMInspectable *>(inspectable)) {
+ if (const auto group = dynamic_cast<Qt3DSDMInspectorGroup *>(theInspectorGroup)) {
+ const auto materialGroup = dynamic_cast<Qt3DSDMMaterialInspectorGroup *>(group);
+ bool isMatData = isBasicMaterial(cdmInspectable);
+ if (materialGroup && materialGroup->isMaterialGroup()) {
+ InspectorControlBase *item = nullptr;
+
+ if (!isInsideMaterialContainer(cdmInspectable) && !isReference) {
+ item = createMaterialTypeItem(cdmInspectable, theIndex);
+ if (item)
+ result.controlElements.push_back(QVariant::fromValue(item));
+ }
+
+ if (isAnimatableMaterial(cdmInspectable)) {
+ item = createShaderItem(cdmInspectable, theIndex);
+ if (item)
+ result.controlElements.push_back(QVariant::fromValue(item));
+ } else if (isMatData) {
+ item = createMatDataItem(cdmInspectable, theIndex);
+ if (item)
+ result.controlElements.push_back(QVariant::fromValue(item));
+ }
+ }
+
+ for (const auto row : group->GetRows()) {
+ InspectorControlBase *item = createItem(cdmInspectable, row, theIndex);
+ if (!item)
+ continue;
+
+ if (disableAnimation)
+ item->m_animatable = false;
+
+ if (!isMatData || item->m_title != getReferencedMaterialString())
+ result.controlElements.push_back(QVariant::fromValue(item));
+ }
+ }
+ } else if (const auto guideInspectable = dynamic_cast<GuideInspectable *>(inspectable)) {
+ // Guide properties don't come from metadata as they are not actual objects
+ m_guideInspectable = guideInspectable;
+ const auto &properties = m_guideInspectable->properties();
+ for (int i = 0, count = int(properties.size()); i < count; ++i) {
+ auto &prop = properties[i];
+ InspectorControlBase *item = new InspectorControlBase;
+ item->m_title = prop->GetInspectableFormalName();
+ item->m_dataType = prop->GetInspectableType();
+ item->m_propertyType = prop->GetInspectableAdditionalType();
+ item->m_tooltip = prop->GetInspectableDescription();
+ item->m_animatable = false;
+ item->m_controllable = false;
+ item->m_property = i + 1; // Zero property is considered invalid, so +1
+ result.controlElements.push_back(QVariant::fromValue(item));
+ updatePropertyValue(item);
+ }
+ }
+
+ return result;
+}
+
+void InspectorControlModel::rebuildTree()
+{
+ beginResetModel();
+ m_guideInspectable = nullptr;
+ QVector<QObject *> deleteVector;
+ for (int i = 0; i < m_groupElements.count(); ++i) {
+ auto group = m_groupElements[i];
+ for (int p = 0; p < group.controlElements.count(); ++p)
+ deleteVector.append(group.controlElements[p].value<QObject *>());
+ }
+ m_groupElements = computeTree(m_inspectableBase);
+ endResetModel();
+
+ // Clean the old objects after reset is done so that qml will not freak out about null pointers
+ for (int i = 0; i < deleteVector.count(); ++i)
+ deleteVector[i]->deleteLater();
+
+ m_refMaterial = getReferenceMaterial(m_inspectableBase);
+}
+
+int InspectorControlModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_groupElements.count();
+}
+
+void InspectorControlModel::updatePropertyValue(InspectorControlBase *element) const
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto studioSystem = doc->GetStudioSystem();
+ const auto propertySystem = studioSystem->GetPropertySystem();
+ qt3dsdm::SValue value;
+ const auto instance = element->m_instance;
+ qt3dsdm::Option<qt3dsdm::SMetaDataPropertyInfo> info;
+ if (m_guideInspectable) {
+ value = m_guideInspectable->properties()
+ [handleToGuidePropIndex(element->m_property)]->GetInspectableData();
+ } else {
+ if (!propertySystem->HandleValid(instance))
+ return;
+ propertySystem->GetInstancePropertyValue(instance, element->m_property, value);
+
+ if (value.getType() == qt3dsdm::DataModelDataType::None)
+ return;
+
+ const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData();
+ info = metaDataProvider->GetMetaDataPropertyInfo(
+ metaDataProvider->GetMetaDataProperty(instance, element->m_property));
+ }
+
+ bool skipEmits = false;
+ switch (element->m_dataType) {
+ case qt3dsdm::DataModelDataType::String: {
+ QString stringValue = qt3dsdm::get<QString>(value);
+ if (getBridge()->isInsideMaterialContainer(element->m_instance)) {
+ int index = stringValue.lastIndexOf(QLatin1Char('/'));
+ if (index != -1)
+ stringValue = stringValue.mid(index + 1);
+ }
+
+ element->m_value = stringValue;
+ }
+ Q_FALLTHROUGH(); // fall-through for other String-derived datatypes
+
+ case qt3dsdm::DataModelDataType::StringOrInt:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::StringList) {
+ QStringList stringlist;
+ if (m_guideInspectable) {
+ const auto strings = m_guideInspectable->properties()
+ [handleToGuidePropIndex(element->m_property)]->GetInspectableList();
+ for (auto &str : strings)
+ stringlist.append(QString::fromWCharArray(str.wide_str()));
+ } else {
+ stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData);
+ }
+ auto slideSystem = studioSystem->GetSlideSystem();
+
+ if (element->m_title == QLatin1String("Play Mode")) {
+ std::pair<bool, bool> slideData(
+ getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(),
+ *slideSystem));
+ bool hasNextSlide(slideData.first);
+ bool hasPreviousSlide(slideData.second);
+ if (!hasNextSlide && !hasPreviousSlide)
+ stringlist.removeAll("Play Through To...");
+ } else if (element->m_title == QLatin1String("Play Through To")) {
+ // the code duplication is intentional as we may ask for slide characteristics
+ // only if the property refers to slides
+ std::pair<bool, bool> slideData(
+ getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(),
+ *slideSystem));
+ bool hasNextSlide(slideData.first);
+ bool hasPreviousSlide(slideData.second);
+ if (!hasNextSlide)
+ stringlist.removeAll("Next");
+ if (!hasPreviousSlide)
+ stringlist.removeAll("Previous");
+
+ auto itemCount = stringlist.count();
+ QString listOpt;
+ int selectedSlideHandle = 0;
+ int selectedIndex = -1;
+ qt3dsdm::SStringOrInt stringOrInt = qt3dsdm::get<qt3dsdm::SStringOrInt>(value);
+ if (stringOrInt.GetType() == qt3dsdm::SStringOrIntTypes::String)
+ listOpt = QString::fromWCharArray(qt3dsdm::get<qt3dsdm::TDataStrPtr>
+ (stringOrInt.m_Value)->GetData());
+ else
+ selectedSlideHandle = qt3dsdm::get<long>(stringOrInt.m_Value);
+
+ selectedIndex = stringlist.indexOf(listOpt);
+ // Add the slide names (exclude the master slide)
+ auto slideHandle = slideSystem->GetSlideByInstance(instance);
+ auto masterSlide = slideSystem->GetMasterSlide(slideHandle);
+ long slideCount = long(slideSystem->GetSlideCount(masterSlide));
+ for (long slideIndex = 1; slideIndex < slideCount; ++slideIndex) {
+ auto currentSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex);
+ auto currentInstance = slideSystem->GetSlideInstance(currentSlide);
+
+ QString slideName = getBridge()->GetName(currentInstance).toQString();
+ //hack to add a separator before the item
+ if (slideIndex == 1 && itemCount > 0)
+ slideName += "|separator";
+ stringlist.append(slideName);
+
+ if (currentSlide.GetHandleValue() == selectedSlideHandle)
+ selectedIndex = slideIndex + itemCount - 1;
+ }
+
+ element->m_value = QString(selectedIndex > 0 ? stringlist[selectedIndex]
+ : stringlist.first()).replace(QLatin1String("|separator"),
+ QString());
+ }
+ element->m_values = stringlist;
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Import) {
+ QStringList stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData);
+ element->m_values = stringlist;
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) {
+ element->m_values = renderableItems();
+ if (element->m_value.toString().isEmpty())
+ element->m_value = element->m_values.toStringList().at(0);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::MultiLine) {
+ element->m_value = qt3dsdm::get<QString>(value);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font) {
+ updateFontValues(element);
+ skipEmits = true; // updateFontValues handles emits in correct order
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Mesh) {
+ QString meshValue = QFileInfo(qt3dsdm::get<QString>(value)).fileName();
+ element->m_value = meshValue.startsWith('#'_L1) ? meshValue.mid(1) : meshValue;
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Texture) {
+ QFileInfo fileInfo(qt3dsdm::get<QString>(value));
+ element->m_value = fileInfo.fileName();
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::PathBuffer) {
+ element->m_value = qt3dsdm::get<QString>(value);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::String) {
+ // Basic string already handled, do not warn about that.
+ // If we hit any other datatypes then give a warning
+ } else {
+ qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:"
+ << element->m_dataType << " element->m_propertyType : "
+ << element->m_propertyType;
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::StringRef:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) {
+ element->m_value = qt3dsdm::get<QString>(value);
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Bool:
+ element->m_value = qt3dsdm::get<bool>(value);
+ break;
+
+ case qt3dsdm::DataModelDataType::Long4:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Image) {
+ qt3dsdm::Option<qt3dsdm::SLong4> guid = qt3dsdm::get<qt3dsdm::SLong4>(value);
+ qt3dsdm::Qt3DSDMInstanceHandle imageInstance = doc->GetDocumentReader()
+ .GetInstanceForGuid(guid);
+ if (imageInstance.Valid()) {
+ Q3DStudio::CString path = doc->GetDocumentReader().GetSourcePath(imageInstance);
+ Q3DStudio::CFilePath relPath(path);
+ element->m_value = QVariant(relPath.GetFileName().toQString());
+ } else {
+ element->m_value = QVariant(QString());
+ }
+ } else {
+ qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:"
+ << element->m_dataType << " " << element->m_title;
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Long:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) {
+ element->m_value = qt3dsdm::get<int>(value);
+ qt3dsdm::SMetaDataRange ranges;
+ if (m_guideInspectable) {
+ const auto prop = m_guideInspectable->properties()
+ [handleToGuidePropIndex(element->m_property)];
+ ranges.m_min = prop->GetInspectableMin();
+ ranges.m_max = prop->GetInspectableMax();
+ } else {
+ ranges = qt3dsdm::get<qt3dsdm::SMetaDataRange>(info->m_MetaDataData);
+ }
+ const QList<double> rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)};
+ element->m_values = QVariant::fromValue<QList<double> >(rangesValues);
+ }
+ else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ShadowMapResolution) {
+ element->m_value = qt3dsdm::get<int>(value);
+ } else {
+ qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:"
+ << element->m_dataType;
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Float3:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) {
+ element->m_value = qt3dsdm::get<QColor>(value);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Rotation) {
+ const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(value);
+ const QList<double> float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()};
+ element->m_values = QVariant::fromValue<QList<double> >(float3Values);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) {
+ const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(value);
+ const QList<double> float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()};
+ element->m_values = QVariant::fromValue<QList<double> >(float3Values);
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Float4:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) {
+ element->m_value = qt3dsdm::get<QColor>(value);
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Float2:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) {
+ const QVector2D theFloat2 = qt3dsdm::get<QVector2D>(value);
+ const QList<double> float2Values{theFloat2.x(), theFloat2.y()};
+ element->m_values = QVariant::fromValue<QList<double> >(float2Values);
+ } else {
+ qWarning() << "TODO: InspectorControlModel::updatePropertyValue: need to implement:"
+ << element->m_dataType << element->m_propertyType;
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::Float:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) {
+ element->m_value = qt3dsdm::get<float>(value);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) {
+ element->m_value = qt3dsdm::get<float>(value);
+ const qt3dsdm::SMetaDataRange ranges = qt3dsdm::get<qt3dsdm::SMetaDataRange>(info->m_MetaDataData);
+ const QList<double> rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)};
+ element->m_values = QVariant::fromValue<QList<double> >(rangesValues);
+ } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::FontSize) {
+ element->m_value = qt3dsdm::get<float>(value);
+ }
+ break;
+
+ case qt3dsdm::DataModelDataType::ObjectRef:
+ if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ObjectRef) {
+ IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper();
+ if (objRefHelper) {
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance = objRefHelper->Resolve(value, instance);
+ QString refName = objRefHelper->LookupObjectFormalName(refInstance).toQString();
+ if (getBridge()->IsReferencedMaterialInstance(instance) && !refName.isEmpty()) {
+ // get the material's object name (parent)
+ auto parentInstance = getBridge()->GetParentInstance(refInstance);
+ qt3dsdm::SValue vParent;
+ propertySystem->GetInstancePropertyValue(parentInstance,
+ getBridge()->GetObjectDefinitions().m_Named.m_NameProp, vParent);
+ QString parentName = qt3dsdm::get<QString>(vParent);
+ refName.append(QLatin1String(" (") + parentName + QLatin1String(")"));
+ }
+ element->m_value = refName;
+ }
+ }
+ break;
+
+ default:
+ qWarning() << "TODO: InspectorControlModel::updatePropertyValue: I've no idea how to handle this datatype"
+ << element->m_dataType;
+ break;
+ }
+
+ if (!skipEmits) {
+ Q_EMIT element->valueChanged();
+ Q_EMIT element->valuesChanged();
+ }
+
+ // Controlled state must be manually set after undo operations,
+ // as only the "controlledproperty" is restored in undo,
+ // not the controlled flag nor the tooltip
+ if (element->m_controllable)
+ updateControlledToggleState(element);
+}
+
+void InspectorControlModel::refreshRenderables()
+{
+ for (int row = 0; row < m_groupElements.count(); ++row) {
+ auto group = m_groupElements[row];
+ for (int p = 0; p < group.controlElements.count(); ++p) {
+ QVariant& element = group.controlElements[p];
+ InspectorControlBase *property = element.value<InspectorControlBase *>();
+ if (property->m_property.Valid()
+ && property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) {
+ updatePropertyValue(property);
+ }
+ }
+ }
+}
+
+void InspectorControlModel::refreshTree()
+{
+ //check if the structure has changed
+ if (isTreeRebuildRequired(m_inspectableBase)) {
+ rebuildTree();
+ } else {
+ // group structure is intact, let's walk to see which rows changed
+ QVector<QObject *> deleteVector;
+ long theCount = m_inspectableBase->getGroupCount();
+ for (long theIndex = 0; theIndex < theCount; ++theIndex) {
+ if (isGroupRebuildRequired(m_inspectableBase, theIndex)) {
+ auto group = m_groupElements[theIndex];
+ for (int p = 0; p < group.controlElements.count(); ++p)
+ deleteVector.append(group.controlElements[p].value<QObject *>());
+ m_groupElements[theIndex] = computeGroup(m_inspectableBase, theIndex);
+ Q_EMIT dataChanged(index(theIndex), index(theIndex));
+ }
+ }
+ }
+}
+
+void InspectorControlModel::refresh()
+{
+ refreshTree();
+ // update values
+ for (int row = 0; row < m_groupElements.count(); ++row) {
+ auto group = m_groupElements[row];
+ for (int p = 0; p < group.controlElements.count(); ++p) {
+ QVariant& element = group.controlElements[p];
+ InspectorControlBase *property = element.value<InspectorControlBase *>();
+ if (property->m_property.Valid()) {
+ updatePropertyValue(property);
+ updateControlledToggleState(property);
+ }
+ }
+ }
+ Q_EMIT dataChanged(index(0), index(rowCount() - 1));
+}
+
+void InspectorControlModel::saveIfMaterial(qt3dsdm::Qt3DSDMInstanceHandle instance)
+{
+ if (!instance.Valid())
+ return;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto sceneEditor = doc->getSceneEditor();
+
+ const auto studio = doc->GetStudioSystem();
+ EStudioObjectType type = getBridge()->GetObjectType(instance);
+
+ auto material = instance;
+ if (type == EStudioObjectType::OBJTYPE_IMAGE)
+ material = sceneEditor->GetParent(instance);
+
+ if (!material.Valid())
+ return;
+
+ const auto refMaterial = getBridge()->getMaterialReference(material);
+ if (refMaterial.Valid())
+ material = refMaterial;
+
+ if (!getBridge()->isInsideMaterialContainer(material))
+ return;
+
+ type = getBridge()->GetObjectType(material);
+
+ if (type == EStudioObjectType::OBJTYPE_MATERIAL
+ || type == EStudioObjectType::OBJTYPE_CUSTOMMATERIAL) {
+ qt3dsdm::SValue value;
+ studio->GetPropertySystem()->GetInstancePropertyValue(
+ material, getBridge()->GetObjectDefinitions().m_Named.m_NameProp, value);
+ qt3dsdm::TDataStrPtr namePtr(qt3dsdm::get<qt3dsdm::TDataStrPtr>(value));
+ QString materialName = QString::fromWCharArray(namePtr->GetData(),
+ int(namePtr->GetLength()));
+ QString sourcePath;
+ for (int i = 0; i < m_matDatas.size(); ++i) {
+ if (QString::compare(m_matDatas[i].m_name, materialName, Qt::CaseInsensitive) == 0) {
+ sourcePath = doc->GetDocumentDirectory() + QLatin1Char('/')
+ + m_matDatas[i].m_relativePath;
+ }
+ }
+
+ sceneEditor->writeMaterialFile(material, materialName, sourcePath.isEmpty(), sourcePath);
+ }
+}
+
+void InspectorControlModel::setMaterialTypeValue(long instance, int handle, const QVariant &value)
+{
+ Q_UNUSED(handle)
+
+ const QString typeValue = value.toString();
+ QString v;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto sceneEditor = doc->getSceneEditor();
+ const Q3DStudio::CString oldType = sceneEditor->GetObjectTypeName(instance);
+ qt3dsdm::Qt3DSDMInstanceHandle refMaterial;
+ if (oldType == "ReferencedMaterial")
+ refMaterial = getBridge()->getMaterialReference(instance);
+
+ bool changeMaterialFile = false;
+ bool canCopyProperties = false;
+ if (typeValue == getAnimatableMaterialString()) {
+ v = QStringLiteral("Standard Material");
+ if (refMaterial.Valid()) {
+ const auto refSourcePath = getBridge()->GetSourcePath(refMaterial);
+ for (auto &material : m_materials) {
+ if (refSourcePath == material.m_relativePath) {
+ v = material.m_relativePath;
+ break;
+ }
+ }
+ }
+ canCopyProperties = true;
+ } else if (typeValue == getBasicMaterialString()) {
+ v = QStringLiteral("Referenced Material");
+ changeMaterialFile = true;
+ } else if (typeValue == getReferencedMaterialString()) {
+ v = QStringLiteral("Referenced Material");
+ }
+
+ Q3DStudio::ScopedDocumentEditor scopedEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type")));
+
+ scopedEditor->SetMaterialType(instance, v);
+
+ if (refMaterial.Valid() && canCopyProperties) {
+ const Q3DStudio::CString newType = sceneEditor->GetObjectTypeName(instance);
+ const Q3DStudio::CString refType = sceneEditor->GetObjectTypeName(refMaterial);
+ if (refType == newType)
+ scopedEditor->copyMaterialProperties(refMaterial, instance);
+
+ if (getBridge()->isInsideMaterialContainer(refMaterial)) {
+ const auto name = scopedEditor->GetName(instance);
+ if (!name.toQString().endsWith(QLatin1String("_animatable")))
+ scopedEditor->SetName(instance, name + "_animatable");
+ }
+ }
+
+ if (changeMaterialFile) {
+ scopedEditor->setMaterialProperties(instance, Q3DStudio::CString::fromQString(
+ getBridge()->getDefaultMaterialName()), {}, {});
+
+ // Select the original instance again since potentially creating a material selects the
+ // created one
+ doc->SelectDataModelObject(instance);
+
+ rebuildTree(); // Hack to mimic value changing behavior of the type selector
+ }
+
+ saveIfMaterial(instance);
+}
+
+void InspectorControlModel::setShaderValue(long instance, int handle, const QVariant &value)
+{
+ Q_UNUSED(handle)
+
+ const QString typeValue = value.toString();
+ QString v = QStringLiteral("Standard Material");
+ for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx) {
+ if (m_materials[matIdx].m_name == typeValue)
+ v = m_materials[matIdx].m_relativePath;
+ }
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Material Type"))
+ ->SetMaterialType(instance, v);
+
+ const auto dispatch = g_StudioApp.GetCore()->GetDispatch();
+ QVector<qt3dsdm::Qt3DSDMInstanceHandle> refMats;
+ doc->getSceneReferencedMaterials(doc->GetSceneInstance(), refMats);
+ for (auto &refMat : qAsConst(refMats)) {
+ const auto origMat = getBridge()->getMaterialReference(refMat);
+ if (origMat.Valid() && long(origMat) == instance)
+ dispatch->FireImmediateRefreshInstance(refMat);
+ }
+
+ saveIfMaterial(instance);
+}
+
+void InspectorControlModel::setMatDataValue(long instance, int handle, const QVariant &value)
+{
+ Q_UNUSED(handle)
+
+ const QString typeValue = value.toString();
+ QString v;
+ QString name;
+ Q3DStudio::CString srcPath;
+ QMap<QString, QString> values;
+ QMap<QString, QMap<QString, QString>> textureValues;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+
+ bool changeMaterialFile = false;
+ if (typeValue == getDefaultMaterialString()) {
+ v = QStringLiteral("Referenced Material");
+ name = getBridge()->getDefaultMaterialName();
+ srcPath = Q3DStudio::CString::fromQString(name);
+ changeMaterialFile = true;
+ } else {
+ const auto sceneEditor = doc->getSceneEditor();
+ for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) {
+ QString shownName = m_matDatas[matIdx].m_name;
+ int slashIndex = shownName.lastIndexOf(QLatin1Char('/'));
+ if (slashIndex != -1)
+ shownName = shownName.mid(slashIndex + 1);
+ if (QString::compare(shownName + QLatin1String(" (")
+ + m_matDatas[matIdx].m_relativePath + QLatin1Char(')'),
+ typeValue, Qt::CaseInsensitive) == 0
+ || QString::compare(shownName, typeValue, Qt::CaseInsensitive) == 0) {
+ v = QStringLiteral("Referenced Material");
+ changeMaterialFile = true;
+ name = m_matDatas[matIdx].m_name;
+ srcPath = Q3DStudio::CString::fromQString(m_matDatas[matIdx].m_relativePath);
+ const auto material = sceneEditor->getMaterial(srcPath.toQString());
+ if (material.Valid()) {
+ // Get the correct case source path
+ const auto absPath = sceneEditor->getFilePathFromMaterialName(
+ sceneEditor->GetName(material).toQString());
+ const auto relPath = QDir(doc->GetDocumentDirectory())
+ .relativeFilePath(absPath);
+ srcPath = Q3DStudio::CString::fromQString(relPath);
+ }
+ values = m_matDatas[matIdx].m_values;
+ textureValues = m_matDatas[matIdx].m_textureValues;
+ break;
+ }
+ }
+ }
+
+ if (changeMaterialFile) {
+ {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString())
+ ->setMaterialValues(srcPath.toQString(), values, textureValues);
+ }
+ // Several aspects of the editor are not updated correctly
+ // if the data core is changed without a transaction
+ // The above scope completes the transaction for creating a new material
+ // Next the added undo has to be popped from the stack
+ // TODO: Find a way to update the editor fully without a transaction
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+ }
+
+ Q3DStudio::ScopedDocumentEditor scopedEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type")));
+ scopedEditor->SetMaterialType(instance, v);
+
+ if (changeMaterialFile) {
+ scopedEditor->setMaterialSourcePath(instance, srcPath);
+ scopedEditor->setMaterialReferenceByPath(instance, srcPath.toQString());
+
+ // Select original instance again since potentially
+ // creating a material selects the created one
+ doc->SelectDataModelObject(instance);
+
+ rebuildTree(); // Hack to mimic value changing behavior of the type selector
+ }
+
+ saveIfMaterial(instance);
+}
+
+void InspectorControlModel::setRenderableValue(long instance, int handle, const QVariant &value)
+{
+ qt3dsdm::SValue oldValue = currentPropertyValue(instance, handle);
+
+ QString v = value.toString();
+ if (v == QObject::tr("No renderable item"))
+ v = QString();
+
+ if (v == qt3dsdm::get<QString>(oldValue))
+ return;
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property"))
+ ->SetInstancePropertyValueAsRenderable(instance, handle,
+ Q3DStudio::CString::fromQString(v));
+}
+
+void InspectorControlModel::setPropertyValue(long instance, int handle, const QVariant &value,
+ bool commit)
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto studio = doc->GetStudioSystem();
+ // Name property needs special handling
+ if (instance && handle == getBridge()->GetNameProperty()) {
+ // Ignore preview of name property change
+ if (!commit)
+ return;
+
+ m_modifiedProperty.first = 0;
+ m_modifiedProperty.second = 0;
+ m_previouslyCommittedValue = {};
+
+ Q3DStudio::CString currentName = getBridge()->GetName(instance, true);
+ Q3DStudio::CString newName = Q3DStudio::CString::fromQString(value.toString());
+ if (!newName.IsEmpty()) {
+ if (getBridge()->isInsideMaterialContainer(instance)
+ && ((newName.Find('/') != Q3DStudio::CString::ENDOFSTRING
+ || newName.Find('#') != Q3DStudio::CString::ENDOFSTRING
+ || newName.Find(':') != Q3DStudio::CString::ENDOFSTRING)
+ || m_suspendMaterialRename)) {
+ return;
+ }
+ qt3dsdm::Qt3DSDMInstanceHandle parentInstance = getBridge()
+ ->GetParentInstance(instance);
+
+ if (parentInstance == doc->GetSceneInstance()
+ && newName.toQString() == getBridge()->getMaterialContainerName()) {
+ QString theTitle = QObject::tr("Rename Object Error");
+ QString theString = getBridge()->getMaterialContainerName()
+ + QObject::tr(" is a reserved name.");
+ // Display error message box asynchronously so focus loss won't trigger setting
+ // the name again
+ g_StudioApp.GetDialogs()->asyncDisplayMessageBox(theTitle, theString,
+ Qt3DSMessageBox::ICON_ERROR);
+ return;
+ }
+
+ Q3DStudio::CString realNewName = newName;
+ if (getBridge()->isInsideMaterialContainer(instance)) {
+ Q3DStudio::CString realName = getBridge()->GetName(instance);
+ int slashIndex = realName.rfind('/');
+ if (slashIndex != Q3DStudio::CString::ENDOFSTRING)
+ realNewName = realName.Left(slashIndex + 1) + newName;
+ }
+
+ if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) {
+ QString origNewName = newName.toQString();
+ realNewName = getBridge()->GetUniqueChildName(parentInstance, instance,
+ realNewName);
+ newName = realNewName;
+ if (getBridge()->isInsideMaterialContainer(instance)) {
+ int slashIndex = newName.rfind('/');
+ if (slashIndex != Q3DStudio::CString::ENDOFSTRING)
+ newName = newName.substr(slashIndex + 1);
+ }
+ // Display rename message box asynchronously so focus loss won't trigger setting
+ // the name again
+ g_StudioApp.GetDialogs()->DisplayObjectRenamed(origNewName, newName.toQString(),
+ true);
+ }
+
+ const auto sceneEditor = doc->getSceneEditor();
+
+ // A materialdef with the same name might exists as a file but not in the container,
+ // so an additional check is needed for that case
+ if (getBridge()->isInsideMaterialContainer(instance)) {
+ int i = 1;
+ while (QFileInfo(sceneEditor->getFilePathFromMaterialName(
+ realNewName.toQString())).exists()) {
+ ++i;
+ realNewName = Q3DStudio::CString::fromQString(
+ realNewName.toQString() + QString::number(i));
+ if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) {
+ realNewName = getBridge()->GetUniqueChildName(parentInstance, instance,
+ realNewName);
+ }
+ }
+ newName = realNewName;
+ int slashIndex = newName.rfind('/');
+ if (slashIndex != Q3DStudio::CString::ENDOFSTRING)
+ newName = newName.substr(slashIndex + 1);
+ }
+
+ if (newName != currentName) {
+ if (getBridge()->isInsideMaterialContainer(instance)) {
+ const auto properOldName = sceneEditor->GetName(instance).toQString();
+ const QString dirPath = doc->GetDocumentDirectory();
+ for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) {
+ if (m_matDatas[matIdx].m_name == properOldName) {
+ QFileInfo fileInfo(dirPath + QLatin1Char('/')
+ + m_matDatas[matIdx].m_relativePath);
+ const QString newFile = fileInfo.absolutePath()
+ + QLatin1Char('/')
+ + newName.toQString()
+ + QStringLiteral(".materialdef");
+ const auto properNewName
+ = sceneEditor->getMaterialNameFromFilePath(newFile);
+ newName = Q3DStudio::CString::fromQString(properNewName);
+ doc->queueMaterialRename(properOldName, properNewName);
+ }
+ }
+ }
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(
+ *g_StudioApp.GetCore()->GetDoc(),
+ QObject::tr("Set Name"))->SetName(instance, newName, false);
+ }
+ }
+ return;
+ }
+
+ qt3dsdm::SValue oldValue = m_guideInspectable
+ ? m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->GetInspectableData()
+ : currentPropertyValue(instance, handle);
+ qt3dsdm::SValue v = value;
+
+ const bool hasPreview = (m_modifiedProperty.first == instance
+ && m_modifiedProperty.second == handle);
+
+ // If this set is a commit for property that was previously changed without
+ // committing, we must let the set go through even if the value hasn't changed
+ // to finish the transaction.
+ if (v == oldValue && !(commit && hasPreview))
+ return;
+
+ if (!commit && !hasPreview) {
+ m_previouslyCommittedValue = oldValue;
+ m_modifiedProperty.first = instance;
+ m_modifiedProperty.second = handle;
+ }
+
+ if (instance) {
+ // If the user enters 0.0 to any (x, y, z) values of camera scale,
+ // we reset the value back to original, because zero scale factor will crash
+ // camera-specific inverse matrix math. (Additionally, scale of zero for a camera
+ // is generally not useful anyway.) We could silently discard zero values also deeper in the
+ // value setter code, but then the inspector panel value would not be updated as opposed
+ // to both rejecting invalid and resetting the original value here.
+ EStudioObjectType theType = getBridge()->GetObjectType(instance);
+
+ if (theType == EStudioObjectType::OBJTYPE_CAMERA &&
+ studio->GetPropertySystem()->GetName(handle) == Q3DStudio::CString("scale")) {
+ const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(v);
+ if (theFloat3.x() == 0.0f || theFloat3.y() == 0.0f || theFloat3.z() == 0.0f )
+ v = oldValue;
+ }
+
+ // some properties may initialize OpenGL resources (e.g. loading meshes will
+ // initialize vertex buffers), so the renderer's OpenGL context must be current
+ Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer());
+ theRenderer.MakeContextCurrent();
+ m_UpdatableEditor.EnsureEditor(QObject::tr("Set Property"), __FILE__, __LINE__)
+ .SetInstancePropertyValue(instance, handle, v);
+
+ theRenderer.ReleaseContext();
+
+ m_UpdatableEditor.FireImmediateRefresh(instance);
+ } else if (m_guideInspectable) {
+ m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->ChangeInspectableData(v);
+ }
+
+ if (commit) {
+ m_modifiedProperty.first = 0;
+ m_modifiedProperty.second = 0;
+ if (m_previouslyCommittedValue == v) {
+ if (m_guideInspectable)
+ m_guideInspectable->Rollback();
+ else
+ m_UpdatableEditor.RollbackEditor();
+ } else {
+ if (m_guideInspectable) {
+ // If the guide ends up over the matte, destroy it
+ QSize presSize = g_StudioApp.GetCore()->GetStudioProjectSettings()
+ ->getPresentationSize();
+ bool isInPres = true;
+ qt3dsdm::SValue posValue = m_guideInspectable->GetPosition();
+ float position = qt3dsdm::get<float>(posValue);
+ if (m_guideInspectable->isHorizontal())
+ isInPres = 0.f <= position && float(presSize.height()) >= position;
+ else
+ isInPres = 0.f <= position && float(presSize.width()) >= position;
+ if (isInPres)
+ m_guideInspectable->Commit();
+ else
+ m_guideInspectable->Destroy();
+ } else {
+ m_UpdatableEditor.CommitEditor();
+ }
+ }
+
+ m_previouslyCommittedValue = {};
+ refreshTree();
+
+ saveIfMaterial(instance);
+ }
+}
+
+void InspectorControlModel::setSlideSelection(long instance, int handle, int index,
+ const QStringList &list)
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto studioSystem = doc->GetStudioSystem();
+ const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData();
+ const auto info = metaDataProvider->GetMetaDataPropertyInfo(
+ metaDataProvider->GetMetaDataProperty(instance, handle));
+ QStringList stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData);
+
+ auto slideSystem = studioSystem->GetSlideSystem();
+ std::pair<bool, bool> slideData(
+ getSlideCharacteristics(instance, *studioSystem->GetSlideCore(),
+ *slideSystem));
+ bool hasNextSlide(slideData.first);
+ bool hasPreviousSlide(slideData.second);
+ qt3dsdm::SStringOrInt newSelectedData;
+ if (!hasNextSlide)
+ stringlist.removeAll("Next");
+ if (!hasPreviousSlide)
+ stringlist.removeAll("Previous");
+
+ auto itemCount = stringlist.count();
+ if (index < itemCount) {
+ newSelectedData = qt3dsdm::SStringOrInt(std::make_shared<qt3dsdm::CDataStr>
+ (Q3DStudio::CString::fromQString(list[index]).c_str()));
+ } else {
+ auto slideHandle = slideSystem->GetSlideByInstance(instance);
+ auto masterSlide = slideSystem->GetMasterSlide(slideHandle);
+ long slideIndex = index - itemCount + 1;
+ auto newSelectedSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex);
+ newSelectedData = qt3dsdm::SStringOrInt((long)newSelectedSlide);
+ }
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property"))
+ ->SetInstancePropertyValue(instance, handle, newSelectedData);
+}
+
+// temporarily prevent material renaming when opening the colors dialog (fix for QT3DS-3407)
+void InspectorControlModel::suspendMaterialRename(bool flag)
+{
+ m_suspendMaterialRename = flag;
+}
+
+void InspectorControlModel::setPropertyControllerInstance(
+ long instance,int property, Q3DStudio::CString controllerInstance, bool controlled)
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper();
+
+ Q3DStudio::CString instancepath = Q3DStudio::CString(
+ objRefHelper->GetObjectReferenceString(doc->GetSceneInstance(),
+ CRelativePathTools::EPATHTYPE_GUID, instance));
+ Q_ASSERT(instancepath.size());
+
+ doc->SetInstancePropertyControlled(instance, instancepath, property,
+ controllerInstance, controlled);
+}
+
+void InspectorControlModel::setPropertyControlled(long instance, int property)
+{
+ const auto signalSender
+ = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalSender();
+
+ signalSender->SendControlledToggled(instance, property);
+}
+
+bool InspectorControlModel::isLayer(long instance) const
+{
+ return getBridge()->GetObjectType(instance) == EStudioObjectType::OBJTYPE_LAYER;
+}
+
+QString InspectorControlModel::renderableId(const QString &filePath) const
+{
+ return g_StudioApp.getRenderableId(filePath);
+}
+
+void InspectorControlModel::setPropertyAnimated(long instance, int handle, bool animated)
+{
+ CCmd* cmd = nullptr;
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ if (animated)
+ cmd = new CCmdDataModelAnimate(doc, instance, handle);
+ else
+ cmd = new CCmdDataModelDeanimate(doc, instance, handle);
+
+ g_StudioApp.GetCore()->ExecuteCommand(cmd);
+}
+
+QVariant InspectorControlModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(),index.parent()))
+ return {};
+
+ const auto row = index.row();
+
+ switch (role) {
+ case GroupValuesRole:
+ return m_groupElements.at(row).controlElements;
+ case GroupTitleRole:
+ return m_groupElements.at(row).groupTitle;
+ case GroupInfoRole:
+ return m_groupElements.at(row).groupInfo;
+ }
+ return {};
+}
+
+QHash<int, QByteArray> InspectorControlModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(GroupValuesRole, "values");
+ names.insert(GroupTitleRole, "title");
+ names.insert(GroupInfoRole, "info");
+ return names;
+}
+
+InspectorControlBase::~InspectorControlBase()
+{
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h
new file mode 100644
index 00000000..3063f047
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h
@@ -0,0 +1,258 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INSPECTORCONTROLMODEL_H
+#define INSPECTORCONTROLMODEL_H
+
+#include "Qt3DSDMValue.h"
+#include "Qt3DSDMMetaDataValue.h"
+#include "Qt3DSDMMetaDataTypes.h"
+#include "IDocumentEditor.h"
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qvector.h>
+
+class CInspectableBase;
+class Qt3DSDMInspectable;
+class GuideInspectable;
+class VariantsGroupModel;
+class CClientDataModelBridge;
+
+namespace qt3dsdm {
+class ISignalConnection;
+typedef std::shared_ptr<ISignalConnection> TSignalConnectionPtr;
+}
+
+namespace Q3DStudio
+{
+class Qt3DSDMInspectorRow;
+}
+
+class InspectorControlBase : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(qt3dsdm::DataModelDataType::Value dataType MEMBER m_dataType CONSTANT)
+ Q_PROPERTY(qt3dsdm::AdditionalMetaDataType::Value propertyType MEMBER m_propertyType CONSTANT)
+ Q_PROPERTY(QVariant value MEMBER m_value NOTIFY valueChanged)
+ Q_PROPERTY(QVariant values MEMBER m_values NOTIFY valuesChanged)
+ Q_PROPERTY(QString title MEMBER m_title CONSTANT)
+ Q_PROPERTY(QString toolTip MEMBER m_tooltip NOTIFY tooltipChanged)
+ Q_PROPERTY(int instance MEMBER m_instance CONSTANT)
+ Q_PROPERTY(int handle MEMBER m_property CONSTANT)
+
+ Q_PROPERTY(bool enabled MEMBER m_enabled CONSTANT)
+ Q_PROPERTY(bool animatable MEMBER m_animatable CONSTANT)
+ Q_PROPERTY(bool animated MEMBER m_animated NOTIFY animatedChanged)
+ Q_PROPERTY(bool controlled MEMBER m_controlled NOTIFY controlledChanged)
+ Q_PROPERTY(bool controllable MEMBER m_controllable CONSTANT)
+ Q_PROPERTY(QString controller MEMBER m_controller NOTIFY controlledChanged)
+
+public:
+ virtual ~InspectorControlBase();
+
+Q_SIGNALS:
+ void valueChanged();
+ void valuesChanged();
+ void animatedChanged();
+ void controlledChanged();
+ void tooltipChanged();
+
+public:
+ qt3dsdm::DataModelDataType::Value m_dataType;
+ qt3dsdm::AdditionalMetaDataType::Value m_propertyType;
+ qt3dsdm::SMetaDataPropertyInfo m_metaProperty;
+ QVariant m_value;
+ QVariant m_values;
+ QString m_title;
+ QString m_tooltip;
+
+ qt3dsdm::Qt3DSDMInstanceHandle m_instance;
+ qt3dsdm::Qt3DSDMPropertyHandle m_property;
+
+ bool m_enabled = true;
+ bool m_animatable = false;
+ bool m_animated = false;
+ bool m_controlled = false;
+ bool m_controllable = false;
+ QString m_controller;
+ std::vector<qt3dsdm::TSignalConnectionPtr> m_connections;
+};
+
+class InspectorControlModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ explicit InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent);
+ ~InspectorControlModel() override = default;
+
+ enum Roles {
+ GroupValuesRole = Qt::UserRole + 1,
+ GroupTitleRole,
+ GroupInfoRole
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ void setInspectable(CInspectableBase *inInspectable);
+ CInspectableBase *inspectable() const;
+ void setMaterials(std::vector<Q3DStudio::CFilePath> &materials);
+ void setMatDatas(const std::vector<Q3DStudio::CFilePath> &matdatas);
+ void updateFontValues(InspectorControlBase *element) const;
+ void refreshRenderables();
+ void refresh();
+ void saveIfMaterial(qt3dsdm::Qt3DSDMInstanceHandle instance);
+
+ bool hasInstanceProperty(long instance, int handle);
+
+ qt3dsdm::SValue currentPropertyValue(long instance, int handle) const;
+ QString currentControllerValue(long instance, int handle) const;
+ void setPropertyControllerInstance(long instance,int handle,
+ Q3DStudio::CString controllerInstance,
+ bool controlled);
+ void notifyPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+
+ Q_INVOKABLE void setMaterialTypeValue(long instance, int handle, const QVariant &value);
+ Q_INVOKABLE void setShaderValue(long instance, int handle, const QVariant &value);
+ Q_INVOKABLE void setMatDataValue(long instance, int handle, const QVariant &value);
+ Q_INVOKABLE void setRenderableValue(long instance, int handle, const QVariant &value);
+ Q_INVOKABLE void setPropertyValue(long instance, int handle, const QVariant &value, bool commit = true);
+ Q_INVOKABLE void setSlideSelection(long instance, int handle, int index,
+ const QStringList &list);
+ Q_INVOKABLE void suspendMaterialRename(bool flag);
+ Q_INVOKABLE void setPropertyAnimated(long instance, int handle, bool animated);
+ Q_INVOKABLE void setPropertyControlled(long instance, int property);
+ Q_INVOKABLE bool isLayer(long instance) const;
+ Q_INVOKABLE QString renderableId(const QString &filePath) const;
+ Q_INVOKABLE bool isMaterial() const;
+ Q_INVOKABLE bool isDefaultMaterial() const;
+ Q_INVOKABLE void addMaterial();
+ Q_INVOKABLE void duplicateMaterial();
+ Q_INVOKABLE bool isGroupCollapsed(int groupIdx) const;
+ Q_INVOKABLE void updateGroupCollapseState(int groupIdx, bool state);
+
+private:
+ void onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex,
+ int inNewIndex);
+
+
+ struct GroupInspectorControl {
+ QString groupTitle;
+ QVariantList controlElements;
+ QString groupInfo;
+
+ ~GroupInspectorControl() {
+ }
+ };
+
+ QVector<GroupInspectorControl> m_groupElements;
+ CInspectableBase *m_inspectableBase = nullptr;
+ GuideInspectable *m_guideInspectable = nullptr;
+
+ struct MaterialEntry
+ {
+ QString m_name;
+ QString m_relativePath;
+ };
+
+ struct MaterialDataEntry
+ {
+ QString m_name;
+ QString m_relativePath;
+ QMap<QString, QString> m_values;
+ QMap<QString, QMap<QString, QString>> m_textureValues;
+ };
+
+ std::vector<MaterialEntry> m_materials;
+ std::vector<MaterialDataEntry> m_matDatas;
+ std::vector<Q3DStudio::CFilePath> m_cachedMatDatas;
+ qt3dsdm::Qt3DSDMInstanceHandle m_refMaterial;
+
+ Q3DStudio::CUpdateableDocumentEditor m_UpdatableEditor;
+
+ bool m_suspendMaterialRename = false;
+
+ QPair<long, int> m_modifiedProperty;
+
+ qt3dsdm::SValue m_previouslyCommittedValue;
+
+ QHash<int, QHash<int, bool> > m_collapseMap;
+
+ QString getBasicMaterialString() const;
+ QString getAnimatableMaterialString() const;
+ QString getReferencedMaterialString() const;
+ QString getStandardMaterialString() const;
+ QString getDefaultMaterialString() const;
+ bool isInsideMaterialContainer() const;
+ bool isInsideMaterialContainer(CInspectableBase *inspectable) const;
+ bool isAnimatableMaterial() const;
+ bool isAnimatableMaterial(CInspectableBase *inspectable) const;
+ bool isBasicMaterial() const;
+ bool isBasicMaterial(CInspectableBase *inspectable) const;
+ void updateMaterialValues(const QStringList &values, int elementIndex,
+ bool updatingShaders = false);
+ qt3dsdm::Qt3DSDMInstanceHandle getReferenceMaterial(CInspectableBase *inspectable) const;
+ void updateShaderValues();
+ void updateMatDataValues();
+ void updatePropertyValue(InspectorControlBase *element) const;
+ void rebuildTree();
+ void refreshTree();
+ void updateAnimateToggleState(InspectorControlBase *inItem);
+ void updateControlledToggleState(InspectorControlBase *inItem) const;
+
+ QStringList materialTypeValues() const;
+ QStringList shaderValues() const;
+ QStringList matDataValues() const;
+ InspectorControlBase *createMaterialTypeItem(Qt3DSDMInspectable *inspectable, int groupIndex);
+ InspectorControlBase *createShaderItem(Qt3DSDMInspectable *inspectable, int groupIndex);
+ InspectorControlBase *createMatDataItem(Qt3DSDMInspectable *inspectable, int groupIndex);
+ InspectorControlBase *createItem(Qt3DSDMInspectable *inspectable,
+ Q3DStudio::Qt3DSDMInspectorRow *row, int groupIndex);
+ InspectorControlBase *createItem(Qt3DSDMInspectable *inspectable,
+ const qt3dsdm::SMetaDataPropertyInfo &metaProperty,
+ int groupIndex);
+
+ QVector<GroupInspectorControl> computeTree(CInspectableBase *inspectBase);
+ bool isTreeRebuildRequired(CInspectableBase *inspectBase);
+
+ GroupInspectorControl computeGroup(CInspectableBase* inspectBase,
+ int theIndex, bool disableAnimation = false,
+ bool isReference = false);
+ bool isGroupRebuildRequired(CInspectableBase *inspectable, int theIndex) const;
+
+ CClientDataModelBridge *getBridge() const;
+
+ static int handleToGuidePropIndex(int handle) { return handle - 1; }
+
+ VariantsGroupModel *m_variantsModel = nullptr;
+};
+
+#endif // INSPECTORCONTROLMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp
new file mode 100644
index 00000000..e4e0379f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp
@@ -0,0 +1,930 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "InspectorControlView.h"
+#include "Literals.h"
+#include "CColor.h"
+#include "Qt3DSDMValue.h"
+#include "StudioUtils.h"
+#include "InspectorControlModel.h"
+#include "StudioPreferences.h"
+#include "Core.h"
+#include "Doc.h"
+#include "IDocumentEditor.h"
+#include "ImageChooserModel.h"
+#include "ImageChooserView.h"
+#include "MeshChooserView.h"
+#include "TextureChooserView.h"
+#include "InspectableBase.h"
+#include "StudioApp.h"
+#include "ObjectListModel.h"
+#include "ObjectBrowserView.h"
+#include "IDirectoryWatchingSystem.h"
+#include "StandardExtensions.h"
+#include "FileChooserView.h"
+#include "IObjectReferenceHelper.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "StudioFullSystem.h"
+#include "ClientDataModelBridge.h"
+#include "MainFrm.h"
+#include "DataInputDlg.h"
+#include "Dialogs.h"
+#include "ProjectFile.h"
+#include "MaterialRefView.h"
+#include "BasicObjectsModel.h"
+#include "Qt3DSDMSlides.h"
+#include "VariantsGroupModel.h"
+#include "VariantTagDialog.h"
+#include "SlideView.h"
+#include "TimelineWidget.h"
+#include "SelectedValue.h"
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMSlides.h"
+#include "Qt3DSDMMaterialInspectable.h"
+#include "GuideInspectable.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtWidgets/qmenu.h>
+#include <QtWidgets/qdesktopwidget.h>
+#include <QtWidgets/qlistwidget.h>
+
+InspectorControlView::InspectorControlView(const QSize &preferredSize, QWidget *parent)
+ : QQuickWidget(parent),
+ TabNavigable(),
+ m_variantsGroupModel(new VariantsGroupModel(this)),
+ m_inspectorControlModel(new InspectorControlModel(m_variantsGroupModel, this)),
+ m_meshChooserView(new MeshChooserView(this)),
+ m_preferredSize(preferredSize)
+{
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &InspectorControlView::initialize);
+ auto dispatch = g_StudioApp.GetCore()->GetDispatch();
+ dispatch->AddPresentationChangeListener(this);
+ dispatch->AddDataModelListener(this);
+
+ connect(m_meshChooserView, &MeshChooserView::meshSelected, this,
+ [this] (int handle, int instance, const QString &name) {
+ if (name.startsWith(QLatin1Char('#'))) {
+ if (m_inspectorControlModel)
+ m_inspectorControlModel->setPropertyValue(instance, handle, name);
+ } else {
+ setPropertyValueFromFilename(instance, handle, name);
+ }
+ });
+}
+
+static bool isInList(const wchar_t **list, const Q3DStudio::CString &inStr)
+{
+ for (const wchar_t **item = list; item && *item; ++item) {
+ if (inStr.Compare(*item, Q3DStudio::CString::ENDOFSTRING, false))
+ return true;
+ }
+ return false;
+}
+
+void InspectorControlView::filterMaterials(std::vector<Q3DStudio::CFilePath> &materials)
+{
+ static const wchar_t *extensions[] = {
+ L"material",
+ L"shader",
+ nullptr
+ };
+ for (size_t i = 0; i < m_fileList.size(); ++i) {
+ if (isInList(extensions, m_fileList[i].GetExtension()))
+ materials.push_back(m_fileList[i]);
+ }
+}
+
+void InspectorControlView::filterMatDatas(std::vector<Q3DStudio::CFilePath> &matDatas)
+{
+ static const wchar_t *extensions[] = {
+ L"materialdef",
+ nullptr
+ };
+ for (size_t i = 0; i < m_fileList.size(); ++i) {
+ if (isInList(extensions, m_fileList[i].GetExtension()))
+ matDatas.push_back(m_fileList[i]);
+ }
+}
+
+void InspectorControlView::OnNewPresentation()
+{
+ auto core = g_StudioApp.GetCore();
+ auto sp = core->GetDoc()->GetStudioSystem()->GetFullSystem()->GetSignalProvider();
+ auto assetGraph = core->GetDoc()->GetAssetGraph();
+
+ m_connections.push_back(core->GetDispatch()->ConnectSelectionChange(
+ std::bind(&InspectorControlView::OnSelectionSet, this, std::placeholders::_1)));
+ m_connections.push_back(g_StudioApp.getDirectoryWatchingSystem().AddDirectory(
+ g_StudioApp.GetCore()->getProjectFile().getProjectPath(),
+ std::bind(&InspectorControlView::onFilesChanged, this, std::placeholders::_1)));
+ m_connections.push_back(sp->ConnectInstancePropertyValue(
+ std::bind(&InspectorControlView::onPropertyChanged, this, std::placeholders::_1,
+ std::placeholders::_2)));
+ m_connections.push_back(assetGraph->ConnectChildAdded(
+ std::bind(&InspectorControlView::onChildAdded, this, std::placeholders::_2)));
+ m_connections.push_back(assetGraph->ConnectChildRemoved(
+ std::bind(&InspectorControlView::onChildRemoved, this)));
+}
+
+void InspectorControlView::OnClosingPresentation()
+{
+ // Image chooser model needs to be deleted, because otherwise it'll try to update the model for
+ // the new presentation before subpresentations are resolved, corrupting the model.
+ // The model also has a connection to project file which needs to refreshed if project changes.
+ delete m_imageChooserView;
+ m_fileList.clear();
+ m_connections.clear();
+}
+
+void InspectorControlView::onFilesChanged(
+ const Q3DStudio::TFileModificationList &inFileModificationList)
+{
+ static const wchar_t *materialExtensions[] = {
+ L"material", L"shader", L"materialdef",
+ nullptr
+ };
+ static const wchar_t *fontExtensions[] = {
+ L"ttf", L"otf",
+ nullptr
+ };
+
+ bool updateFonts = false;
+ for (size_t idx = 0, end = inFileModificationList.size(); idx < end; ++idx) {
+ const Q3DStudio::SFileModificationRecord &record(inFileModificationList[idx]);
+ if (record.m_FileInfo.IsFile()) {
+ if (isInList(materialExtensions, record.m_File.GetExtension())) {
+ Q3DStudio::CFilePath relativePath(
+ Q3DStudio::CFilePath::GetRelativePathFromBase(
+ g_StudioApp.GetCore()->GetDoc()->GetDocumentDirectory(),
+ record.m_File));
+
+ if (record.m_ModificationType == Q3DStudio::FileModificationType::Created)
+ qt3dsdm::binary_sort_insert_unique(m_fileList, relativePath);
+ else if (record.m_ModificationType == Q3DStudio::FileModificationType::Destroyed)
+ qt3dsdm::binary_sort_erase(m_fileList, relativePath);
+ } else if (isInList(fontExtensions, record.m_File.GetExtension())) {
+ if (record.m_ModificationType == Q3DStudio::FileModificationType::Created
+ || record.m_ModificationType == Q3DStudio::FileModificationType::Destroyed) {
+ updateFonts = true;
+ }
+ } else if (record.m_ModificationType == Q3DStudio::FileModificationType::Modified
+ && record.m_File.toQString()
+ == g_StudioApp.GetCore()->getProjectFile().getProjectFilePath()) {
+ g_StudioApp.GetCore()->getProjectFile().loadSubpresentationsAndDatainputs(
+ g_StudioApp.m_subpresentations, g_StudioApp.m_dataInputDialogItems);
+ m_inspectorControlModel->refreshRenderables();
+ }
+ }
+ }
+ std::vector<Q3DStudio::CFilePath> materials;
+ filterMaterials(materials);
+ m_inspectorControlModel->setMaterials(materials);
+
+ std::vector<Q3DStudio::CFilePath> matDatas;
+ filterMatDatas(matDatas);
+ m_inspectorControlModel->setMatDatas(matDatas);
+
+ if (updateFonts) {
+ // The fonts list in doc is not necessarily yet updated, so do update async
+ QTimer::singleShot(0, this, [this]() {
+ m_inspectorControlModel->updateFontValues(nullptr);
+ });
+ }
+}
+
+InspectorControlView::~InspectorControlView()
+{
+ g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this);
+ delete m_dataInputChooserView;
+}
+
+QSize InspectorControlView::sizeHint() const
+{
+ return m_preferredSize;
+}
+
+void InspectorControlView::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+ QQuickWidget::mousePressEvent(event);
+}
+
+void InspectorControlView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_parentView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_inspectorModel"), m_inspectorControlModel);
+ rootContext()->setContextProperty(QStringLiteral("_variantsGroupModel"), m_variantsGroupModel);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_tabOrderHandler"), tabOrderHandler());
+ rootContext()->setContextProperty(QStringLiteral("_mouseHelper"), &m_mouseHelper);
+ rootContext()->setContextProperty(QStringLiteral("_utils"), &m_qmlUtils);
+ m_mouseHelper.setWidget(this);
+
+ qmlRegisterUncreatableType<qt3dsdm::DataModelDataType>(
+ "Qt3DStudio", 1, 0, "DataModelDataType",
+ QStringLiteral("DataModelDataType is an enum container"));
+ qmlRegisterUncreatableType<qt3dsdm::AdditionalMetaDataType>(
+ "Qt3DStudio", 1, 0, "AdditionalMetaDataType",
+ QStringLiteral("AdditionalMetaDataType is an enum container"));
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/InspectorControlView.qml")));
+}
+
+QAbstractItemModel *InspectorControlView::inspectorControlModel() const
+{
+ return m_inspectorControlModel;
+}
+
+QString InspectorControlView::titleText() const
+{
+ if (m_inspectableBase) {
+ Q3DStudio::CString theName = m_inspectableBase->getName();
+ if (theName == L"PathAnchorPoint")
+ return tr("Anchor Point");
+ else
+ return theName.toQString();
+ }
+ return tr("No Object Selected");
+}
+
+bool InspectorControlView::isRefMaterial(int instance) const
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ return bridge->IsReferencedMaterialInstance(instance);
+}
+
+QString InspectorControlView::noneString() const
+{
+ return ChooserModelBase::noneString();
+}
+
+bool InspectorControlView::canLinkProperty(int instance, int handle) const
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+
+ if (bridge->isInsideMaterialContainer(instance))
+ return false;
+
+ if (bridge->IsMaterialBaseInstance(instance)) // all material types are unlinkable
+ return false;
+
+ if (handle == bridge->GetSceneAsset().m_Eyeball.m_Property) // eyeball is unlinkable
+ return false;
+
+ return doc->GetDocumentReader().CanPropertyBeLinked(instance, handle);
+}
+
+bool InspectorControlView::canOpenInInspector(int instance, int handle) const
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::SValue value;
+ doc->GetPropertySystem()->GetInstancePropertyValue(instance, handle, value);
+ if (!value.empty() && value.getType() == qt3dsdm::DataModelDataType::Long4) {
+ qt3dsdm::SLong4 guid = qt3dsdm::get<qt3dsdm::SLong4>(value);
+ return guid.Valid();
+ }
+ return false;
+}
+
+void InspectorControlView::openInInspector()
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::SValue value;
+ doc->GetPropertySystem()->GetInstancePropertyValue(m_contextMenuInstance, m_contextMenuHandle,
+ value);
+ qt3dsdm::SLong4 guid = qt3dsdm::get<qt3dsdm::SLong4>(value);
+ const auto instance = bridge->GetInstanceByGUID(guid);
+ doc->SelectDataModelObject(instance);
+}
+
+void InspectorControlView::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ m_inspectorControlModel->notifyPropertyChanged(inInstance, inProperty);
+
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ // titleChanged implies icon change too, but that will only occur if inspectable type changes,
+ // which will invalidate the inspectable anyway, so in reality we are only interested in name
+ // property here
+ if (inProperty == bridge->GetNameProperty() && m_inspectableBase
+ && m_inspectableBase->isValid()) {
+ Q_EMIT titleChanged();
+ }
+}
+
+void InspectorControlView::onChildAdded(int inChild)
+{
+ // Changes to asset graph invalidate the object browser model, so close it if it is open
+ if (m_activeBrowser.isActive() && m_activeBrowser.m_browser == m_objectReferenceView)
+ m_activeBrowser.clear();
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ if (bridge->IsCustomMaterialInstance(inChild)) {
+ QVector<qt3dsdm::Qt3DSDMInstanceHandle> refMats;
+ doc->getSceneReferencedMaterials(doc->GetSceneInstance(), refMats);
+ for (auto &refMat : qAsConst(refMats)) {
+ if ((int)bridge->getMaterialReference(refMat) == inChild)
+ g_StudioApp.GetCore()->GetDispatch()->FireImmediateRefreshInstance(refMat);
+ }
+ }
+}
+
+void InspectorControlView::onChildRemoved()
+{
+ // Changes to asset graph invalidate the object browser model, so close it if it is open
+ if (m_activeBrowser.isActive() && m_activeBrowser.m_browser == m_objectReferenceView)
+ m_activeBrowser.clear();
+}
+
+QColor InspectorControlView::titleColor(int instance, int handle) const
+{
+ QColor ret = CStudioPreferences::textColor();
+ if (instance != 0) {
+ if (g_StudioApp.GetCore()->GetDoc()->GetDocumentReader()
+ .IsPropertyLinked(instance, handle)) {
+ ret = CStudioPreferences::masterColor();
+ }
+ }
+ return ret;
+}
+
+QString InspectorControlView::titleIcon() const
+{
+ if (m_inspectableBase)
+ return CStudioObjectTypes::GetNormalIconName(m_inspectableBase->getObjectType());
+ return {};
+}
+
+bool InspectorControlView::isEditable(int handle) const
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ if (doc->GetStudioSystem()->GetSlideSystem()->IsMasterSlide(doc->GetActiveSlide())
+ && doc->GetStudioSystem()->GetPropertySystem()->GetName(handle) == L"eyeball") {
+ return false;
+ }
+ return true;
+}
+
+void InspectorControlView::OnSelectionSet(Q3DStudio::SSelectedValue selectable)
+{
+ CInspectableBase *inspectable = createInspectableFromSelectable(selectable);
+
+ if (inspectable && !inspectable->isValid())
+ inspectable = nullptr;
+
+ setInspectable(inspectable);
+}
+
+CInspectableBase *InspectorControlView::createInspectableFromSelectable(
+ Q3DStudio::SSelectedValue selectable)
+{
+ using namespace Q3DStudio;
+
+ CInspectableBase *inspectableBase = nullptr;
+ if (!selectable.empty()) {
+ switch (selectable.getType()) {
+ case SelectedValueTypes::Slide: {
+ // TODO: seems like slides are not directly selectable, this should be removed.
+ auto selectableInstance = selectable.getData<SSlideInstanceWrapper>().m_Instance;
+ inspectableBase = new Qt3DSDMInspectable(selectableInstance);
+ } break;
+
+ case SelectedValueTypes::MultipleInstances:
+ case SelectedValueTypes::Instance: {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ // Note: Inspector doesn't support multiple selection
+ qt3dsdm::TInstanceHandleList selectedsInstances = selectable.GetSelectedInstances();
+ if (selectedsInstances.size() == 1) {
+ Qt3DSDMInstanceHandle selectedInstance = selectedsInstances[0];
+ if (doc->GetDocumentReader().IsInstance(selectedInstance)) {
+ auto *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::Qt3DSDMSlideHandle activeSlide = doc->GetActiveSlide();
+
+ // Scene or Component (when being edited)
+ if (selectedInstance == bridge->GetOwningComponentInstance(activeSlide)) {
+ Qt3DSDMInstanceHandle activeSlideInstance = doc->GetStudioSystem()
+ ->GetSlideSystem()->GetSlideInstance(activeSlide);
+ inspectableBase = new Qt3DSDMInspectable(selectedInstance,
+ activeSlideInstance);
+ } else if (bridge->IsMaterialBaseInstance(selectedInstance)) {
+ inspectableBase = new Qt3DSDMMaterialInspectable(selectedInstance);
+ } else {
+ inspectableBase = new Qt3DSDMInspectable(selectedInstance);
+ }
+ }
+ }
+ } break;
+
+ case SelectedValueTypes::Guide: {
+ qt3dsdm::Qt3DSDMGuideHandle guide = selectable.getData<qt3dsdm::Qt3DSDMGuideHandle>();
+ inspectableBase = new GuideInspectable(guide);
+ } break;
+
+ default:
+ break; // Ignore slide insertion and unknown selectable types
+ };
+ }
+
+ return inspectableBase;
+}
+
+void InspectorControlView::setInspectable(CInspectableBase *inInspectable)
+{
+ if (m_inspectableBase != inInspectable) {
+ m_activeBrowser.clear();
+ m_inspectableBase = inInspectable;
+ m_inspectorControlModel->setInspectable(inInspectable);
+
+ Q_EMIT titleChanged();
+
+ m_variantsGroupModel->refresh();
+ }
+}
+
+void InspectorControlView::showContextMenu(int x, int y, int handle, int instance)
+{
+ m_contextMenuInstance = instance;
+ m_contextMenuHandle = handle;
+
+ QMenu theContextMenu;
+
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+
+ if (canOpenInInspector(instance, handle)) {
+ auto action = theContextMenu.addAction(tr("Open in Inspector"));
+ connect(action, &QAction::triggered, this, &InspectorControlView::openInInspector);
+ }
+
+ if (canLinkProperty(instance, handle)) {
+ bool isLinked = doc->GetDocumentReader().IsPropertyLinked(instance, handle);
+ auto action = theContextMenu.addAction(isLinked ? tr("Unlink Property from Master Slide")
+ : tr("Link Property from Master Slide"));
+ connect(action, &QAction::triggered, this, &InspectorControlView::toggleMasterLink);
+ } else {
+ auto action = theContextMenu.addAction(tr("Unable to link from Master Slide"));
+ action->setEnabled(false);
+ }
+
+ theContextMenu.exec(mapToGlobal({x, y}));
+ m_contextMenuInstance = 0;
+ m_contextMenuHandle = 0;
+}
+
+// context menu for the variants tags
+void InspectorControlView::showTagContextMenu(int x, int y, const QString &group,
+ const QString &tag)
+{
+ QMenu theContextMenu;
+
+ auto actionRename = theContextMenu.addAction(QObject::tr("Rename Tag"));
+ connect(actionRename, &QAction::triggered, this, [&]() {
+ VariantTagDialog dlg(VariantTagDialog::RenameTag, group, tag);
+ if (dlg.exec() == QDialog::Accepted) {
+ g_StudioApp.GetCore()->getProjectFile().renameVariantTag(group, dlg.getNames().first,
+ dlg.getNames().second);
+ m_variantsGroupModel->refresh();
+
+ // refresh slide view so the tooltip show the renamed tag immediately, no need to
+ // refresh the timeline because each row gets the tags directly from the property which
+ // is always up to date.
+ g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants();
+ }
+ });
+
+ auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Tag"));
+ connect(actionDelete, &QAction::triggered, this, [&]() {
+ g_StudioApp.GetCore()->getProjectFile().deleteVariantTag(group, tag);
+ g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants();
+ g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants();
+ m_variantsGroupModel->refresh();
+ if (g_StudioApp.GetCore()->getProjectFile().variantsDef()[group].m_tags.size() == 0)
+ g_StudioApp.m_pMainWnd->updateActionFilterEnableState();
+ });
+
+ theContextMenu.exec(mapToGlobal({x, y}));
+}
+
+// context menu for the variants groups
+void InspectorControlView::showGroupContextMenu(int x, int y, const QString &group)
+{
+ QMenu theContextMenu;
+
+ ProjectFile &projectFile = g_StudioApp.GetCore()->getProjectFile();
+
+ auto actionRename = theContextMenu.addAction(QObject::tr("Rename Group"));
+ connect(actionRename, &QAction::triggered, this, [&]() {
+ VariantTagDialog dlg(VariantTagDialog::RenameGroup, {}, group);
+ if (dlg.exec() == QDialog::Accepted) {
+ projectFile.renameVariantGroup(dlg.getNames().first, dlg.getNames().second);
+ g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants();
+ m_variantsGroupModel->refresh();
+
+ // refresh slide view so the tooltip show the renamed group immediately, no need to
+ // refresh the timeline because each row gets the tags directly from the property which
+ // is always up to date.
+ g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants();
+ }
+ });
+
+ auto actionColor = theContextMenu.addAction(QObject::tr("Change Group Color"));
+ connect(actionColor, &QAction::triggered, this, [&]() {
+ const auto variantsDef = projectFile.variantsDef();
+ QColor newColor = this->showColorDialog(variantsDef[group].m_color);
+ projectFile.changeVariantGroupColor(group, newColor.name());
+ // no need to refresh variants in the timeline widget as it references the group color in
+ // the project file m_variants, and a redraw is triggered upon color selection dialog close.
+ g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants();
+ m_variantsGroupModel->refresh();
+ });
+
+ auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Group"));
+ connect(actionDelete, &QAction::triggered, this, [&]() {
+ projectFile.deleteVariantGroup(group);
+ g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants();
+ g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants();
+ g_StudioApp.m_pMainWnd->updateActionFilterEnableState();
+ m_variantsGroupModel->refresh();
+ });
+
+ theContextMenu.exec(mapToGlobal({x, y}));
+}
+
+void InspectorControlView::toggleMasterLink()
+{
+ Q3DStudio::ScopedDocumentEditor editor(*g_StudioApp.GetCore()->GetDoc(),
+ QObject::tr("Link Property"), __FILE__, __LINE__);
+ bool wasLinked = editor->IsPropertyLinked(m_contextMenuInstance, m_contextMenuHandle);
+
+ if (wasLinked)
+ editor->UnlinkProperty(m_contextMenuInstance, m_contextMenuHandle);
+ else
+ editor->LinkProperty(m_contextMenuInstance, m_contextMenuHandle);
+}
+
+void InspectorControlView::setPropertyValueFromFilename(long instance, int handle,
+ const QString &name)
+{
+ if (m_inspectorControlModel) {
+ QString value;
+ if (name != ChooserModelBase::noneString()) {
+ // Relativize the path to the presentation
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const QDir documentDir(doc->GetDocumentDirectory());
+ QString relativeName = documentDir.relativeFilePath(name);
+ value = relativeName;
+ }
+ m_inspectorControlModel->setPropertyValue(instance, handle, value);
+ }
+}
+
+QObject *InspectorControlView::showImageChooser(int handle, int instance, const QPoint &point)
+{
+ if (!m_imageChooserView) {
+ m_imageChooserView = new ImageChooserView(this);
+ connect(m_imageChooserView, &ImageChooserView::imageSelected, this,
+ [this] (int handle, int instance, const QString &imageName) {
+ // To avoid duplicate undo points when setting image property we can't rely
+ // on regular property duplication checks, as images are not directly stored as
+ // their paths. Also, there is no check for duplication on renderables.
+ if (m_imageChooserView->currentDataModelPath() != imageName) {
+ QString renderableId = g_StudioApp.getRenderableId(imageName);
+ if (renderableId.isEmpty()) {
+ setPropertyValueFromFilename(instance, handle, imageName);
+ } else {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(),
+ QObject::tr("Set Property"))
+ ->setInstanceImagePropertyValue(
+ instance, handle, Q3DStudio::CString::fromQString(renderableId));
+ if (m_inspectorControlModel)
+ m_inspectorControlModel->saveIfMaterial(instance);
+ }
+ }
+ });
+ }
+
+ m_imageChooserView->setHandle(handle);
+ m_imageChooserView->setInstance(instance);
+
+ CDialogs::showWidgetBrowser(this, m_imageChooserView, point);
+ m_activeBrowser.setData(m_imageChooserView, handle, instance);
+
+ return m_imageChooserView;
+}
+
+QObject *InspectorControlView::showFilesChooser(int handle, int instance, const QPoint &point)
+{
+ if (!m_fileChooserView) {
+ m_fileChooserView = new FileChooserView(this);
+ connect(m_fileChooserView, &FileChooserView::fileSelected, this,
+ [this] (int handle, int instance, const QString &fileName) {
+ setPropertyValueFromFilename(instance, handle, fileName);
+ });
+ }
+
+ m_fileChooserView->setHandle(handle);
+ m_fileChooserView->setInstance(instance);
+
+ CDialogs::showWidgetBrowser(this, m_fileChooserView, point);
+ m_activeBrowser.setData(m_fileChooserView, handle, instance);
+
+ return m_fileChooserView;
+}
+
+QObject *InspectorControlView::showMeshChooser(int handle, int instance, const QPoint &point)
+{
+ m_meshChooserView->setHandle(handle);
+ m_meshChooserView->setInstance(instance);
+
+ m_activeBrowser.setData(m_meshChooserView, handle, instance);
+ int numPrimitives = BasicObjectsModel::BasicMeshesModel().count();
+ bool combo = numPrimitives == m_meshChooserView->numMeshes(); // make a combobox size popup
+ int comboH = qMin(m_meshChooserView->numMeshes(), 15) // max popup height: 15 items
+ * CStudioPreferences::controlBaseHeight();
+
+ CDialogs::showWidgetBrowser(this, m_meshChooserView, point,
+ CDialogs::WidgetBrowserAlign::ComboBox,
+ combo ? QSize(CStudioPreferences::valueWidth(), comboH) : QSize());
+
+ return m_meshChooserView;
+}
+
+QObject *InspectorControlView::showTextureChooser(int handle, int instance, const QPoint &point)
+{
+ if (!m_textureChooserView) {
+ m_textureChooserView = new TextureChooserView(this);
+ connect(m_textureChooserView, &TextureChooserView::textureSelected, this,
+ [this] (int handle, int instance, const QString &fileName) {
+ if (m_textureChooserView->currentDataModelPath() != fileName) {
+ QString renderableId = g_StudioApp.getRenderableId(fileName);
+ if (renderableId.isEmpty())
+ setPropertyValueFromFilename(instance, handle, fileName);
+ else
+ m_inspectorControlModel->setPropertyValue(instance, handle, renderableId);
+ }
+ });
+ }
+
+ m_textureChooserView->setHandle(handle);
+ m_textureChooserView->setInstance(instance);
+
+ CDialogs::showWidgetBrowser(this, m_textureChooserView, point);
+ m_activeBrowser.setData(m_textureChooserView, handle, instance);
+
+ return m_textureChooserView;
+}
+
+QObject *InspectorControlView::showObjectReference(int handle, int instance, const QPoint &point)
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ // different base handle than current active root instance means that we have entered/exited
+ // component after the reference model had been created, and we need to recreate it
+ if (!m_objectReferenceModel
+ || (m_objectReferenceModel->baseHandle() != doc->GetActiveRootInstance())) {
+ if (m_objectReferenceModel)
+ delete m_objectReferenceModel;
+ m_objectReferenceModel = new ObjectListModel(g_StudioApp.GetCore(),
+ doc->GetActiveRootInstance(), this, true);
+ }
+ if (!m_objectReferenceView)
+ m_objectReferenceView = new ObjectBrowserView(this);
+ m_objectReferenceView->setModel(m_objectReferenceModel);
+
+ if (doc->GetStudioSystem()->GetClientDataModelBridge()
+ ->GetObjectType(instance) == OBJTYPE_ALIAS) {
+ QVector<EStudioObjectType> exclude;
+ exclude << OBJTYPE_ALIAS << OBJTYPE_BEHAVIOR << OBJTYPE_CUSTOMMATERIAL
+ << OBJTYPE_EFFECT << OBJTYPE_GUIDE << OBJTYPE_IMAGE << OBJTYPE_LAYER
+ << OBJTYPE_MATERIAL << OBJTYPE_REFERENCEDMATERIAL << OBJTYPE_SCENE;
+ m_objectReferenceModel->excludeObjectTypes(exclude);
+ } else {
+ m_objectReferenceModel->excludeObjectTypes(QVector<EStudioObjectType>());
+ }
+
+ disconnect(m_objectReferenceView, nullptr, nullptr, nullptr);
+
+ IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper();
+ if (objRefHelper) {
+ qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue(instance, handle);
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance = objRefHelper->Resolve(value, instance);
+ m_objectReferenceView->selectAndExpand(refInstance, instance);
+ }
+
+ CDialogs::showWidgetBrowser(this, m_objectReferenceView, point);
+ m_activeBrowser.setData(m_objectReferenceView, handle, instance);
+
+ connect(m_objectReferenceView, &ObjectBrowserView::selectionChanged,
+ this, [this, doc, handle, instance] {
+ auto selectedItem = m_objectReferenceView->selectedHandle();
+ qt3dsdm::SObjectRefType objRef = doc->GetDataModelObjectReferenceHelper()->GetAssetRefValue(
+ selectedItem, instance,
+ (CRelativePathTools::EPathType)(m_objectReferenceView->pathType()));
+ qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue(instance, handle);
+ if (!(value.getData<qt3dsdm::SObjectRefType>() == objRef)) {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Property"))
+ ->SetInstancePropertyValue(instance, handle, objRef);
+ }
+ });
+
+ return m_objectReferenceView;
+}
+
+QObject *InspectorControlView::showMaterialReference(int handle, int instance, const QPoint &point)
+{
+ // create the list widget
+ if (!m_matRefListWidget)
+ m_matRefListWidget = new MaterialRefView(this);
+
+ disconnect(m_matRefListWidget, &QListWidget::itemClicked, nullptr, nullptr);
+ disconnect(m_matRefListWidget, &QListWidget::itemDoubleClicked, nullptr, nullptr);
+
+ const QSize popupSize = m_matRefListWidget->refreshMaterials(instance, handle);
+ CDialogs::showWidgetBrowser(this, m_matRefListWidget, point,
+ CDialogs::WidgetBrowserAlign::ComboBox, popupSize);
+ m_activeBrowser.setData(m_matRefListWidget, handle, instance);
+
+ connect(m_matRefListWidget, &QListWidget::itemClicked, this,
+ [instance, handle](QListWidgetItem *item) {
+ auto selectedInstance = item->data(Qt::UserRole).toInt();
+ if (selectedInstance > 0) {
+ qt3dsdm::SValue value;
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ propertySystem->GetInstancePropertyValue(instance, handle, value);
+ auto refInstance = doc->GetDataModelObjectReferenceHelper()->Resolve(value, instance);
+ if (selectedInstance != refInstance) {
+ auto objRef = doc->GetDataModelObjectReferenceHelper()->GetAssetRefValue(
+ selectedInstance, instance, CRelativePathTools::EPATHTYPE_GUID);
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Property"))
+ ->SetInstancePropertyValue(instance, handle, objRef);
+ }
+ }
+ });
+ connect(m_matRefListWidget, &QListWidget::itemDoubleClicked, this, [this]() {
+ m_matRefListWidget->hide();
+ });
+
+ return m_matRefListWidget;
+}
+
+void InspectorControlView::showDataInputChooser(int handle, int instance, const QPoint &point)
+{
+ if (!m_dataInputChooserView) {
+ const QVector<EDataType> acceptedTypes;
+ m_dataInputChooserView = new DataInputSelectView(acceptedTypes, this);
+ connect(m_dataInputChooserView, &DataInputSelectView::dataInputChanged, this,
+ [this](int handle, int instance, const QString &controllerName) {
+ bool controlled =
+ controllerName == m_dataInputChooserView->getNoneString() ? false : true;
+ m_inspectorControlModel
+ ->setPropertyControllerInstance(
+ instance, handle,
+ Q3DStudio::CString::fromQString(controllerName), controlled);
+ m_inspectorControlModel->setPropertyControlled(instance, handle);
+ });
+ }
+ const auto propertySystem =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem();
+ const qt3dsdm::DataModelDataType::Value dataType
+ = propertySystem->GetDataType(handle);
+ // only add datainputs with matching type for this property
+ QVector<QPair<QString, int>> dataInputList;
+
+ for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems))
+ dataInputList.append({it->name, it->type});
+
+ m_dataInputChooserView->setMatchingTypes(CDataInputDlg::getAcceptedTypes(dataType));
+ m_dataInputChooserView->
+ setData(dataInputList,
+ m_inspectorControlModel->currentControllerValue(instance, handle),
+ handle, instance);
+ CDialogs::showWidgetBrowser(this, m_dataInputChooserView, point,
+ CDialogs::WidgetBrowserAlign::ToolButton);
+ m_activeBrowser.setData(m_dataInputChooserView, handle, instance);
+}
+
+QColor InspectorControlView::showColorDialog(const QColor &color, int instance, int handle)
+{
+ bool showAlpha = false;
+ if (instance && handle) {
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge();
+ showAlpha = bridge->getBGColorProperty(instance).GetHandleValue() == handle
+ || bridge->getTextColorProperty(instance).GetHandleValue() == handle;
+ }
+
+ m_currentColor = color;
+ CDialogs *dialogs = g_StudioApp.GetDialogs();
+ connect(dialogs, &CDialogs::onColorChanged,
+ this, &InspectorControlView::dialogCurrentColorChanged);
+ QColor currentColor = dialogs->displayColorDialog(color, showAlpha);
+ disconnect(dialogs, &CDialogs::onColorChanged,
+ this, &InspectorControlView::dialogCurrentColorChanged);
+ return currentColor;
+}
+
+bool InspectorControlView::toolTipsEnabled()
+{
+ return CStudioPreferences::ShouldShowTooltips();
+}
+
+// Converts a path that is relative to the current presentation to be relative to
+// the current project root
+QString InspectorControlView::convertPathToProjectRoot(const QString &presentationPath)
+{
+ QDir projDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+ QFileInfo presentationFile(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath());
+ QDir presentationDir(presentationFile.absolutePath());
+ QString absPath = presentationDir.absoluteFilePath(presentationPath);
+
+ return projDir.relativeFilePath(absPath);
+}
+
+void InspectorControlView::OnBeginDataModelNotifications()
+{
+}
+
+void InspectorControlView::OnEndDataModelNotifications()
+{
+ CInspectableBase *inspectable = m_inspectorControlModel->inspectable();
+ if (inspectable && !inspectable->isValid())
+ OnSelectionSet(Q3DStudio::SSelectedValue());
+ m_inspectorControlModel->refresh();
+
+ if (m_activeBrowser.isActive()) {
+ // Check if the instance/handle pair still has an active UI control. If not, close browser.
+ if (!m_inspectorControlModel->hasInstanceProperty(
+ m_activeBrowser.m_instance, m_activeBrowser.m_handle)) {
+ m_activeBrowser.clear();
+ } else {
+ // Update browser selection
+ if (m_activeBrowser.m_browser == m_imageChooserView) {
+ m_imageChooserView->updateSelection();
+ } else if (m_activeBrowser.m_browser == m_fileChooserView) {
+ m_fileChooserView->updateSelection();
+ } else if (m_activeBrowser.m_browser == m_meshChooserView) {
+ m_meshChooserView->updateSelection();
+ } else if (m_activeBrowser.m_browser == m_textureChooserView) {
+ m_textureChooserView->updateSelection();
+ } else if (m_activeBrowser.m_browser == m_objectReferenceView) {
+ IObjectReferenceHelper *objRefHelper
+ = g_StudioApp.GetCore()->GetDoc()->GetDataModelObjectReferenceHelper();
+ if (objRefHelper) {
+ qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue(
+ m_activeBrowser.m_instance, m_activeBrowser.m_handle);
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance
+ = objRefHelper->Resolve(value, m_activeBrowser.m_instance);
+ m_objectReferenceView->selectAndExpand(refInstance, m_activeBrowser.m_instance);
+ }
+ } else if (m_activeBrowser.m_browser == m_matRefListWidget) {
+ m_matRefListWidget->updateSelection();
+ } else if (m_activeBrowser.m_browser == m_dataInputChooserView) {
+ m_dataInputChooserView->setCurrentController(
+ m_inspectorControlModel->currentControllerValue(
+ m_dataInputChooserView->instance(),
+ m_dataInputChooserView->handle()));
+ }
+ }
+ }
+}
+
+void InspectorControlView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ m_inspectorControlModel->refresh();
+}
+
+void InspectorControlView::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, long inInstanceCount)
+{
+ m_inspectorControlModel->refresh();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h
new file mode 100644
index 00000000..75d07d73
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h
@@ -0,0 +1,186 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INSPECTORCONTROLVIEW_H
+#define INSPECTORCONTROLVIEW_H
+
+#include <QtQuickWidgets/qquickwidget.h>
+#include <QtCore/qpointer.h>
+#include "DispatchListeners.h"
+#include "Dispatch.h"
+#include "Qt3DSFileTools.h"
+#include "TabOrderHandler.h"
+#include "MouseHelper.h"
+#include "QmlUtils.h"
+#include "DataInputSelectView.h"
+
+class InspectorControlModel;
+class VariantsGroupModel;
+class CInspectableBase;
+class ImageChooserView;
+class DataInputSelectView;
+class ImageChooserModel;
+class MeshChooserView;
+class ObjectBrowserView;
+class ObjectListModel;
+class FileChooserView;
+class TextureChooserView;
+class MaterialRefView;
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+class InspectorControlView : public QQuickWidget,
+ public CPresentationChangeListener,
+ public IDataModelListener,
+ public TabNavigable
+{
+ Q_OBJECT
+ Q_PROPERTY(QString titleText READ titleText NOTIFY titleChanged FINAL)
+ Q_PROPERTY(QString titleIcon READ titleIcon NOTIFY titleChanged FINAL)
+
+public:
+ explicit InspectorControlView(const QSize &preferredSize, QWidget *parent = nullptr);
+ ~InspectorControlView() override;
+
+ void OnSelectionSet(Q3DStudio::SSelectedValue inValue);
+ QAbstractItemModel *inspectorControlModel() const;
+
+ QString titleText() const;
+ QString titleIcon() const;
+ VariantsGroupModel *variantsModel() const { return m_variantsGroupModel; }
+
+ Q_INVOKABLE QColor titleColor(int instance = 0, int handle = 0) const;
+ Q_INVOKABLE QColor showColorDialog(const QColor &color, int instance = 0, int handle = 0);
+ Q_INVOKABLE void showContextMenu(int x, int y, int handle, int instance);
+ Q_INVOKABLE void showTagContextMenu(int x, int y, const QString &group, const QString &tag);
+ Q_INVOKABLE void showDataInputChooser(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE void showGroupContextMenu(int x, int y, const QString &group);
+ Q_INVOKABLE QObject *showImageChooser(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE QObject *showFilesChooser(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE QObject *showMeshChooser(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE QObject *showObjectReference(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE QObject *showMaterialReference(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE QObject *showTextureChooser(int handle, int instance, const QPoint &point);
+ Q_INVOKABLE bool toolTipsEnabled();
+ Q_INVOKABLE bool isRefMaterial(int instance) const;
+ Q_INVOKABLE bool isEditable(int handle) const;
+ Q_INVOKABLE QString convertPathToProjectRoot(const QString &presentationPath);
+ Q_INVOKABLE QString noneString() const;
+
+ // IDataModelListener
+ void OnBeginDataModelNotifications() override;
+ void OnEndDataModelNotifications() override;
+ void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount) override;
+
+Q_SIGNALS:
+ void titleChanged();
+ void controlsChanged();
+ void imageSelected(const QString &name);
+ void dialogCurrentColorChanged(const QColor &newColor);
+
+public Q_SLOTS:
+ void toggleMasterLink();
+
+protected:
+ QSize sizeHint() const override;
+ void mousePressEvent(QMouseEvent *event) override;
+
+private:
+ void setInspectable(CInspectableBase *inInspectable);
+ void initialize();
+ void onFilesChanged(const Q3DStudio::TFileModificationList &inFileModificationList);
+ void OnNewPresentation() override;
+ void OnClosingPresentation() override;
+ void filterMaterials(std::vector<Q3DStudio::CFilePath> &materials);
+ void filterMatDatas(std::vector<Q3DStudio::CFilePath> &matDatas);
+ void setPropertyValueFromFilename(long instance, int handle, const QString &name);
+ CInspectableBase *createInspectableFromSelectable(Q3DStudio::SSelectedValue selectable);
+ bool canLinkProperty(int instance, int handle) const;
+ bool canOpenInInspector(int instance, int handle) const;
+ void openInInspector();
+ void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void onChildAdded(int inChild);
+ void onChildRemoved();
+
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_connections;
+ QColor m_backgroundColor;
+ VariantsGroupModel *m_variantsGroupModel = nullptr;
+ InspectorControlModel *m_inspectorControlModel = nullptr;
+ CInspectableBase *m_inspectableBase = nullptr;
+ QPointer<ImageChooserView> m_imageChooserView;
+ QPointer<MeshChooserView> m_meshChooserView;
+ QPointer<FileChooserView> m_fileChooserView;
+ QPointer<TextureChooserView> m_textureChooserView;
+ QPointer<ObjectBrowserView> m_objectReferenceView;
+ QPointer<MaterialRefView> m_matRefListWidget;
+ QPointer<ObjectListModel> m_objectReferenceModel;
+ QPointer<DataInputSelectView> m_dataInputChooserView;
+ std::vector<Q3DStudio::CFilePath> m_fileList;
+ MouseHelper m_mouseHelper;
+ QmlUtils m_qmlUtils;
+
+ int m_contextMenuInstance = 0;
+ int m_contextMenuHandle = 0;
+
+ QSize m_preferredSize;
+ QColor m_currentColor;
+
+ class ActiveBrowserData
+ {
+ public:
+ void setData(QWidget *browser, int handle, int instance)
+ {
+ m_browser = browser;
+ m_handle = handle;
+ m_instance = instance;
+ }
+ void clear()
+ {
+ if (isActive())
+ m_browser->close();
+ m_browser.clear();
+ m_handle = -1;
+ m_instance = -1;
+ }
+ bool isActive() const
+ {
+ return !m_browser.isNull() && m_browser->isVisible();
+ }
+
+ QPointer<QWidget> m_browser = nullptr;
+ int m_handle = -1;
+ int m_instance = -1;
+ };
+
+ ActiveBrowserData m_activeBrowser;
+};
+
+#endif // INSPECTORCONTROLVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml
new file mode 100644
index 00000000..c291de7b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml
@@ -0,0 +1,1289 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.8
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Extras 1.4
+
+import Qt3DStudio 1.0
+import "../controls"
+import "../Action"
+
+Rectangle {
+ id: root
+ color: _backgroundColor
+
+ Connections {
+ target: _inspectorModel
+ onModelAboutToBeReset: {
+ _tabOrderHandler.clear();
+ inspectorToolbar.model = null;
+ if (_inspectorModel.isDefaultMaterial())
+ inspectorToolbar.model = defaultMaterialToolbarModel;
+ else if (_inspectorModel.isMaterial())
+ inspectorToolbar.model = materialToolbarModel;
+ inspectorToolbar.visible = inspectorToolbar.model !== null;
+ }
+ }
+
+ MouseArea {
+ anchors.fill: controlArea
+ onPressed: {
+ mouse.accepted = false
+ focusEater.forceActiveFocus();
+ }
+ }
+
+ ColumnLayout {
+ id: controlArea
+ anchors.fill: parent
+ anchors.topMargin: 4
+ spacing: 8
+
+ Item {
+ id: focusEater
+ // Used to eat keyboard focus when user clicks outside any property control
+ }
+
+ ListModel {
+ id: materialToolbarModel
+
+ ListElement {
+ image: "add.png"
+ name: qsTr("New")
+ inUse: true
+ }
+
+ ListElement {
+ image: "add.png"
+ name: qsTr("Duplicate")
+ inUse: true
+ }
+
+ property var actions: [
+ function(){ _inspectorModel.addMaterial(); },
+ function(){ _inspectorModel.duplicateMaterial(); }
+ ]
+ }
+
+ ListModel {
+ id: defaultMaterialToolbarModel
+
+ ListElement {
+ image: "add.png"
+ name: qsTr("New")
+ inUse: true
+ }
+
+ ListElement {
+ image: "add-disabled.png"
+ name: qsTr("Duplicate")
+ inUse: false
+ }
+
+ property var actions: [
+ function(){ _inspectorModel.addMaterial(); }
+ ]
+ }
+
+ ListView {
+ id: inspectorToolbar
+ model: null
+ visible: false
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: 32
+ orientation: ListView.Horizontal
+
+ spacing: 4
+
+ delegate: ToolButton {
+ id: control
+ enabled: inUse
+
+ onClicked: {
+ inspectorToolbar.model.actions[index]();
+ }
+
+ background: Rectangle {
+ color: control.pressed ? _selectionColor : (hovered ? _studioColor1 : "transparent")
+ border.color: _studioColor1
+ }
+
+ contentItem: RowLayout {
+ Image {
+ source: _resDir + image
+ }
+ StyledLabel {
+ text: name
+ Layout.preferredWidth: -1
+ color: control.enabled ? _textColor : _disabledColor
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ height: _controlBaseHeight + 8
+ Layout.leftMargin: 4
+
+ Image {
+ id: headerImage
+ source: _parentView.titleIcon !== "" ? _resDir + _parentView.titleIcon : ""
+ }
+
+ StyledLabel {
+ text: _parentView.titleText
+ color: _parentView.titleColor()
+ }
+ }
+
+ ListView {
+ id: groupElements
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.bottomMargin: 10
+ spacing: 4
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {
+ visible: size < 1.0
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ z: -10
+ onPressed: {
+ mouse.accepted = false
+ focusEater.forceActiveFocus();
+ }
+ }
+
+ model: _inspectorModel
+ delegate: Rectangle {
+ id: delegateItem
+
+ property int indexOfThisDelegate: index
+
+ width: parent.width
+ height: items.height
+ color: "transparent";
+ ListView.delayRemove: true
+
+ readonly property var values: model.values
+
+ Column {
+ id: items
+
+ x: 10
+ width: parent.width - x
+ spacing: 4
+
+ Rectangle { // group header
+ x: -10
+ width: delegateItem.width
+ height: 25
+ color: _inspectorGroupHeaderColor
+
+ StyledLabel {
+ x: 30
+ text: model.title
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Image {
+ id: collapseButton
+ x: 10
+ anchors.verticalCenter: parent.verticalCenter
+ source: {
+ _resDir + (groupItems.visible ? "arrow_down.png" : "arrow.png")
+ }
+ }
+
+ MouseArea {
+ id: collapseButtonMouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (mouse.button === Qt.LeftButton) {
+ groupItems.visible = !groupItems.visible;
+ _inspectorModel.updateGroupCollapseState(indexOfThisDelegate,
+ !groupItems.visible)
+ }
+ }
+ }
+ }
+
+ Column { // properties in a group
+ spacing: 4
+ id: groupItems
+
+ visible: !_inspectorModel.isGroupCollapsed(indexOfThisDelegate)
+
+ Repeater {
+ model: delegateItem.values
+
+ onItemAdded: {
+ if (index === 0)
+ _tabOrderHandler.clearGroup(indexOfThisDelegate);
+ if (item.loadedItem.tabItem1 !== undefined) {
+ _tabOrderHandler.addItem(indexOfThisDelegate,
+ item.loadedItem.tabItem1)
+ if (item.loadedItem.tabItem2 !== undefined) {
+ _tabOrderHandler.addItem(
+ indexOfThisDelegate,
+ item.loadedItem.tabItem2)
+ if (item.loadedItem.tabItem3 !== undefined) {
+ _tabOrderHandler.addItem(
+ indexOfThisDelegate,
+ item.loadedItem.tabItem3)
+ }
+ }
+ }
+ }
+
+ RowLayout { // a property row
+ id: groupDelegateItem
+ spacing: 0
+ enabled: _parentView.isEditable(modelData.handle)
+
+ property alias loadedItem: loader.item
+
+ function showContextMenu(coords) {
+ _parentView.showContextMenu(
+ coords.x, coords.y,
+ model.modelData.handle,
+ model.modelData.instance);
+ // Refresh text; title color is wrong after this
+ propertyRow.color = _parentView.titleColor(
+ modelData.instance, modelData.handle);
+ }
+
+ ColumnLayout { // Property row and datainput control
+ Layout.alignment: Qt.AlignTop
+ visible: modelData.title !== "variants"
+ spacing: 0
+ RowLayout { // Property row
+ Layout.alignment: Qt.AlignLeft
+ Rectangle { // Property animation control button
+ width: 16
+ height: 16
+ color: animateButtonMouseArea.containsMouse
+ ? _studioColor1 : _backgroundColor
+
+ Image {
+ id: animatedPropertyButton
+ visible: model.modelData.animatable
+
+ property bool animated: model.modelData.animated
+
+ anchors.fill: parent
+ fillMode: Image.Pad
+
+ source: {
+ _resDir + (animated
+ ? "Inspector-AnimateToggle-Active.png"
+ : "Inspector-AnimateToggle-Normal.png")
+ }
+
+ MouseArea {
+ id: animateButtonMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ hoverEnabled: true
+ onClicked: {
+ if (mouse.button === Qt.LeftButton) {
+ _inspectorModel.setPropertyAnimated(
+ model.modelData.instance,
+ model.modelData.handle,
+ !model.modelData.animated)
+ } else {
+ const coords = mapToItem(root,
+ mouse.x,
+ mouse.y);
+ groupDelegateItem.showContextMenu(coords);
+ }
+ }
+ }
+ StyledTooltip {
+ text: qsTr("Enable animation")
+ enabled: animateButtonMouseArea.containsMouse
+ }
+ }
+ }
+
+ Rectangle { // Datainput control button
+ width: 16
+ height: 16
+ color: dataInputButtonMouseArea.containsMouse
+ ? _studioColor1 : _backgroundColor
+
+ Image {
+ id: ctrldPropButton
+
+ property bool controlled: model.modelData.controlled
+ visible: model.modelData.controllable
+ anchors.fill: parent
+ fillMode: Image.Pad
+
+ source: {
+ _resDir + (controlled
+ ? "Objects-DataInput-Active.png"
+ : "Objects-DataInput-Inactive.png")
+ }
+
+ MouseArea {
+ id: dataInputButtonMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ hoverEnabled: true
+ onClicked: {
+ _parentView.showDataInputChooser(
+ model.modelData.handle,
+ model.modelData.instance,
+ mapToGlobal(
+ ctrldPropButton.x
+ + ctrldPropButton.width,
+ ctrldPropButton.y
+ + ctrldPropButton.height));
+
+ }
+ }
+
+ StyledTooltip {
+ text: model.modelData.controlled
+ ? qsTr("Data Input controller:\n")
+ + model.modelData.controller
+ : qsTr("Set Data Input controller")
+ enabled: dataInputButtonMouseArea.containsMouse
+ }
+ }
+ }
+
+ StyledLabel { // Property label
+ id: propertyRow
+
+ readonly property var modelData: model.modelData
+ text: model.modelData.title
+ // Color picked from DataInput icon
+ color: model.modelData.controlled?
+ _dataInputColor
+ : _parentView.titleColor(modelData.instance,
+ modelData.handle)
+
+ Layout.alignment: Qt.AlignTop
+
+ MouseArea {
+ id: propertyLabelMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ hoverEnabled: true
+ onClicked: {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ groupDelegateItem.showContextMenu(coords);
+ }
+ }
+ StyledTooltip {
+ id: valueToolTip
+ text: modelData.toolTip
+ enabled: propertyLabelMouseArea.containsMouse
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: loader
+ readonly property var modelData: propertyRow.modelData
+ enabled: modelData.enabled
+ opacity: enabled ? 1 : .5
+ Layout.alignment: Qt.AlignTop
+ sourceComponent: {
+ if (modelData.title === "variants")
+ return variantTagsComponent;
+
+ const dataType = modelData.dataType;
+ switch (dataType) {
+ case DataModelDataType.Long:
+ if (modelData.propertyType ===
+ AdditionalMetaDataType.ShadowMapResolution) {
+ return shadowResolutionComponent;
+ }
+ if (modelData.propertyType === AdditionalMetaDataType.Range)
+ return intSliderComponent;
+ console.warn("KDAB_TODO: implement handler for type \"Long\", property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.Long4:
+ if (modelData.propertyType === AdditionalMetaDataType.Image)
+ return imageChooser;
+ console.warn("KDAB_TODO: implement handler for type \"long4\" property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.ObjectRef:
+ if (modelData.propertyType === AdditionalMetaDataType.ObjectRef) {
+ return _parentView.isRefMaterial(modelData.instance)
+ ? materialReference
+ : objectReference;
+ }
+ console.warn("KDAB_TODO: implement handler for type: \"objectref\" property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.StringOrInt:
+ //TODO: Maybe do some further check if the right combo is used
+ if (modelData.propertyType === AdditionalMetaDataType.StringList)
+ return slideSelectionDropDown;
+ console.warn("KDAB_TODO: (String) implement handler for type \"stringOrInt\" property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.String:
+ if (modelData.propertyType === AdditionalMetaDataType.Import)
+ return fileChooser;
+ if (modelData.propertyType === AdditionalMetaDataType.StringList)
+ return comboDropDown;
+ if (modelData.propertyType === AdditionalMetaDataType.Renderable)
+ return renderableDropDown;
+ if (modelData.propertyType === AdditionalMetaDataType.Mesh)
+ return meshChooser;
+ if (modelData.propertyType === AdditionalMetaDataType.MultiLine)
+ return multiLine;
+ if (modelData.propertyType === AdditionalMetaDataType.Font)
+ return fontDropDown;
+ if (modelData.propertyType === AdditionalMetaDataType.Texture)
+ return textureChooser;
+ if (modelData.propertyType === AdditionalMetaDataType.String)
+ return editLine;
+ if (modelData.propertyType === AdditionalMetaDataType.None)
+ return null;
+ console.warn("KDAB_TODO: (String) implement handler for type \"string\" property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.Bool:
+ return checkBox;
+ case DataModelDataType.Float:
+ if (modelData.propertyType === AdditionalMetaDataType.None)
+ return valueComponent;
+ if (modelData.propertyType === AdditionalMetaDataType.Range)
+ return sliderComponent;
+ if (modelData.propertyType === AdditionalMetaDataType.FontSize)
+ return fontSizeComponent;
+ console.warn("KDAB_TODO: implement handler for type\"float\" property:",
+ modelData.propertyType);
+ return null;
+ case DataModelDataType.Float2:
+ if (modelData.propertyType === AdditionalMetaDataType.None)
+ return xyPropertyComponent;
+ console.warn("TODO: implement handler for type:\"float2\" property:",
+ modelData.propertyType, "text ",
+ model.modelData.title);
+ return null;
+ case DataModelDataType.Float3:
+ if (modelData.propertyType === AdditionalMetaDataType.Rotation)
+ return xyzPropertyComponent;
+ if (modelData.propertyType === AdditionalMetaDataType.None)
+ return xyzPropertyComponent;
+ console.warn("KDAB_TODO: implement handler for type:\"float3\" property:",
+ modelData.propertyType, "text ",
+ model.modelData.title);
+ return null;
+ case DataModelDataType.Float4:
+ if (modelData.propertyType === AdditionalMetaDataType.Color)
+ return colorBox;
+ return null;
+ case DataModelDataType.StringRef:
+ if (modelData.propertyType === AdditionalMetaDataType.None)
+ return materialTypeDropDown;
+ if (modelData.propertyType === AdditionalMetaDataType.Renderable)
+ return shaderDropDown;
+ if (modelData.propertyType === AdditionalMetaDataType.ObjectRef)
+ return matDataDropDown;
+ console.warn("KDAB_TODO: implement handler for type:\"StringRef\" text ",
+ model.modelData.title);
+ return null;
+ default:
+ console.warn("KDAB_TODO: implement handler for type",
+ dataType, "property",
+ modelData.propertyType, "text ",
+ model.modelData.title);
+ }
+ return null;
+ }
+ }
+ }
+ }
+ }
+
+ Column {
+ visible: model.info.length > 0
+ StyledLabel {
+ width: groupElements.width
+ horizontalAlignment: Text.AlignHCenter
+ text: model.info;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: editLine
+
+ StyledTextField {
+ id: textArea
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant value: parent.modelData.value
+ property Item tabItem1: this
+ width: _valueWidth
+ height: _controlBaseHeight
+ horizontalAlignment: TextInput.AlignLeft
+ verticalAlignment: TextInput.AlignVCenter
+
+ // Don't just bind text to value, since changing text explicitly in onEditingFinished
+ // would break binding
+ onValueChanged: text = value
+
+ onTextChanged: _inspectorModel.setPropertyValue(instance, handle, text, false)
+
+ onEditingFinished: {
+ _inspectorModel.setPropertyValue(instance, handle, text, true);
+ // Ensure we update the text to current value also in cases where making name
+ // unique results in no change to model value
+ text = value;
+ }
+ }
+ }
+
+ Component {
+ id: multiLine
+
+ HandlerBaseMultilineText {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ width: _valueWidth
+ height: _controlBaseHeight * 3
+ value: parent.modelData.value
+
+ onTextChanged: _inspectorModel.setPropertyValue(instance, handle, value, false)
+ onEditingFinished: _inspectorModel.setPropertyValue(instance, handle, value, true)
+ }
+ }
+
+ Component {
+ id: meshChooser
+ HandlerFilesChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: parent.modelData.value
+ onShowBrowser: {
+ activeBrowser = _parentView.showMeshChooser(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: imageChooser
+ HandlerFilesChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: {
+ var renderableId = _inspectorModel.renderableId(parent.modelData.value);
+ renderableId === "" ? parent.modelData.value : renderableId;
+ }
+ onShowBrowser: {
+ activeBrowser = _parentView.showImageChooser(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: fileChooser
+ HandlerFilesChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: {
+ parent.modelData.value === "" ? _parentView.noneString()
+ : _parentView.convertPathToProjectRoot(
+ parent.modelData.value)
+ }
+ onShowBrowser: {
+ activeBrowser = _parentView.showFilesChooser(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: textureChooser
+ HandlerFilesChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: parent.modelData.value
+ onShowBrowser: {
+ activeBrowser = _parentView.showTextureChooser(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: xyzPropertyComponent
+
+ RowLayout {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ property alias tabItem1: xyzHandler.tabItem1
+ property alias tabItem2: xyzHandler.tabItem2
+ property alias tabItem3: xyzHandler.tabItem3
+ spacing: 0
+
+ onValuesChanged: {
+ // FloatTextField can set its text internally, thus breaking the binding, so
+ // let's set the text value explicitly each time value changes
+ xyzHandler.valueX = Number(values[0]).toFixed(xyzHandler.numberOfDecimal);
+ xyzHandler.valueY = Number(values[1]).toFixed(xyzHandler.numberOfDecimal);
+ xyzHandler.valueZ = Number(values[2]).toFixed(xyzHandler.numberOfDecimal);
+ }
+
+ HandlerPropertyBaseXYZ {
+ id: xyzHandler
+ valueX: Number(values[0]).toFixed(numberOfDecimal)
+ valueY: Number(values[1]).toFixed(numberOfDecimal)
+ valueZ: Number(values[2]).toFixed(numberOfDecimal)
+ onEditingFinished: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Qt.vector3d(valueX, valueY, valueZ), true);
+ }
+ onPreviewValueChanged: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Qt.vector3d(valueX, valueY, valueZ), false);
+ }
+ }
+ }
+ }
+
+ Component {
+ id: xyPropertyComponent
+
+ RowLayout {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ property alias tabItem1: xyHandler.tabItem1
+ property alias tabItem2: xyHandler.tabItem2
+ spacing: 0
+
+ onValuesChanged: {
+ // FloatTextField can set its text internally, thus breaking the binding, so
+ // let's set the text value explicitly each time value changes
+ xyHandler.valueX = Number(values[0]).toFixed(xyHandler.numberOfDecimal);
+ xyHandler.valueY = Number(values[1]).toFixed(xyHandler.numberOfDecimal);
+ }
+
+ HandlerPropertyBaseXY {
+ id: xyHandler
+ valueX: Number(values[0]).toFixed(numberOfDecimal)
+ valueY: Number(values[1]).toFixed(numberOfDecimal)
+ onEditingFinished: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Qt.vector2d(valueX, valueY), true);
+ }
+ onPreviewValueChanged: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Qt.vector2d(valueX, valueY), false);
+ }
+ }
+ }
+ }
+
+ Component {
+ id: valueComponent
+
+ RowLayout {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property real value: Number(parent.modelData.value).toFixed(floatField.decimalValue)
+ property Item tabItem1: floatField
+
+ onValueChanged: {
+ // FloatTextField can set its text internally, thus breaking the binding, so
+ // let's set the text value explicitly each time value changes
+ floatField.text = Number(value).toFixed(floatField.decimalValue);
+ }
+
+ spacing: 0
+
+ FloatTextField {
+ id: floatField
+ text: Number(parent.value).toFixed(decimalValue)
+ implicitHeight: _controlBaseHeight
+ implicitWidth: _valueWidth / 3
+
+ onPreviewValueChanged: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Number(text), false);
+ }
+
+ onEditingFinished: {
+ _inspectorModel.setPropertyValue(parent.instance, parent.handle,
+ Number(text), true);
+ }
+ }
+ }
+ }
+
+ Component {
+ id: sliderComponent
+
+ HandlerPropertyBaseSlider {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+
+ value: parent.modelData.value
+ sliderMin: values[0]
+ sliderMax: values[1]
+ sliderDecimals: values[2]
+
+ onCommitValue: _inspectorModel.setPropertyValue(instance, handle, desiredValue, true)
+ onPreviewValue: _inspectorModel.setPropertyValue(instance, handle, desiredValue, false)
+ }
+ }
+
+ Component {
+ id: comboDropDown
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var values: parent.modelData.values
+ property var value: parent.modelData.value
+
+ model: values
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ currentIndex = find(value)
+ }
+ onCurrentIndexChanged: {
+ var newValue = textAt(currentIndex)
+ if (value !== newValue && currentIndex !== -1)
+ _inspectorModel.setPropertyValue(instance, handle, newValue)
+ }
+ onValueChanged: {
+ currentIndex = find(value)
+ }
+ }
+ }
+
+ Component {
+ id: fontDropDown
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var values: parent.modelData.values
+ property var value: parent.modelData.value
+ property bool blockIndexChange: false
+
+ model: values
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ currentIndex = find(value)
+ }
+ onCurrentIndexChanged: {
+ var newValue = textAt(currentIndex)
+ if (!blockIndexChange && value !== newValue && currentIndex !== -1)
+ _inspectorModel.setPropertyValue(instance, handle, newValue)
+ }
+ onValueChanged: {
+ var newNewIndex = find(value);
+ if (!blockIndexChange || newNewIndex > 0)
+ currentIndex = newNewIndex;
+ blockIndexChange = false;
+ }
+ onValuesChanged : {
+ // Changing the values list will reset the currentIndex to zero, so block setting
+ // the actual font. We'll get the proper index right after.
+ if (currentIndex > 0)
+ blockIndexChange = true;
+ }
+ }
+ }
+
+ Component {
+ id: slideSelectionDropDown
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var values: parent.modelData.values
+ property var value: parent.modelData.value
+
+ model: values
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ var newIndex = find(value)
+ if (newIndex === -1)
+ newIndex = find(value + "|separator")
+ currentIndex = newIndex
+ }
+ onCurrentIndexChanged: {
+ var newValue = textAt(currentIndex).replace("|separator", "")
+ if (value !== newValue && currentIndex !== -1) {
+ _inspectorModel.setSlideSelection(instance, handle,
+ currentIndex, values)
+ }
+ }
+ onValueChanged: {
+ var newIndex = find(value)
+ if (newIndex === -1)
+ newIndex = find(value + "|separator")
+ currentIndex = newIndex
+ }
+ }
+ }
+
+ Component {
+ id: materialTypeDropDown
+
+ MaterialDropDown {
+ callback: _inspectorModel.setMaterialTypeValue
+ }
+ }
+
+ Component {
+ id: shaderDropDown
+
+ MaterialDropDown {
+ callback: _inspectorModel.setShaderValue
+ }
+ }
+
+ Component {
+ id: matDataDropDown
+
+ MaterialDropDown {
+ callback: _inspectorModel.setMatDataValue
+ }
+ }
+
+ Component {
+ id: renderableDropDown
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var values: parent.modelData.values
+ property var value: parent.modelData.value
+ model: values
+
+ showArrow: enabled
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ // Disable for non-layer
+ enabled = _inspectorModel.isLayer(instance);
+ currentIndex = find(value);
+ }
+
+ onCurrentIndexChanged: {
+ var newValue = textAt(currentIndex)
+ if (value !== newValue && currentIndex !== -1)
+ _inspectorModel.setRenderableValue(instance, handle, newValue)
+ }
+ onValueChanged: {
+ currentIndex = find(value)
+ }
+ }
+ }
+
+ Component {
+ id: checkBox
+
+ Item {
+ id: checkboxItem
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property bool checked: parent.modelData.value
+
+ width: 16
+ height: _controlBaseHeight
+ Image {
+ anchors.fill: parent
+ fillMode: Image.Pad
+ source: (_resDir + (checked ? "checkbox-checked.png" : "checkbox-unchecked.png"))
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: _inspectorModel.setPropertyValue(checkboxItem.instance,
+ checkboxItem.handle,
+ !checkboxItem.checked)
+ }
+ }
+ }
+ }
+
+ Component {
+ id: colorBox
+
+ HandlerGenericBaseColor {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+
+ color: parent.modelData.value
+ onColorSelected: _inspectorModel.setPropertyValue(instance, handle, selectedColor);
+ onPreviewColorSelected: _inspectorModel.setPropertyValue(instance, handle,
+ selectedColor, false);
+ }
+ }
+
+ Component {
+ id: objectReference
+
+ HandlerGenericChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: parent.modelData.value
+ onShowBrowser: {
+ activeBrowser = _parentView.showObjectReference(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: materialReference
+
+ HandlerGenericChooser {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: parent.modelData.value
+ onShowBrowser: {
+ activeBrowser = _parentView.showMaterialReference(handle, instance,
+ mapToGlobal(width, 0))
+ }
+ }
+ }
+
+ Component {
+ id: intSliderComponent
+
+ HandlerPropertyBaseSlider {
+ intSlider: true;
+ property int intValue: Math.round(desiredValue)
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property variant values: parent.modelData.values
+ value: parent.modelData.value
+ sliderMin: values[0]
+ sliderMax: values[1]
+
+ onCommitValue: _inspectorModel.setPropertyValue(instance, handle, intValue, true)
+ onPreviewValue: _inspectorModel.setPropertyValue(instance, handle, intValue, false)
+ }
+ }
+
+ Component {
+ id: fontSizeComponent
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property real value: parent.modelData.value
+
+ editable: true
+ property bool ready: false
+
+ validator: IntValidator {
+ bottom: 1
+ }
+
+ model: ["8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28",
+ "36", "48", "72", "96", "120", "160", "200"]
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ editText = value;
+ ready = true;
+ }
+
+ onValueChanged: {
+ if (ready && !isNaN(value))
+ editText = value;
+ }
+
+ onEditTextChanged: {
+ if (ready) {
+ var newvalue = parseInt(editText);
+ _inspectorModel.setPropertyValue(instance, handle, newvalue, false);
+ }
+ }
+
+ onActiveFocusChanged: {
+ if (!activeFocus) {
+ var newvalue = parseInt(editText);
+ _inspectorModel.setPropertyValue(instance, handle, newvalue);
+ }
+ }
+
+ }
+ }
+
+ Component {
+ id: shadowResolutionComponent
+
+ StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var value: parent.modelData.value
+ property int newValue
+
+ model: ["8", "9", "10", "11"]
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ currentIndex = find(value)
+ }
+
+ onCurrentIndexChanged: {
+ newValue = parseInt(textAt(currentIndex))
+ if (value !== newValue && currentIndex !== -1)
+ _inspectorModel.setPropertyValue(instance, handle, newValue)
+ }
+
+ onValueChanged: {
+ currentIndex = find(value)
+ }
+ }
+ }
+
+ Component {
+ id: variantTagsComponent
+
+ Column {
+ width: root.width - 10
+ spacing: 10
+
+ Row {
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ spacing: 5
+
+ ToolButton {
+ id: importButton
+ text: qsTr("Import...")
+ font.pixelSize: _fontSize
+ width: 70
+ height: 20
+
+ onClicked: {
+ _variantsGroupModel.importVariants()
+ }
+ }
+
+ ToolButton {
+ id: exportButton
+ text: qsTr("Export...")
+ font.pixelSize: _fontSize
+ width: 70
+ height: 20
+ enabled: !_variantsGroupModel.variantsEmpty
+
+ onClicked: {
+ _variantsGroupModel.exportVariants()
+ }
+ }
+ }
+
+ Text {
+ text: qsTr("There are no variant tags yet. Click [+ Group] to add a new tags group and start adding tags.")
+ font.pixelSize: _fontSize
+ color: _textColor
+ visible: _variantsGroupModel.variantsEmpty
+ width: parent.width
+ wrapMode: Text.WordWrap
+ rightPadding: 5
+ }
+
+ Repeater {
+ id: tagsRepeater
+ model: _variantsGroupModel
+ property int maxGroupLabelWidth;
+
+ onItemAdded: {
+ // make all group labels have equal width as the widest one
+ if (index == 0)
+ maxGroupLabelWidth = 20; // min group label width
+
+ if (item.groupLabelWidth > maxGroupLabelWidth) {
+ maxGroupLabelWidth = item.groupLabelWidth;
+
+ if (maxGroupLabelWidth > 150) // max group label width
+ maxGroupLabelWidth = 150;
+ }
+ }
+
+ Row {
+ id: variantTagsRow
+ spacing: 5
+
+ readonly property var tagsModel: model.tags
+ readonly property var groupModel: model
+ readonly property int groupLabelWidth: tLabel.implicitWidth + 10
+
+ Text {
+ id: tLabel
+ text: model.group
+ color: model.color
+ font.pixelSize: _fontSize
+ width: tagsRepeater.maxGroupLabelWidth;
+ elide: Text.ElideRight
+ anchors.top: parent.top
+ anchors.topMargin: 5
+
+ MouseArea {
+ anchors.fill: parent;
+ acceptedButtons: Qt.RightButton
+ onClicked: {
+ if (mouse.button === Qt.RightButton) {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ _parentView.showGroupContextMenu(coords.x, coords.y, model.group);
+ }
+ }
+ }
+ }
+
+ Flow {
+ width: root.width - 110
+ spacing: 5
+
+ Repeater {
+ model: tagsModel
+
+ Loader {
+ readonly property var tagsModel: model
+ readonly property var grpModel: groupModel
+ sourceComponent: tagComponent
+ }
+ }
+
+ ToolButton {
+ id: addTagButton
+ text: qsTr("+ Tag")
+ font.pixelSize: _fontSize
+ height: 25
+
+ onClicked: {
+ _variantsGroupModel.addNewTag(groupModel.group)
+ }
+
+ }
+ }
+ }
+ }
+
+ Item { width: 1; height: 5 } // vertical spacer
+
+ ToolButton {
+ id: addGroupButton
+ text: qsTr("+ Group")
+ font.pixelSize: _fontSize
+ width: 65
+ height: 25
+ onClicked: {
+ _variantsGroupModel.addNewGroup()
+ }
+ }
+
+ Item { width: 1; height: 5 } // vertical spacer
+ }
+ }
+
+ Component {
+ id: tagComponent
+
+ Rectangle {
+ property bool toggled: tagsModel ? tagsModel.selected : false
+ property color grpColor: grpModel ? grpModel.color : ""
+ property bool isBright: grpModel ? _utils.isBright(grpColor) : false
+
+ width: Math.max(tLabel.width + 10, 60)
+ height: 25
+ color: toggled ? grpColor : _backgroundColor
+ border.color: _studioColor4
+
+ Text {
+ id: tLabel
+ anchors.centerIn: parent
+ text: tagsModel ? tagsModel.tag : ""
+ font.pixelSize: _fontSize
+ color: toggled ? (isBright ? _studioColor1 : _textColor) : _studioColor4
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ onClicked: {
+ if (mouse.button === Qt.LeftButton) {
+ toggled = !toggled;
+ _variantsGroupModel.setTagState(grpModel.group, tagsModel.tag, toggled);
+ } else if (mouse.button === Qt.RightButton) {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ _parentView.showTagContextMenu(coords.x, coords.y, grpModel.group,
+ tagsModel.tag);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp
new file mode 100644
index 00000000..84967f4c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "InspectorGroup.h"
+
+CInspectorGroup::CInspectorGroup(const QString &inName)
+{
+ m_name = inName;
+}
+
+CInspectorGroup::~CInspectorGroup()
+{
+}
+
+QString CInspectorGroup::GetName() const
+{
+ return m_name;
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h
new file mode 100644
index 00000000..18f56d79
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_INSPECTOR_GROUP_H
+#define INCLUDED_INSPECTOR_GROUP_H
+
+#include <QString>
+
+/**
+ * This is the base class for inspector groups.
+ *
+ * Derive from this class in order to create a new group for the inspector palette.
+ */
+class CInspectorGroup
+{
+public:
+ CInspectorGroup(const QString &inName = {});
+ virtual ~CInspectorGroup();
+
+ QString GetName() const;
+
+protected:
+ QString m_name;
+};
+
+#endif // INCLUDED_INSPECTOR_GROUP_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml
new file mode 100644
index 00000000..88de98c5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.0
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.2
+
+import Qt3DStudio 1.0
+import "../controls"
+import "../Action"
+
+StyledComboBox {
+ property int instance: parent.modelData.instance
+ property int handle: parent.modelData.handle
+ property var values: parent.modelData.values
+ property var value: parent.modelData.value
+ property bool blockIndexChange: false
+ property var callback
+
+ model: values
+
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+
+ Component.onCompleted: {
+ currentIndex = find(value)
+ }
+ onCurrentIndexChanged: {
+ var newValue = textAt(currentIndex)
+ if (!blockIndexChange && value !== newValue && currentIndex !== -1)
+ callback(instance, handle, newValue)
+ }
+ onValueChanged: {
+ var newNewIndex = find(value);
+ if (!blockIndexChange || newNewIndex > 0)
+ currentIndex = newNewIndex;
+ blockIndexChange = false;
+ }
+ onValuesChanged : {
+ // Changing the values list will reset the currentIndex to zero, so block setting
+ // the actual material. We'll get the proper index right after.
+ if (currentIndex > 0)
+ blockIndexChange = true;
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp
new file mode 100644
index 00000000..b2d1a595
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "MaterialRefView.h"
+#include "StudioApp.h"
+#include "StudioPreferences.h"
+#include "Doc.h"
+#include "Core.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "IObjectReferenceHelper.h"
+#include "IDocumentEditor.h"
+
+#include <QtCore/qtimer.h>
+
+MaterialRefView::MaterialRefView(QWidget *parent)
+: QListWidget(parent)
+{
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QListWidget::Fixed);
+}
+
+/**
+ * Gather and display the currently used standard and custom material list
+ *
+ * @param instance The instance that owns the property handle
+ * @param handle The property handle this materials list is for
+ *
+ * @return necessary size for the dialog
+ */
+QSize MaterialRefView::refreshMaterials(int instance, int handle)
+{
+ clear(); // clear old material list
+
+ m_instance = instance;
+ m_handle = handle;
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance = getRefInstance();
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ static const QPixmap pixMaterialNormal = QPixmap(":/images/Objects-Material-Normal.png");
+
+ QVector<qt3dsdm::Qt3DSDMInstanceHandle> mats;
+ doc->getSceneMaterials(doc->GetSceneInstance(), mats);
+
+ // add freshly collected material list
+ for (auto matInstance : qAsConst(mats)) {
+ qt3dsdm::SValue v;
+ propertySystem->GetInstancePropertyValue(matInstance,
+ bridge->GetObjectDefinitions().m_Named.m_NameProp, v);
+ QString matName = qt3dsdm::get<QString>(v);
+
+ // get the material's object name (parent)
+ qt3dsdm::Qt3DSDMInstanceHandle parentInstance = bridge->GetParentInstance(matInstance);
+ qt3dsdm::SValue vParent;
+ propertySystem->GetInstancePropertyValue(parentInstance,
+ bridge->GetObjectDefinitions().m_Named.m_NameProp, vParent);
+ QString objName = qt3dsdm::get<QString>(vParent);
+ matName.append(QLatin1String(" (") + objName + QLatin1String(")"));
+
+ QListWidgetItem *matItem = new QListWidgetItem(this);
+ matItem->setData(Qt::DisplayRole, matName);
+ matItem->setData(Qt::DecorationRole, pixMaterialNormal);
+ matItem->setData(Qt::UserRole, QVariant(matInstance));
+
+ if (matInstance == refInstance)
+ setCurrentItem(matItem);
+ }
+
+ if (count() == 0) {
+ // Show an unselectable dummy item
+ static const QPixmap pixWarning = QPixmap(":/images/warning.png");
+ QListWidgetItem *matItem = new QListWidgetItem(this);
+ matItem->setData(Qt::DisplayRole, tr("No animatable materials found"));
+ matItem->setData(Qt::DecorationRole, pixWarning);
+ matItem->setData(Qt::UserRole, -1);
+ setSelectionMode(QAbstractItemView::NoSelection);
+ } else {
+ setSelectionMode(QAbstractItemView::SingleSelection);
+ }
+
+ QSize widgetSize(CStudioPreferences::valueWidth(),
+ qMin(10, count()) * CStudioPreferences::controlBaseHeight());
+
+ return widgetSize;
+}
+
+void MaterialRefView::updateSelection()
+{
+ int refInstance = getRefInstance();
+ for (int i = 0, itemCount = count(); i < itemCount; ++i) {
+ int matInstance = item(i)->data(Qt::UserRole).toInt();
+ if (matInstance == refInstance) {
+ setCurrentRow(i);
+ break;
+ }
+ }
+}
+
+bool MaterialRefView::isFocused() const
+{
+ return hasFocus();
+}
+
+int MaterialRefView::getRefInstance() const
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(m_instance, m_handle, value);
+ return doc->GetDataModelObjectReferenceHelper()->Resolve(value, m_instance);
+}
+
+void MaterialRefView::focusInEvent(QFocusEvent *event)
+{
+ QAbstractItemView::focusInEvent(event);
+ emit focusChanged();
+}
+
+void MaterialRefView::focusOutEvent(QFocusEvent *event)
+{
+ QAbstractItemView::focusOutEvent(event);
+ emit focusChanged();
+ QTimer::singleShot(0, this, &QAbstractItemView::close);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h
new file mode 100644
index 00000000..d4131cda
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MATERIALREFVIEW_H
+#define MATERIALREFVIEW_H
+
+#include <QtWidgets/qlistwidget.h>
+#include "Qt3DSDMHandles.h"
+
+class MaterialRefView : public QListWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged)
+
+public:
+ explicit MaterialRefView(QWidget *parent = nullptr);
+
+ QSize refreshMaterials(int instance, int handle);
+
+ void updateSelection();
+protected:
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+
+Q_SIGNALS:
+ void focusChanged();
+
+private:
+ bool isFocused() const;
+ int getRefInstance() const;
+
+ int m_instance = -1;
+ int m_handle = -1;
+};
+
+#endif // MATERIALREFVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml
new file mode 100644
index 00000000..fcaf498c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+ ListView {
+ id: listView
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 4
+
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _meshChooserModel
+
+ delegate: ChooserDelegate {
+ onClicked: {
+ _meshChooserView.setSelectedMeshName(filePath);
+ }
+ onDoubleClicked: {
+ _meshChooserView.setSelectedMeshName(filePath);
+ _meshChooserView.hide();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp
new file mode 100644
index 00000000..f87dc89b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "MeshChooserModel.h"
+#include "Core.h"
+#include "Doc.h"
+#include "IDocumentBufferCache.h"
+#include "StudioApp.h"
+#include "BasicObjectsModel.h"
+
+MeshChooserModel::MeshChooserModel(QObject *parent)
+ : ChooserModelBase(parent)
+{
+}
+
+MeshChooserModel::~MeshChooserModel()
+{
+
+}
+
+bool MeshChooserModel::isVisible(const QString &path) const
+{
+ return getIconType(path) == OBJTYPE_MODEL;
+}
+
+const QVector<ChooserModelBase::FixedItem> MeshChooserModel::getFixedItems() const
+{
+ static QVector<FixedItem> items;
+
+ if (items.isEmpty()) {
+ auto primitives = BasicObjectsModel::BasicMeshesModel();
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ for (int i = 0; i < primitives.size(); i++) {
+ auto item = primitives.at(i);
+ const wchar_t *itemName = doc->GetBufferCache().GetPrimitiveName(item.primitiveType());
+ if (itemName[0] == L'#')
+ items.append({ OBJTYPE_MODEL, item.icon(), QString::fromWCharArray(itemName + 1) });
+ }
+ }
+
+ return items;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h
new file mode 100644
index 00000000..5aea88a8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MESHCHOOSERMODEL_H
+#define MESHCHOOSERMODEL_H
+
+#include "ChooserModelBase.h"
+
+class MeshChooserModel : public ChooserModelBase
+{
+ Q_OBJECT
+
+public:
+ explicit MeshChooserModel(QObject *parent = nullptr);
+ virtual ~MeshChooserModel();
+
+private:
+ bool isVisible(const QString &path) const override;
+ const QVector<FixedItem> getFixedItems() const override;
+};
+
+#endif // MESHCHOOSERMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp
new file mode 100644
index 00000000..1aac4731
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "MeshChooserView.h"
+#include "MeshChooserModel.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMValue.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "IDocumentBufferCache.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+MeshChooserView::MeshChooserView(QWidget *parent)
+ : QQuickWidget(parent)
+ , m_model(new MeshChooserModel(this))
+{
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &MeshChooserView::initialize);
+}
+
+void MeshChooserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"),
+ StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_meshChooserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_meshChooserModel"), m_model);
+
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/MeshChooser.qml")));
+}
+
+int MeshChooserView::numMeshes() const
+{
+ return m_model->rowCount();
+}
+
+void MeshChooserView::setSelectedMeshName(const QString &name)
+{
+ bool resourceName = false;
+ QString meshName = QLatin1Char('#') + name;
+ const wchar_t **files = g_StudioApp.GetCore()->GetDoc()->GetBufferCache().GetPrimitiveNames();
+ for (const wchar_t **item = files; item && *item; ++item) {
+ QString primitive = QString::fromWCharArray(*item);
+ if (primitive == meshName) {
+ resourceName = true;
+ break;
+ }
+ }
+ if (!resourceName)
+ meshName = name;
+
+ Q_EMIT meshSelected(m_handle, m_instance, meshName);
+}
+
+void MeshChooserView::setHandle(int handle)
+{
+ m_handle = handle;
+}
+
+void MeshChooserView::setInstance(int instance)
+{
+ m_instance = instance;
+}
+
+void MeshChooserView::updateSelection()
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(m_instance, m_handle, value);
+
+ QString currentFile;
+ const QString meshValue = qt3dsdm::get<QString>(value);
+ if (meshValue.startsWith(QLatin1Char('#')))
+ currentFile = meshValue.mid(1);
+ else
+ currentFile = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(meshValue));
+
+ m_model->setCurrentFile(currentFile);
+}
+
+bool MeshChooserView::isFocused() const
+{
+ return hasFocus();
+}
+
+void MeshChooserView::focusInEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusInEvent(event);
+ emit focusChanged();
+}
+
+void MeshChooserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ emit focusChanged();
+ QTimer::singleShot(0, this, &QQuickWidget::close);
+}
+
+void MeshChooserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &MeshChooserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void MeshChooserView::showEvent(QShowEvent *event)
+{
+ updateSelection();
+ QQuickWidget::showEvent(event);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h
new file mode 100644
index 00000000..6d12cf14
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MESHCHOOSERVIEW_H
+#define MESHCHOOSERVIEW_H
+
+#include <QQuickWidget>
+
+namespace Q3DStudio {
+class CFilePath;
+class CString;
+}
+
+class MeshChooserModel;
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+class MeshChooserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged)
+
+public:
+ explicit MeshChooserView(QWidget *parent = nullptr);
+
+ Q_INVOKABLE void setSelectedMeshName(const QString &name);
+
+ void setHandle(int handle);
+ void setInstance(int instance);
+ int numMeshes() const;
+
+ void updateSelection();
+
+Q_SIGNALS:
+ void meshSelected(int handle, int instance, const QString &name);
+
+protected:
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+Q_SIGNALS:
+ void focusChanged();
+
+private:
+ void showEvent(QShowEvent *event) override;
+ void initialize();
+ bool isFocused() const;
+
+ int m_handle = -1;
+ int m_instance = -1;
+ MeshChooserModel *m_model = nullptr;
+};
+
+#endif // MESHCHOOSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp
new file mode 100644
index 00000000..24c67fbe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "MouseHelper.h"
+#include "MainFrm.h"
+#include "StudioApp.h"
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qmainwindow.h>
+#include <QtWidgets/qwidget.h>
+#include <QtGui/qcursor.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qscreen.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qbitmap.h>
+
+static void setBlankCursor()
+{
+ // Qt::BlankCursor gets corrupted in some situations, so use custom bitmap (QTBUG-61678)
+ static QBitmap *zeroBitmap = nullptr;
+ if (!zeroBitmap) {
+ zeroBitmap = new QBitmap(32, 32);
+ zeroBitmap->clear();
+ }
+ QGuiApplication::setOverrideCursor(QCursor(*zeroBitmap, *zeroBitmap));
+}
+
+MouseHelper::MouseHelper(QObject *parent)
+ : QObject(parent)
+ , m_dragState(StateNotDragging)
+ , m_maxDelta(50, 50)
+{
+ // All cursor position modifications are done asynchronously, so we don't get position
+ // changes in middle of mouse event handling.
+ m_cursorResetTimer.setInterval(0);
+ m_cursorResetTimer.setSingleShot(true);
+ connect(&m_cursorResetTimer, &QTimer::timeout, this, &MouseHelper::resetCursor);
+}
+
+void MouseHelper::startUnboundedDrag()
+{
+ m_dragState = StateDragging;
+ setBlankCursor();
+ m_startPos = QCursor::pos();
+
+ QWindow *window = g_StudioApp.m_pMainWnd->windowHandle();
+ if (window)
+ window->installEventFilter(this); // Always install event filter to main window
+
+ if (m_widget) {
+ // Use the center of the on-screen portion of the parent widget as reference point.
+ // This ensures cursor restores properly, as the cursor stays on the widget.
+ m_window = m_widget->window()->windowHandle();
+ const QRect screenGeometry = m_window->screen()->geometry();
+ const QPoint bottomRight = screenGeometry.bottomRight();
+ QSize widgetSize = m_widget->size();
+ QPoint widgetPos = m_widget->mapToGlobal(QPoint(0, 0));
+ if (widgetPos.x() < 0) {
+ widgetSize.setWidth(widgetSize.width() + widgetPos.x());
+ widgetPos.setX(0);
+ }
+ if (widgetPos.y() < 0) {
+ widgetSize.setHeight(widgetSize.height() + widgetPos.y());
+ widgetPos.setY(0);
+ }
+ if (widgetPos.x() + widgetSize.width() > bottomRight.x())
+ widgetSize.setWidth(bottomRight.x() - widgetPos.x());
+ if (widgetPos.y() + widgetSize.height() > bottomRight.y())
+ widgetSize.setHeight(bottomRight.y() - widgetPos.y());
+ m_maxDelta = QPoint(widgetSize.width() / 2, widgetSize.height() / 2);
+ m_referencePoint = widgetPos + m_maxDelta;
+ } else {
+ // Just assume the screen of the app is at least 400x400 if we don't have widget
+ m_referencePoint = QPoint(200, 200);
+ m_window = nullptr;
+ }
+ m_previousPoint = m_startPos;
+
+ m_cursorResetTimer.start();
+}
+
+void MouseHelper::endUnboundedDrag()
+{
+ QWindow *window = g_StudioApp.m_pMainWnd->windowHandle();
+ if (window)
+ window->removeEventFilter(this);
+ m_dragState = StateEndingDrag;
+ m_cursorResetTimer.start();
+}
+
+QPoint MouseHelper::delta()
+{
+ QPoint delta(0, 0);
+ if (m_dragState == StateDragging) {
+ QPoint currentPoint = QCursor::pos();
+ delta = currentPoint - m_previousPoint;
+ m_previousPoint = currentPoint;
+
+ // Limit delta to even out the maximum possible change rate regardless of widget position
+ if (delta.x() > m_maxDelta.x())
+ delta.setX(m_maxDelta.x());
+ else if (delta.x() < -m_maxDelta.x())
+ delta.setX(-m_maxDelta.x());
+
+ if (delta.y() > m_maxDelta.y())
+ delta.setY(m_maxDelta.y());
+ else if (delta.y() < -m_maxDelta.y())
+ delta.setY(-m_maxDelta.y());
+
+ if (!m_cursorResetTimer.isActive())
+ m_cursorResetTimer.start();
+ }
+ return delta;
+}
+
+void MouseHelper::setWidget(QWidget *widget)
+{
+ m_widget = widget;
+}
+
+void MouseHelper::resetCursor()
+{
+ switch (m_dragState) {
+ case StateDragging:
+ if (m_window)
+ QCursor::setPos(m_window->screen(), m_referencePoint);
+ else
+ QCursor::setPos(m_referencePoint);
+ m_previousPoint = m_referencePoint;
+ break;
+ case StateEndingDrag:
+ if (m_window)
+ QCursor::setPos(m_window->screen(), m_startPos);
+ else
+ QCursor::setPos(m_startPos);
+ m_dragState = StateFinalCursorReset;
+ m_cursorResetTimer.start();
+ break;
+ case StateFinalCursorReset:
+ // First change to default cursor to avoid any flicker of cursor
+ qApp->changeOverrideCursor(Qt::ArrowCursor);
+ qApp->restoreOverrideCursor();
+ m_dragState = StateNotDragging;
+ break;
+ case StateNotDragging:
+ default:
+ break;
+ }
+}
+
+bool MouseHelper::eventFilter(QObject *obj, QEvent *event)
+{
+ Q_UNUSED(obj)
+
+ // Eat all mouse button events that are not for left button and all key events
+ switch (event->type()) {
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease: {
+ QMouseEvent *me = static_cast<QMouseEvent *>(event);
+ if (me->button() == Qt::LeftButton)
+ return false;
+ else
+ return true;
+ }
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::ShortcutOverride:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h
new file mode 100644
index 00000000..8f343bc4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef MOUSEHELPER_H
+#define MOUSEHELPER_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qtimer.h>
+
+QT_FORWARD_DECLARE_CLASS(QWidget)
+QT_FORWARD_DECLARE_CLASS(QWindow)
+
+class MouseHelper : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit MouseHelper(QObject *parent = nullptr);
+ ~MouseHelper() {};
+
+ Q_INVOKABLE void startUnboundedDrag();
+ Q_INVOKABLE void endUnboundedDrag();
+ Q_INVOKABLE QPoint delta();
+
+ void setWidget(QWidget *widget);
+
+private Q_SLOTS:
+ void resetCursor();
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+private:
+ QPoint m_startPos;
+ QPoint m_referencePoint;
+ QPoint m_previousPoint;
+ QTimer m_cursorResetTimer;
+
+ enum DragState {
+ StateNotDragging,
+ StateDragging,
+ StateEndingDrag,
+ StateFinalCursorReset
+ };
+ DragState m_dragState;
+ QWidget *m_widget = nullptr; // Not owned
+ QWindow *m_window = nullptr; // Not owned
+ QPoint m_maxDelta;
+};
+
+#endif // MOUSEHELPER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml
new file mode 100644
index 00000000..fc0a2e55
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import Qt3DStudio 1.0
+import "../controls"
+
+Rectangle {
+ id: root
+
+ property alias selectedText: selectionText.text
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 10
+
+ ListView {
+ id: browserList
+
+ Layout.margins: 10
+ Layout.columnSpan: 2
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.minimumHeight: 80
+ Layout.preferredHeight: count * 20
+ Layout.preferredWidth: root.width
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _objectBrowserView.model
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ currentIndex: _objectBrowserView.selection
+
+ delegate: Item {
+ id: delegateItem
+
+ x: model.depth * 20
+ width: parent.width
+ height: model.parentExpanded ? typeIcon.height + 10 : 0
+
+ visible: height > 0
+
+ Behavior on height {
+ NumberAnimation {
+ duration: 100
+ easing.type: Easing.OutQuad
+ }
+ }
+
+ Row {
+ id: row
+
+ height: typeIcon.height
+ spacing: 5
+
+ Image {
+ source: {
+ if (!model.hasChildren)
+ return "";
+ model.expanded ? _resDir + "arrow_down.png"
+ : _resDir + "arrow.png";
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: model.expanded = !model.expanded
+ }
+ }
+
+ Rectangle {
+ height: typeIcon.height
+ width: typeIcon.width + name.width + 10
+
+ color: model.index === browserList.currentIndex ? _selectionColor
+ : "transparent"
+
+ Row {
+ spacing: 10
+ Image {
+ id: typeIcon
+
+ source: model.icon
+ }
+
+ StyledLabel {
+ id: name
+ anchors.verticalCenter: typeIcon.verticalCenter
+ color: model.textColor
+ text: model.name
+ }
+ }
+
+ MouseArea {
+ id: delegateArea
+
+ anchors.fill: parent
+ onClicked: {
+ if (_objectBrowserView.selectable(model.index)) {
+ browserList.currentIndex = model.index;
+ // Set the selection here, as otherwise we can't set for
+ // example the same reference material to more than one target
+ // without selecting something else first
+ _objectBrowserView.selection = browserList.currentIndex;
+ }
+ }
+ onDoubleClicked: {
+ if (_objectBrowserView.selectable(model.index)) {
+ browserList.currentIndex = model.index;
+ // Set the selection here, as otherwise we can't set for
+ // example the same reference material to more than one target
+ // without selecting something else first
+ _objectBrowserView.selection = browserList.currentIndex;
+ _objectBrowserView.close();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Connections {
+ target: _objectBrowserView
+ onSelectionChanged: {
+ if (browserList.currentIndex !== _objectBrowserView.selection)
+ browserList.currentIndex = _objectBrowserView.selection;
+ }
+ }
+ }
+
+ StyledMenuSeparator {}
+
+ GridLayout {
+ columns: 2
+ Layout.margins: 10
+
+ StyledLabel {
+ text: qsTr("Type")
+ }
+
+ StyledComboBox {
+ id: pathCombo
+ model: [qsTr("Absolute Reference"), qsTr("Path Reference")]
+
+ onActivated: {
+ if (index === 0)
+ _objectBrowserView.pathType = ObjectBrowserView.Absolute;
+ else if (index === 1)
+ _objectBrowserView.pathType = ObjectBrowserView.Relative;
+ }
+ }
+
+ StyledLabel {
+ text: qsTr("Path")
+ }
+
+ StyledLabel {
+ id: selectionText
+ Layout.preferredWidth: _valueWidth
+ text: pathCombo.currentIndex === 0 ? _objectBrowserView.absPath(browserList.currentIndex)
+ : _objectBrowserView.relPath(browserList.currentIndex)
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp
new file mode 100644
index 00000000..87238847
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "ObjectBrowserView.h"
+
+#include "ObjectListModel.h"
+#include "StudioPreferences.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+ObjectBrowserView::ObjectBrowserView(QWidget *parent)
+ : QQuickWidget(parent)
+{
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &ObjectBrowserView::initialize);
+}
+
+QAbstractItemModel *ObjectBrowserView::model() const
+{
+ return m_model;
+}
+
+void ObjectBrowserView::setModel(ObjectListModel *model)
+{
+ if (!m_model)
+ m_model = new FlatObjectListModel(model, this);
+ m_model->setSourceModel(model);
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+
+ // Remove "Scene.__Container" and "materials//Default" entries
+ QModelIndexList list = m_model->match(m_model->index(0, 0),
+ ObjectListModel::AbsolutePathRole,
+ QStringLiteral("Scene.")
+ + bridge->getMaterialContainerName(), 1,
+ Qt::MatchFlags(Qt::MatchWrap | Qt::MatchExactly
+ | Qt::MatchRecursive));
+ list.append(m_model->match(m_model->index(0, 0),
+ ObjectListModel::NameRole,
+ QStringLiteral("materials/") + bridge->getDefaultMaterialName(), 1,
+ Qt::MatchFlags(Qt::MatchWrap | Qt::MatchExactly
+ | Qt::MatchRecursive)));
+
+ for (int i = list.size(); i > 0; i--)
+ m_model->removeRow(list.at(i - 1).row());
+
+ m_ownerInstance = 0;
+ m_selection = -1;
+
+ Q_EMIT modelChanged();
+}
+
+QString ObjectBrowserView::absPath(int index) const
+{
+ return m_model->index(index, 0).data(ObjectListModel::AbsolutePathRole).toString();
+}
+
+QString ObjectBrowserView::relPath(int index) const
+{
+ return m_model->data(
+ m_model->index(index),
+ m_model->sourceModel()->indexForHandle(m_ownerInstance),
+ ObjectListModel::PathReferenceRole).toString();
+}
+
+bool ObjectBrowserView::selectable(int index) const
+{
+ auto handleId = m_model->index(index, 0).data(ObjectListModel::HandleRole).toInt();
+ auto handle = qt3dsdm::Qt3DSDMInstanceHandle(handleId);
+ return m_model->sourceModel()->selectable(handle);
+}
+
+void ObjectBrowserView::selectAndExpand(const qt3dsdm::Qt3DSDMInstanceHandle &handle,
+ const qt3dsdm::Qt3DSDMInstanceHandle &owner)
+{
+ m_ownerInstance = owner;
+ QModelIndex index = m_model->sourceIndexForHandle(handle);
+ if (!index.isValid())
+ return;
+ m_model->expandTo(QModelIndex(), index);
+ m_blockCommit = true;
+ setSelection(m_model->rowForSourceIndex(index));
+ m_blockCommit = false;
+}
+
+void ObjectBrowserView::setSelection(int index)
+{
+ if (m_selection != index) {
+ m_selection = index;
+ Q_EMIT selectionChanged();
+ }
+}
+
+void ObjectBrowserView::setPathType(ObjectBrowserView::PathType type)
+{
+ if (type != m_pathType) {
+ m_pathType = type;
+ Q_EMIT pathTypeChanged();
+ }
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle ObjectBrowserView::selectedHandle() const
+{
+ auto handleId = m_model->index(m_selection, 0).data(ObjectListModel::HandleRole).toInt();
+ return qt3dsdm::Qt3DSDMInstanceHandle(handleId);
+}
+
+bool ObjectBrowserView::isFocused() const
+{
+ return hasFocus();
+}
+
+void ObjectBrowserView::focusInEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusInEvent(event);
+ emit focusChanged();
+}
+
+void ObjectBrowserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ emit focusChanged();
+ QTimer::singleShot(0, this, &QQuickWidget::close);
+}
+
+void ObjectBrowserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &ObjectBrowserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void ObjectBrowserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_objectBrowserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+ qmlRegisterUncreatableType<ObjectBrowserView>(
+ "Qt3DStudio", 1, 0, "ObjectBrowserView",
+ QStringLiteral("Creation of ObjectBrowserView not allowed from QML"));
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/ObjectBrowser.qml")));
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h
new file mode 100644
index 00000000..7fb7ec30
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef OBJECTBROWSERVIEW_H
+#define OBJECTBROWSERVIEW_H
+
+#include <QQuickWidget>
+
+#include "RelativePathTools.h"
+#include "Qt3DSDMHandles.h"
+
+#include <QColor>
+
+class ObjectListModel;
+class FlatObjectListModel;
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+class ObjectBrowserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged)
+ Q_PROPERTY(QAbstractItemModel *model READ model NOTIFY modelChanged FINAL)
+ Q_PROPERTY(int selection READ selection WRITE setSelection NOTIFY selectionChanged FINAL)
+ Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL)
+
+public:
+ ObjectBrowserView(QWidget *parent = nullptr);
+
+
+ enum PathType {
+ Absolute = CRelativePathTools::EPATHTYPE_GUID,
+ Relative = CRelativePathTools::EPATHTYPE_RELATIVE,
+ };
+ Q_ENUM(PathType)
+
+ QAbstractItemModel *model() const;
+ void setModel(ObjectListModel *model);
+
+ Q_INVOKABLE QString absPath(int index) const;
+ Q_INVOKABLE QString relPath(int index) const;
+ Q_INVOKABLE bool selectable(int index) const;
+
+ void selectAndExpand(const qt3dsdm::Qt3DSDMInstanceHandle &handle,
+ const qt3dsdm::Qt3DSDMInstanceHandle &owner);
+
+ int selection() const { return m_selection; }
+ void setSelection(int index);
+
+ PathType pathType() const {return m_pathType;}
+ void setPathType(PathType type);
+
+ qt3dsdm::Qt3DSDMInstanceHandle selectedHandle() const;
+
+ bool canCommit() const { return !m_blockCommit; }
+
+Q_SIGNALS:
+ void modelChanged();
+ void pathTypeChanged();
+ void selectionChanged();
+
+protected:
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+Q_SIGNALS:
+ void focusChanged();
+
+private:
+ void initialize();
+ bool isFocused() const;
+
+ FlatObjectListModel *m_model = nullptr;
+ QHash<int, ObjectListModel *> m_subModels;
+ QColor m_baseColor = QColor::fromRgb(75, 75, 75);
+ QColor m_selectColor;
+ int m_selection = -1;
+ PathType m_pathType = Absolute;
+ qt3dsdm::Qt3DSDMInstanceHandle m_ownerInstance = 0;
+ bool m_blockCommit = false;
+};
+
+#endif // OBJECTBROWSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp
new file mode 100644
index 00000000..ad66274a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp
@@ -0,0 +1,534 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ObjectListModel.h"
+
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Doc.h"
+#include "GraphUtils.h"
+#include "IObjectReferenceHelper.h"
+#include "StudioUtils.h"
+#include "SlideSystem.h"
+#include "StudioObjectTypes.h"
+#include "StudioPreferences.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+
+#include <QCoreApplication>
+#include <QColor>
+
+ObjectListModel::ObjectListModel(CCore *core,
+ const qt3dsdm::Qt3DSDMInstanceHandle &baseHandle, QObject *parent,
+ bool isAliasSelectList)
+ : QAbstractItemModel(parent)
+ , m_core(core)
+ , m_baseHandle(baseHandle)
+ , m_AliasSelectList(isAliasSelectList)
+{
+ auto doc = m_core->GetDoc();
+ m_objRefHelper = doc->GetDataModelObjectReferenceHelper();
+ if (!m_AliasSelectList)
+ m_slideHandle = m_objRefHelper->GetSlideList(m_baseHandle)[0];
+ else
+ m_slideHandle = m_objRefHelper->GetSlideList(m_baseHandle).back();
+}
+
+QHash<int, QByteArray> ObjectListModel::roleNames() const
+{
+ auto names = QAbstractItemModel::roleNames();
+ names.insert(NameRole, "name");
+ names.insert(HandleRole, "handle");
+ names.insert(IconRole, "icon");
+ names.insert(TextColorRole, "textColor");
+ names.insert(AbsolutePathRole, "absolutePath");
+ names.insert(PathReferenceRole, "referencePath");
+
+ return names;
+}
+
+int ObjectListModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return 1;
+
+ const auto handle = handleForIndex(parent);
+ const auto children = childrenList(m_slideHandle, handle);
+ return int(children.size());
+}
+
+int ObjectListModel::columnCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent)
+ return 1;
+}
+
+QVariant ObjectListModel::data(const QModelIndex &index, int role) const
+{
+ return data(index, QModelIndex(), role);
+}
+
+QVariant ObjectListModel::data(const QModelIndex &index,
+ const QModelIndex &startingIndex,
+ int role) const
+{
+ if (!hasIndex(index.row(), index.column(), index.parent()))
+ return {};
+
+ auto handle = handleForIndex(index);
+
+ auto studioSystem = m_core->GetDoc()->GetStudioSystem();
+ auto propertySystem = studioSystem->GetPropertySystem();
+ qt3dsdm::SValue typeValue;
+ propertySystem->GetInstancePropertyValue(handle,
+ studioSystem->GetClientDataModelBridge()
+ ->GetTypeProperty(), typeValue);
+ qt3dsdm::DataModelDataType::Value valueType(qt3dsdm::GetValueType(typeValue));
+ if (valueType == qt3dsdm::DataModelDataType::None)
+ return {};
+
+ switch (role) {
+ case NameRole: {
+ return nameForHandle(handle);
+ }
+ case PathReferenceRole: {
+ Q3DStudio::CString data;
+ if (startingIndex.isValid()) {
+ data = m_objRefHelper->GetObjectReferenceString(
+ handleForIndex(startingIndex),
+ CRelativePathTools::EPATHTYPE_RELATIVE,
+ handle);
+ } else {
+ data = m_objRefHelper->GetObjectReferenceString(
+ m_baseHandle,
+ CRelativePathTools::EPATHTYPE_RELATIVE,
+ handle);
+ }
+ return data.toQString();
+ }
+ case AbsolutePathRole: {
+ Q3DStudio::CString data(m_objRefHelper->GetObjectReferenceString(
+ m_baseHandle, CRelativePathTools::EPATHTYPE_GUID, handle));
+ return data.toQString();
+ }
+ case HandleRole: {
+ return (int)handleForIndex(index);
+ }
+ case IconRole: {
+ auto info = m_objRefHelper->GetInfo(handle);
+ return StudioUtils::resourceImageUrl() + CStudioObjectTypes::GetNormalIconName(info.m_Type);
+ }
+ case TextColorRole: {
+ auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ auto objType = bridge->GetObjectType(handle);
+ auto info = m_objRefHelper->GetInfo(handle);
+ if (m_excludeTypes.contains(objType))
+ return QVariant::fromValue(CStudioPreferences::disabledColor());
+ else if (info.m_Master)
+ return QVariant::fromValue(CStudioPreferences::masterColor());
+ else
+ return QVariant::fromValue(CStudioPreferences::textColor());
+ }
+ default:
+ return {};
+ }
+
+ return {};
+}
+
+QModelIndex ObjectListModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return createIndex(row, column, (quintptr)(m_baseHandle));
+
+ const auto handle = handleForIndex(parent);
+ const auto children = childrenList(m_slideHandle, handle);
+ if (row >= children.size())
+ return {};
+
+ auto childHandle = children[row];
+ return createIndex(row, column, (quintptr)(childHandle));
+}
+
+QModelIndex ObjectListModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return {};
+
+ const auto handle = handleForIndex(index);
+ qt3dsdm::Qt3DSDMInstanceHandle parentHandle = m_core->GetDoc()->GetAssetGraph()->GetParent(handle);
+ if (!parentHandle.Valid())
+ return {};
+
+ int row = 0;
+ qt3dsdm::Qt3DSDMInstanceHandle grandParentHandle = m_core->GetDoc()->GetAssetGraph()
+ ->GetParent(parentHandle);
+ if (grandParentHandle.Valid()) {
+ const auto children = childrenList(m_slideHandle, grandParentHandle);
+ const auto it = std::find(children.begin(), children.end(), parentHandle);
+ Q_ASSERT(it != children.end());
+ row = it - children.begin();
+ }
+
+ return createIndex(row, 0, (quintptr)(parentHandle));
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle ObjectListModel::handleForIndex(const QModelIndex &index) const
+{
+ return static_cast<qt3dsdm::Qt3DSDMInstanceHandle>(index.internalId());
+}
+
+void ObjectListModel::excludeObjectTypes(const QVector<EStudioObjectType> &types)
+{
+ m_excludeTypes = types;
+}
+
+bool ObjectListModel::selectable(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const
+{
+ auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ auto objType = bridge->GetObjectType(handle);
+ // disallow aliasing the current active root
+ bool tryingToAliasParent = (m_core->GetDoc()->GetActiveRootInstance() == handle)
+ && m_AliasSelectList;
+ return (!m_excludeTypes.contains(objType) && !tryingToAliasParent);
+}
+
+qt3dsdm::TInstanceHandleList ObjectListModel::childrenList(
+ const qt3dsdm::Qt3DSDMSlideHandle &slideHandle,
+ const qt3dsdm::Qt3DSDMInstanceHandle &handle) const
+{
+ auto studioSystem = m_core->GetDoc()->GetStudioSystem();
+ auto slideSystem = studioSystem->GetSlideSystem();
+ auto currentMaster = slideSystem->GetMasterSlide(slideHandle);
+
+ qt3dsdm::TInstanceHandleList children;
+ m_objRefHelper->GetChildInstanceList(handle, children, slideHandle, m_baseHandle, true);
+ // allow action trigger/target from all objects
+ if (m_AliasSelectList) {
+ children.erase(
+ std::remove_if(children.begin(), children.end(),
+ [&slideHandle, slideSystem, &currentMaster](const qt3dsdm::Qt3DSDMInstanceHandle &h) {
+ const auto childSlide = slideSystem->GetAssociatedSlide(h);
+ if (!childSlide.Valid())
+ return true;
+ const auto childMaster = slideSystem->GetMasterSlide(childSlide);
+ if (childMaster == currentMaster) {
+ return childSlide != childMaster && childSlide != slideHandle;
+ } else {
+ return childSlide != childMaster;
+ }
+ }), children.end());
+ }
+ return children;
+}
+
+QString ObjectListModel::nameForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const
+{
+ const auto data = m_objRefHelper->GetInfo(handle);
+ return data.m_Name.toQString();
+}
+
+QModelIndex ObjectListModel::indexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle,
+ const QModelIndex &startIndex) const
+{
+ if (handle == m_baseHandle)
+ return index(0, 0, {});
+
+ for (int i = 0; i < rowCount(startIndex); i++) {
+ auto idx = index(i, 0, startIndex);
+ if (static_cast<qt3dsdm::Qt3DSDMInstanceHandle>(idx.internalId()) == handle)
+ return idx;
+ if (rowCount(idx) > 0) {
+ QModelIndex foundIndex = indexForHandle(handle, idx);
+ if (foundIndex.isValid())
+ return foundIndex;
+ }
+ }
+ return {};
+}
+
+
+FlatObjectListModel::FlatObjectListModel(ObjectListModel *sourceModel, QObject *parent)
+ : QAbstractListModel(parent)
+
+{
+ Q_ASSERT(sourceModel);
+ setSourceModel(sourceModel);
+}
+
+QVector<FlatObjectListModel::SourceInfo> FlatObjectListModel::collectSourceIndexes(
+ const QModelIndex &sourceIndex, int depth) const
+{
+ QVector<SourceInfo> sourceInfo;
+
+ for (int i = 0; i < m_sourceModel->rowCount(sourceIndex); i++) {
+ auto idx = m_sourceModel->index(i, 0, sourceIndex);
+ SourceInfo info;
+ info.depth = depth;
+ info.index = idx;
+ sourceInfo.append(info);
+ if (m_sourceModel->rowCount(idx) > 0) {
+ sourceInfo += collectSourceIndexes(idx, depth + 1);
+ }
+ }
+
+ return sourceInfo;
+}
+
+QHash<int, QByteArray> FlatObjectListModel::roleNames() const
+{
+ auto roles = m_sourceModel->roleNames();
+ roles.insert(DepthRole, "depth");
+ roles.insert(ExpandedRole, "expanded");
+ roles.insert(ParentExpandedRole, "parentExpanded");
+ roles.insert(HasChildrenRole, "hasChildren");
+ return roles;
+}
+
+QModelIndex FlatObjectListModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ int row = proxyIndex.row();
+ if (row < 0 || row >= m_sourceInfo.count())
+ return {};
+ return m_sourceInfo[row].index;
+}
+
+QModelIndex FlatObjectListModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ return index(rowForSourceIndex(sourceIndex));
+}
+
+QVariant FlatObjectListModel::data(const QModelIndex &index, int role) const
+{
+ return data(index, QModelIndex(), role);
+}
+
+QVariant FlatObjectListModel::data(const QModelIndex &index,
+ const QModelIndex &startingIndex,
+ int role) const
+{
+ const auto row = index.row();
+ if (row < 0 || row >= m_sourceInfo.count())
+ return {};
+
+ switch (role) {
+ case DepthRole: {
+ auto info = m_sourceInfo[row];
+ return info.depth;
+ }
+ case ExpandedRole: {
+ auto info = m_sourceInfo[row];
+ return info.expanded;
+ }
+ case ParentExpandedRole: {
+ auto info = m_sourceInfo[row];
+ if (info.depth == 0)
+ return true;
+
+ int depth = info.depth;
+ for (int i = row - 1; i >= 0; i--) {
+ const auto prevInfo = m_sourceInfo[i];
+ if (prevInfo.depth < depth) {
+ if (!prevInfo.expanded) {
+ return false;
+ } else {
+ depth = prevInfo.depth;
+ }
+ }
+ }
+ return true;
+ }
+ case HasChildrenRole: {
+ if (row == m_sourceInfo.count() - 1)
+ return false;
+ auto info = m_sourceInfo[row];
+ auto nextInfo = m_sourceInfo[row + 1];
+ return (nextInfo.depth > info.depth);
+ }
+ }
+
+ QModelIndex sourceIndex = mapToSource(index);
+ if (startingIndex.isValid())
+ return m_sourceModel->data(sourceIndex, startingIndex, role);
+ else
+ return m_sourceModel->data(sourceIndex, QModelIndex(), role);
+
+}
+
+bool FlatObjectListModel::setData(const QModelIndex &index, const QVariant &data, int role)
+{
+ const auto row = index.row();
+ if (row < 0 || row >= m_sourceInfo.count())
+ return {};
+
+ switch (role) {
+ case ExpandedRole: {
+ auto info = &m_sourceInfo[index.row()];
+ info->expanded = data.toBool();
+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {});
+ return true;
+ }
+ }
+
+ QModelIndex sourceIndex = mapToSource(index);
+ return m_sourceModel->setData(sourceIndex, data, role);
+}
+
+int FlatObjectListModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_sourceInfo.count();
+}
+
+bool FlatObjectListModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ beginRemoveRows(parent, row, row + count - 1);
+ m_sourceInfo.remove(row, count);
+ endRemoveRows();
+ return true;
+}
+
+void FlatObjectListModel::setSourceModel(ObjectListModel *sourceModel)
+{
+ beginResetModel();
+ sourceModel->disconnect(this);
+ connect(sourceModel, &QAbstractListModel::dataChanged, this,
+ [this](const QModelIndex &start, const QModelIndex &end, const QVector<int> &roles) {
+ emit dataChanged(mapFromSource(start), mapFromSource(end), roles);
+
+ });
+ connect(sourceModel, &QAbstractListModel::rowsInserted, this,
+ [this](const QModelIndex &parent, int start, int end) {
+ const int parentRow = rowForSourceIndex(parent);
+ const int depth = m_sourceInfo[parentRow].depth + 1;
+ const int startRow = rowForSourceIndex(parent, start);
+ Q_ASSERT(startRow != -1);
+ beginInsertRows({}, startRow, startRow + end - start);
+ for (int row = end; row >= start; --row) {
+ SourceInfo info;
+ info.depth = depth;
+ info.index = m_sourceModel->index(row, 0, parent);
+ m_sourceInfo.insert(startRow, info);
+ }
+ endInsertRows();
+ });
+ connect(sourceModel, &QAbstractListModel::rowsRemoved, this,
+ [this](const QModelIndex &parent, int start, int end) {
+ const int startRow = rowForSourceIndex(parent, start);
+ const int endRow = rowForSourceIndex(parent, end);
+ Q_ASSERT(startRow != -1 && endRow != -1);
+ beginRemoveRows({}, startRow, endRow);
+ m_sourceInfo.remove(startRow, endRow - startRow + 1);
+ endRemoveRows();
+ });
+ connect(sourceModel, &QAbstractListModel::modelReset, this,
+ [this]() {
+ beginResetModel();
+ m_sourceInfo = collectSourceIndexes({}, 0);
+ endResetModel();
+ });
+
+ connect(sourceModel, &ObjectListModel::roleUpdated, this, [this](int role) {
+ emit dataChanged(index(0,0), index(rowCount() - 1, 0), {role});
+ });
+
+ connect(sourceModel, &ObjectListModel::rolesUpdated, this,
+ [this](const QVector<int> &roles = QVector<int> ()) {
+ emit dataChanged(index(0,0), index(rowCount() - 1, 0), roles);
+ });
+
+
+ m_sourceModel = sourceModel;
+ m_sourceInfo = collectSourceIndexes({}, 0);
+ endResetModel();
+}
+
+// startIndex and searchIndex are source indexes
+bool FlatObjectListModel::expandTo(const QModelIndex &startIndex, const QModelIndex &searchIndex)
+{
+ // Found the index we are looking for. We don't want to expand it, so just return true.
+ if (startIndex == searchIndex)
+ return true;
+
+ // Look for the search index in children
+ const int rowCount = m_sourceModel->rowCount(startIndex);
+ for (int i = 0; i < rowCount; i++) {
+ auto idx = m_sourceModel->index(i, 0, startIndex);
+ if (idx == searchIndex) {
+ // Expand startIndex as that is the parent
+ setData(index(rowForSourceIndex(startIndex)), QVariant(true), ExpandedRole);
+ return true;
+ }
+ if (m_sourceModel->rowCount(idx) > 0) {
+ bool found = expandTo(idx, searchIndex);
+ if (found) {
+ // Found by some descendant. Keep expanding parents
+ setData(index(rowForSourceIndex(startIndex)), QVariant(true), ExpandedRole);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+int FlatObjectListModel::rowForSourceIndex(const QModelIndex &sourceIndex) const
+{
+ for (int i = 0; i < m_sourceInfo.size(); i++) {
+ if (m_sourceInfo[i].index == sourceIndex)
+ return i;
+ }
+ return -1;
+}
+
+int FlatObjectListModel::rowForSourceIndex(const QModelIndex& parentIndex, int row) const
+{
+ const int parentRow = rowForSourceIndex(parentIndex);
+ if (parentRow == -1)
+ return -1;
+ const int childDepth = m_sourceInfo[parentRow].depth + 1;
+ int i = parentRow + 1;
+ while (i < m_sourceInfo.size()) {
+ const auto& info = m_sourceInfo[i];
+ if (info.depth < childDepth)
+ break;
+ if (info.depth == childDepth) {
+ if (row == 0)
+ break;
+ --row;
+ }
+ ++i;
+ }
+ return i;
+}
+
+QModelIndex FlatObjectListModel::sourceIndexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle)
+{
+ return m_sourceModel->indexForHandle(handle);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h
new file mode 100644
index 00000000..4013f15c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef OBJECTLISTMODEL_H
+#define OBJECTLISTMODEL_H
+
+#include <QAbstractItemModel>
+#include <QAbstractListModel>
+
+#include "Qt3DSDMHandles.h"
+#include "StudioObjectTypes.h"
+
+class IObjectReferenceHelper;
+class CCore;
+
+class ObjectListModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ ObjectListModel(CCore *core, const qt3dsdm::Qt3DSDMInstanceHandle &baseHandle,
+ QObject *parent = nullptr,
+ bool isAliasSelectList = false);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant data(const QModelIndex &index,
+ const QModelIndex &startingIndex = {},
+ int role = Qt::DisplayRole) const;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+
+ enum Roles {
+ NameRole = Qt::DisplayRole,
+ AbsolutePathRole = Qt::UserRole + 1,
+ PathReferenceRole,
+ IconRole,
+ TextColorRole,
+ HandleRole,
+ LastRole = HandleRole
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ qt3dsdm::Qt3DSDMInstanceHandle baseHandle() const {return m_baseHandle;}
+
+ QModelIndex indexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle,
+ const QModelIndex &startIndex = {}) const;
+
+ bool selectable(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const;
+
+ void excludeObjectTypes(const QVector<EStudioObjectType> &types);
+
+Q_SIGNALS:
+ void roleUpdated(int role);
+ void rolesUpdated(const QVector<int> &roles = QVector<int> ());
+
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle handleForIndex(const QModelIndex &index) const;
+
+ virtual qt3dsdm::TInstanceHandleList childrenList(const qt3dsdm::Qt3DSDMSlideHandle &slideHandle,
+ const qt3dsdm::Qt3DSDMInstanceHandle &handle) const;
+
+ QString nameForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const;
+
+ CCore *m_core;
+ qt3dsdm::Qt3DSDMSlideHandle m_slideHandle;
+ qt3dsdm::Qt3DSDMInstanceHandle m_baseHandle;
+ IObjectReferenceHelper *m_objRefHelper;
+ QVector<EStudioObjectType> m_excludeTypes;
+ bool m_AliasSelectList;
+};
+
+class FlatObjectListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ // TODO Make FlatObjectList take a shared pointer
+ FlatObjectListModel(ObjectListModel *sourceModel, QObject *parent = nullptr);
+
+ enum Roles {
+ DepthRole = ObjectListModel::LastRole + 1,
+ ExpandedRole,
+ ParentExpandedRole,
+ HasChildrenRole
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant data(const QModelIndex &index,
+ const QModelIndex &startingIndex = {},
+ int role = Qt::DisplayRole) const;
+ bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
+
+ void setSourceModel(ObjectListModel *sourceModel);
+ ObjectListModel *sourceModel() const { return m_sourceModel; }
+ bool expandTo(const QModelIndex &rootIndex, const QModelIndex &searchIndex);
+ int rowForSourceIndex(const QModelIndex &sourceIndex) const;
+ QModelIndex sourceIndexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle);
+
+private:
+ int rowForSourceIndex(const QModelIndex &parentIndex, int row) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+
+ struct SourceInfo {
+ bool expanded = false;
+ int depth = 0;
+ QPersistentModelIndex index;
+ };
+
+ QVector<SourceInfo> collectSourceIndexes(const QModelIndex &sourceIndex, int depth) const;
+
+ QVector<SourceInfo> m_sourceInfo;
+ ObjectListModel *m_sourceModel = nullptr;
+};
+
+
+#endif // OBJECTLISTMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp
new file mode 100644
index 00000000..a82df79c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp
@@ -0,0 +1,225 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMInspectorGroup.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMSlides.h"
+#include "IDocumentReader.h"
+
+using namespace qt3dsdm;
+
+Qt3DSDMInspectable::Qt3DSDMInspectable(qt3dsdm::Qt3DSDMInstanceHandle instance,
+ qt3dsdm::Qt3DSDMInstanceHandle activeSlideInstance)
+ : m_instance(instance)
+ , m_activeSlideInstance(activeSlideInstance)
+{
+ QT3DS_ASSERT(getDoc()->GetDocumentReader().IsInstance(m_instance));
+
+ if (m_activeSlideInstance) {
+ // only active root scene or components set m_activeSlideInstance
+ auto *bridge = getDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ QT3DS_ASSERT(bridge->IsSceneInstance(instance)
+ || bridge->IsComponentInstance(instance));
+ }
+}
+
+// Returns the name of this inspectable
+Q3DStudio::CString Qt3DSDMInspectable::getName()
+{
+ auto *bridge = getDoc()->GetStudioSystem()->GetClientDataModelBridge();
+
+ if (!m_activeSlideInstance)
+ return bridge->GetName(m_instance, true);
+
+ Q3DStudio::CString theName = bridge->GetName(m_instance, true);
+ theName += " (";
+ theName += bridge->GetName(m_activeSlideInstance, true);
+ theName += ")";
+
+ return theName;
+}
+
+// Returns the number of groups in this inspectable
+long Qt3DSDMInspectable::getGroupCount() const
+{
+ IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData();
+ // In addition to a background group, Scene has a basic properties group (hidden in
+ // inspector) because it is derived from Asset. Until this is fixed properly, we force the
+ // Scene groups count to 1 (else an empty group will appear in the inspector).
+ long count = getObjectType() == OBJTYPE_SCENE ? 1
+ : long(theMetaData.GetGroupCountForInstance(m_instance));
+
+ if (m_activeSlideInstance)
+ count += long(theMetaData.GetGroupCountForInstance(m_activeSlideInstance));
+
+ return count;
+}
+
+// Return the property group for display
+CInspectorGroup *Qt3DSDMInspectable::getGroup(long inIndex)
+{
+ Qt3DSDMInspectorGroup *group = new Qt3DSDMInspectorGroup(GetGroupName(inIndex));
+
+ TMetaDataPropertyHandleList properties = GetGroupProperties(inIndex);
+
+ for (auto &prop : properties)
+ group->CreateRow(getDoc(), prop);
+
+ return group;
+}
+
+// Return the property handles for display, given the group index
+TMetaDataPropertyHandleList Qt3DSDMInspectable::GetGroupProperties(long inIndex)
+{
+ long activeGroupIdx = activeGroupIndex(inIndex);
+ TMetaDataPropertyHandleList retval;
+ IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData();
+ theMetaData.GetMetaDataProperties(GetGroupInstance(inIndex), retval);
+ qt3dsdm::IPropertySystem &thePropertySystem(*getDoc()->GetStudioSystem()->GetPropertySystem());
+ // get name of the current group for filtering
+ Option<qt3dsdm::TCharStr> theGroupFilterName =
+ theMetaData.GetGroupFilterNameForInstance(GetGroupInstance(inIndex), activeGroupIdx);
+ long theGroupCount = getGroupCount();
+
+ // end is explicitly required
+ for (size_t idx = 0; idx < retval.size(); ++idx) {
+ if (theMetaData.GetMetaDataPropertyInfo(retval[idx])->m_IsHidden) {
+ retval.erase(retval.begin() + idx);
+ --idx;
+ } else if (theGroupCount > 1 && theGroupFilterName.hasValue()
+ && theMetaData.GetMetaDataPropertyInfo(retval[idx])->m_GroupName
+ != theGroupFilterName) {
+ retval.erase(retval.begin() + idx);
+ --idx;
+ } else {
+ qt3ds::foundation::NVConstDataRef<SPropertyFilterInfo> theFilters(
+ theMetaData.GetMetaDataPropertyFilters(retval[idx]));
+ if (theFilters.size()) {
+ Option<bool> keepProperty;
+ // The tests are done in an ambiguous way. Really, show if equal should take
+ // multiple conditions
+ // as should hide if equal. They do not, so we need to rigorously define exactly
+ // how those two interact.
+ for (QT3DSU32 propIdx = 0, propEnd = theFilters.size(); propIdx < propEnd;
+ ++propIdx) {
+ const SPropertyFilterInfo &theFilter(theFilters[propIdx]);
+
+ QT3DS_ASSERT(theFilter.m_FilterType == PropertyFilterTypes::ShowIfEqual
+ || theFilter.m_FilterType == PropertyFilterTypes::HideIfEqual);
+
+ SValue theValue;
+ thePropertySystem.GetInstancePropertyValue(
+ GetGroupInstance(inIndex), theFilter.m_FilterProperty, theValue);
+ bool resultIfTrue = theFilter.m_FilterType == PropertyFilterTypes::ShowIfEqual;
+ if (Equals(theValue.toOldSkool(), theFilter.m_Value.toOldSkool())) {
+ keepProperty = resultIfTrue;
+ break;
+ } else {
+ keepProperty = !resultIfTrue;
+ }
+ }
+ if (keepProperty.hasValue() && *keepProperty == false) {
+ retval.erase(retval.begin() + idx);
+ --idx;
+ }
+ }
+ }
+ }
+ return retval;
+}
+
+// Return the Group Name, given the group index
+QString Qt3DSDMInspectable::GetGroupName(long groupIndex)
+{
+ std::vector<TCharStr> theGroupNames;
+ IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData();
+ theMetaData.GetGroupNamesForInstance(GetGroupInstance(groupIndex), theGroupNames);
+
+ long activeGroupIdx = activeGroupIndex(groupIndex);
+ if (activeGroupIdx < theGroupNames.size())
+ return Q3DStudio::CString(theGroupNames[activeGroupIdx].wide_str()).toQString();
+
+ return QObject::tr("Basic Properties");
+}
+
+// Return the Inspectable Instance Handle for the Group, given the group index
+Qt3DSDMInstanceHandle Qt3DSDMInspectable::GetGroupInstance(long inGroupIndex)
+{
+ // if active root, return the slide instance at first index
+ if (m_activeSlideInstance && inGroupIndex == 0)
+ return m_activeSlideInstance;
+
+ return m_instance;
+}
+
+EStudioObjectType Qt3DSDMInspectable::getObjectType() const
+{
+ return getDoc()->GetStudioSystem()->GetClientDataModelBridge()->GetObjectType(m_instance);
+}
+
+bool Qt3DSDMInspectable::isValid() const
+{
+ if (m_activeSlideInstance) {
+ return getDoc()->GetStudioSystem()->IsInstance(m_instance)
+ && getDoc()->GetStudioSystem()->IsInstance(m_activeSlideInstance);
+ }
+ return getDoc()->GetStudioSystem()->IsInstance(m_instance);
+}
+
+bool Qt3DSDMInspectable::isMaster() const
+{
+ ISlideSystem *slideSystem = getDoc()->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theSlideHandle = slideSystem->GetAssociatedSlide(m_instance);
+ if (theSlideHandle.Valid())
+ return slideSystem->IsMasterSlide(theSlideHandle);
+ // Slide handle may not be valid if we are selecting the Scene or if we are inside Component and
+ // we select the Component root.
+ return false;
+}
+
+// Returns the group index taking into consideration that for active roots, first index is the slide
+// group so need to decrement all index bigger than 1, by 1. For scene we decrement 1 more because
+// the first group (Basic properties) is not in use.
+long Qt3DSDMInspectable::activeGroupIndex(long groupIndex) const
+{
+ if (m_activeSlideInstance && groupIndex > 0 && getObjectType() != OBJTYPE_SCENE)
+ return groupIndex - 1;
+
+ return groupIndex;
+}
+
+CDoc *Qt3DSDMInspectable::getDoc() const
+{
+ return g_StudioApp.GetCore()->GetDoc();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h
new file mode 100644
index 00000000..0da2916d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_QT3DSDM_INSPECTABLE_H
+#define INCLUDED_QT3DSDM_INSPECTABLE_H
+
+#include "InspectableBase.h"
+#include "Qt3DSDMHandles.h"
+
+class CDoc;
+
+// For inspecting data model instances
+class Qt3DSDMInspectable : public CInspectableBase
+{
+public:
+ Qt3DSDMInspectable(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMInstanceHandle activeSlideInstance = 0);
+
+ Q3DStudio::CString getName() override;
+ long getGroupCount() const override;
+ CInspectorGroup *getGroup(long) override;
+ EStudioObjectType getObjectType() const override;
+ bool isValid() const override;
+ bool isMaster() const override;
+ qt3dsdm::Qt3DSDMInstanceHandle getInstance() const override { return m_instance; }
+ virtual qt3dsdm::TMetaDataPropertyHandleList GetGroupProperties(long inGroupIndex);
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetGroupInstance(long inGroupIndex);
+
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle m_instance;
+ qt3dsdm::Qt3DSDMInstanceHandle m_activeSlideInstance;
+
+ virtual QString GetGroupName(long inGroupIndex);
+ CDoc *getDoc() const;
+ long activeGroupIndex(long groupIndex) const;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp
new file mode 100644
index 00000000..f62812ac
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDMInspectorGroup.h"
+#include "Qt3DSDMInspectorRow.h"
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMMetaData.h"
+
+Qt3DSDMInspectorGroup::Qt3DSDMInspectorGroup(const QString &inName)
+ : CInspectorGroup(inName)
+{
+}
+
+Qt3DSDMInspectorGroup::~Qt3DSDMInspectorGroup()
+{
+ for (auto it = m_inspectorRows.begin(); it != m_inspectorRows.end(); ++it)
+ delete (*it);
+}
+
+// Create a new InspectorRowBase.
+void Qt3DSDMInspectorGroup::CreateRow(CDoc *inDoc,
+ qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty)
+{
+ Q3DStudio::Qt3DSDMInspectorRow *theRow = new Q3DStudio::Qt3DSDMInspectorRow(inDoc, inProperty);
+ m_inspectorRows.push_back(theRow); // this Qt3DSDMInspectorRow is now owned by this class
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h
new file mode 100644
index 00000000..c2bbc9fc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_QT3DSDM_INSPECTORGROUP_H
+#define INCLUDED_QT3DSDM_INSPECTORGROUP_H
+
+#include "InspectorGroup.h"
+#include "Qt3DSDMHandles.h"
+
+class Qt3DSDMInspectable;
+class CDoc;
+
+namespace Q3DStudio {
+class Qt3DSDMInspectorRow;
+};
+
+class Qt3DSDMInspectorGroup : public CInspectorGroup
+{
+public:
+ Qt3DSDMInspectorGroup(const QString &inName);
+ ~Qt3DSDMInspectorGroup();
+
+ void CreateRow(CDoc *inDoc, qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty);
+
+ const std::vector<Q3DStudio::Qt3DSDMInspectorRow *> &GetRows() const { return m_inspectorRows; }
+
+protected:
+ std::vector<Q3DStudio::Qt3DSDMInspectorRow *> m_inspectorRows;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp
new file mode 100644
index 00000000..3f629df4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDMInspectorRow.h"
+#include "Qt3DSDMMetaData.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+
+using namespace qt3dsdm;
+
+namespace Q3DStudio {
+
+Qt3DSDMInspectorRow::Qt3DSDMInspectorRow(CDoc *inDoc, Qt3DSDMMetaDataPropertyHandle inProperty)
+ : m_MetaProperty(inProperty)
+{
+ IMetaData *theMetaData = inDoc->GetStudioSystem()->GetActionMetaData();
+ SMetaDataPropertyInfo theInfo(theMetaData->GetMetaDataPropertyInfo(inProperty));
+ m_MetaDataPropertyInfo = theInfo;
+}
+
+Qt3DSDMInspectorRow::~Qt3DSDMInspectorRow()
+{
+}
+
+} // namespace Q3DStudio
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h
new file mode 100644
index 00000000..6c8156c1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#pragma once
+
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMMetaDataTypes.h"
+
+class CDoc;
+
+namespace Q3DStudio {
+
+// This is a binding between a DataModelInspectable and an InspectorRow
+class Qt3DSDMInspectorRow
+{
+public:
+ explicit Qt3DSDMInspectorRow(CDoc *inDoc, qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty);
+ virtual ~Qt3DSDMInspectorRow();
+
+ qt3dsdm::Qt3DSDMMetaDataPropertyHandle GetMetaDataProperty() const { return m_MetaProperty; }
+
+ const qt3dsdm::SMetaDataPropertyInfo &GetMetaDataPropertyInfo() const
+ {
+ return m_MetaDataPropertyInfo;
+ }
+
+protected:
+ qt3dsdm::Qt3DSDMMetaDataPropertyHandle m_MetaProperty;
+ qt3dsdm::SMetaDataPropertyInfo m_MetaDataPropertyInfo;
+};
+
+} // namespace Q3DStudio
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp
new file mode 100644
index 00000000..69d86623
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDMMaterialInspectable.h"
+
+using namespace qt3dsdm;
+
+Qt3DSDMMaterialInspectorGroup::Qt3DSDMMaterialInspectorGroup(const QString &inName)
+ : Qt3DSDMInspectorGroup(inName)
+ , m_isMaterialGroup(inName == QLatin1String("Material"))
+{
+}
+
+CInspectorGroup *Qt3DSDMMaterialInspectable::getGroup(long inIndex)
+{
+ Qt3DSDMInspectorGroup *group = new Qt3DSDMMaterialInspectorGroup(GetGroupName(inIndex));
+
+ TMetaDataPropertyHandleList properties = GetGroupProperties(inIndex);
+
+ for (auto &prop : properties)
+ group->CreateRow(getDoc(), prop);
+
+ return group;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h
new file mode 100644
index 00000000..0367bb7a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_QT3DSDM_MATERIAL_INSPECTABLE_H
+#define INCLUDED_QT3DSDM_MATERIAL_INSPECTABLE_H
+
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMInspectorGroup.h"
+
+class Qt3DSDMMaterialInspectorGroup : public Qt3DSDMInspectorGroup
+{
+public:
+ Qt3DSDMMaterialInspectorGroup(const QString &inName);
+
+ bool isMaterialGroup() const { return m_isMaterialGroup; }
+
+private:
+ bool m_isMaterialGroup = false;
+};
+
+class Qt3DSDMMaterialInspectable : public Qt3DSDMInspectable
+{
+public:
+ Qt3DSDMMaterialInspectable(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+ : Qt3DSDMInspectable(inInstance)
+ {
+ }
+
+ CInspectorGroup *getGroup(long) override;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp
new file mode 100644
index 00000000..1f4b8632
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TabOrderHandler.h"
+
+TabOrderHandler::TabOrderHandler(QObject *parent)
+ : QObject(parent)
+{
+
+}
+
+TabOrderHandler::~TabOrderHandler()
+{
+
+}
+
+void TabOrderHandler::addItem(int group, QQuickItem *item)
+{
+ m_itemMap[group].append(item);
+}
+
+void TabOrderHandler::clear()
+{
+ m_itemMap.clear();
+}
+
+void TabOrderHandler::clearGroup(int group)
+{
+ m_itemMap[group].clear();
+}
+
+void TabOrderHandler::tabNavigate(bool tabForward)
+{
+ // Find the currently focused control
+ for (int i = 0; i < m_itemMap.size(); i++) {
+ const QList<QQuickItem *> items = m_itemMap[i];
+ for (int j = 0; j < items.size(); j++) {
+ if (items[j]->hasActiveFocus()) {
+ if (tabForward)
+ nextItem(i, j)->forceActiveFocus(Qt::TabFocusReason);
+ else
+ previousItem(i, j)->forceActiveFocus(Qt::BacktabFocusReason);
+ return;
+ }
+ }
+ }
+ // Activate the first item if could not find currently focused item
+ for (int i = 0; i < m_itemMap.size(); i++) {
+ if (m_itemMap[i].size() > 0)
+ m_itemMap[i][0]->forceActiveFocus(Qt::TabFocusReason);
+ }
+}
+
+QQuickItem *TabOrderHandler::nextItem(int group, int index)
+{
+ if (m_itemMap[group].size() > index + 1) {
+ // Try next item in group
+ index++;
+ } else {
+ // Get item in next available group
+ int nextGroup = group + 1;
+ while (nextGroup != group) {
+ if (nextGroup >= m_itemMap.size())
+ nextGroup = 0;
+ if (m_itemMap[nextGroup].size() == 0)
+ nextGroup++;
+ else
+ group = nextGroup;
+ }
+ index = 0;
+ }
+ return m_itemMap[group][index];
+}
+
+QQuickItem *TabOrderHandler::previousItem(int group, int index)
+{
+ if (index - 1 >= 0) {
+ // Try previous item in group
+ index--;
+ } else {
+ // Get last item in previous available group
+ int nextGroup = group - 1;
+ while (nextGroup != group) {
+ if (nextGroup < 0)
+ nextGroup = m_itemMap.size() - 1;
+ if (m_itemMap[nextGroup].size() == 0)
+ nextGroup--;
+ else
+ group = nextGroup;
+ }
+ index = m_itemMap[group].size() - 1;
+ }
+ return m_itemMap[group][index];
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h
new file mode 100644
index 00000000..10c1d400
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef TABORDERHANDLER_H
+#define TABORDERHANDLER_H
+
+#include <QtCore/qobject.h>
+#include <QtQuick/qquickitem.h>
+
+class TabOrderHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit TabOrderHandler(QObject *parent = nullptr);
+ ~TabOrderHandler();
+
+ Q_INVOKABLE void addItem(int group, QQuickItem *item);
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE void clearGroup(int group);
+
+ void tabNavigate(bool tabForward);
+
+private:
+ QQuickItem *nextItem(int group, int index);
+ QQuickItem *previousItem(int group, int index);
+
+ QHash<int, QList<QQuickItem *> > m_itemMap;
+};
+
+class TabNavigable {
+public:
+ TabNavigable() : m_tabOrderHandler(new TabOrderHandler) {}
+ virtual ~TabNavigable() { delete m_tabOrderHandler; }
+ TabOrderHandler *tabOrderHandler() const { return m_tabOrderHandler; }
+
+private:
+ TabOrderHandler *m_tabOrderHandler;
+};
+
+#endif // TABORDERHANDLER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml
new file mode 100644
index 00000000..2a406257
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 10
+ ListView {
+ id: listView
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 4
+
+ boundsBehavior: Flickable.StopAtBounds
+ spacing: 4
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _textureChooserModel
+
+ delegate: ChooserDelegate {
+ onClicked: {
+ _textureChooserView.textureSelected(_textureChooserView.handle,
+ _textureChooserView.instance, filePath);
+ }
+ onDoubleClicked: {
+ _textureChooserView.textureSelected(_textureChooserView.handle,
+ _textureChooserView.instance, filePath);
+ _textureChooserView.hide();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp
new file mode 100644
index 00000000..8804e675
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TextureChooserView.h"
+#include "ImageChooserModel.h"
+#include "StudioPreferences.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMValue.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "StudioPreferences.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+TextureChooserView::TextureChooserView(QWidget *parent)
+ : QQuickWidget(parent)
+ , m_model(new ImageChooserModel(false, this))
+{
+ setWindowTitle(tr("Texture"));
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &TextureChooserView::initialize);
+}
+
+void TextureChooserView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_textureChooserView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_textureChooserModel"), m_model);
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/TextureChooser.qml")));
+}
+
+void TextureChooserView::setHandle(int handle)
+{
+ m_handle = handle;
+}
+
+int TextureChooserView::handle() const
+{
+ return m_handle;
+}
+
+void TextureChooserView::setInstance(int instance)
+{
+ m_instance = instance;
+}
+
+int TextureChooserView::instance() const
+{
+ return m_instance;
+}
+
+QString TextureChooserView::currentDataModelPath() const
+{
+ QString cleanPath;
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+
+ qt3dsdm::SValue value;
+ propertySystem->GetInstancePropertyValue(m_instance, m_handle, value);
+
+ const QString path = qt3dsdm::get<QString>(value);
+
+ // An empty value can sometimes be represented by a relative path either to project root or the
+ // presentation file, such as"./" or "../", so let's just consider all directory paths as empty
+ if (path.isEmpty() || QFileInfo(path).isDir()) {
+ cleanPath = ChooserModelBase::noneString();
+ } else {
+ // If path is renderable id, we need to resolve the actual path
+ const QString renderablePath = g_StudioApp.getRenderableAbsolutePath(path);
+
+ if (renderablePath.isEmpty())
+ cleanPath = path;
+ else
+ cleanPath = renderablePath;
+
+ cleanPath = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(cleanPath));
+ }
+ return cleanPath;
+}
+
+void TextureChooserView::updateSelection()
+{
+ m_model->setCurrentFile(currentDataModelPath());
+}
+
+void TextureChooserView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ QTimer::singleShot(0, this, &TextureChooserView::close);
+}
+
+void TextureChooserView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &TextureChooserView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void TextureChooserView::showEvent(QShowEvent *event)
+{
+ updateSelection();
+ QQuickWidget::showEvent(event);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h
new file mode 100644
index 00000000..156b2465
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TEXTURECHOOSERVIEW_H
+#define TEXTURECHOOSERVIEW_H
+
+#include <QQuickWidget>
+
+class ImageChooserModel;
+
+class TextureChooserView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(int instance READ instance)
+ Q_PROPERTY(int handle READ handle)
+
+public:
+ explicit TextureChooserView(QWidget *parent = nullptr);
+
+ void setHandle(int handle);
+ int handle() const;
+
+ void setInstance(int instance);
+ int instance() const;
+ QString currentDataModelPath() const;
+
+ void updateSelection();
+
+Q_SIGNALS:
+ void textureSelected(int handle, int instance, const QString &name);
+
+protected:
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void showEvent(QShowEvent *event) override;
+ void initialize();
+ int m_handle = -1;
+ int m_instance = -1;
+ ImageChooserModel *m_model = nullptr;
+};
+
+#endif // TEXTURECHOOSERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp
new file mode 100644
index 00000000..05a8fa64
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "VariantTagDialog.h"
+#include "ui_VariantTagDialog.h"
+#include "Dialogs.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "ProjectFile.h"
+
+#include <QtGui/qvalidator.h>
+
+VariantTagDialog::VariantTagDialog(DialogType type, const QString &group, const QString &name,
+ QWidget *parent)
+ : QDialog(parent)
+ , m_type(type)
+ , m_group(group)
+ , m_ui(new Ui::VariantTagDialog)
+{
+ m_ui->setupUi(this);
+
+ m_names.first = name;
+
+ QRegExpValidator *rgx = new QRegExpValidator(QRegExp("[\\w\\s]+"), this);
+ m_ui->lineEditTagName->setValidator(rgx);
+
+ if (type == AddGroup) {
+ setWindowTitle(tr("Add new Group"));
+ m_ui->label->setText(tr("Group name"));
+ } else if (type == RenameGroup) {
+ setWindowTitle(tr("Rename Group"));
+ m_ui->label->setText(tr("Group name"));
+ m_ui->lineEditTagName->setText(name);
+ m_ui->lineEditTagName->selectAll();
+ } else if (type == RenameTag) {
+ m_ui->lineEditTagName->setText(name);
+ m_ui->lineEditTagName->selectAll();
+ }
+
+ window()->setFixedSize(size());
+}
+
+void VariantTagDialog::accept()
+{
+ QString name = m_ui->lineEditTagName->text();
+
+ if (name.isEmpty()) {
+ displayWarning(EmptyWarning);
+ } else if (name == m_names.first) { // no change
+ QDialog::reject();
+ } else if (((m_type == AddGroup || m_type == RenameGroup)
+ && !g_StudioApp.GetCore()->getProjectFile().isVariantGroupUnique(name))
+ || (!g_StudioApp.GetCore()->getProjectFile().isVariantTagUnique(m_group, name))) {
+ displayWarning(UniqueWarning);
+ } else {
+ m_names.second = name;
+ QDialog::accept();
+ }
+}
+
+std::pair<QString, QString> VariantTagDialog::getNames() const
+{
+ return m_names;
+}
+
+void VariantTagDialog::displayWarning(WarningType warningType)
+{
+ QString warning;
+ if (warningType == EmptyWarning) {
+ if (m_type == AddGroup || m_type == RenameGroup)
+ warning = tr("The group name must not be empty.");
+ else
+ warning = tr("The tag name must not be empty.");
+ } else if (warningType == UniqueWarning) {
+ if (m_type == AddGroup || m_type == RenameGroup)
+ warning = tr("The group name must be unique.");
+ else
+ warning = tr("The tag name must be unique within the tag group.");
+ }
+
+ g_StudioApp.GetDialogs()->DisplayMessageBox(tr("Warning"), warning,
+ Qt3DSMessageBox::ICON_WARNING, false);
+}
+
+VariantTagDialog::~VariantTagDialog()
+{
+ delete m_ui;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h
new file mode 100644
index 00000000..b5e3989f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef VARIANTTAGDIALOG_H
+#define VARIANTTAGDIALOG_H
+
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class VariantTagDialog;
+}
+QT_END_NAMESPACE
+
+class VariantTagDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ enum DialogType { AddTag, RenameTag, AddGroup, RenameGroup };
+
+ explicit VariantTagDialog(DialogType type, const QString &group = {}, const QString &name = {},
+ QWidget *parent = nullptr);
+ ~VariantTagDialog() override;
+
+public Q_SLOTS:
+ void accept() override;
+ std::pair<QString, QString> getNames() const;
+
+private:
+ enum WarningType {
+ EmptyWarning,
+ UniqueWarning
+ };
+
+ void displayWarning(WarningType warningType);
+
+ DialogType m_type;
+ QString m_group;
+ Ui::VariantTagDialog *m_ui;
+ std::pair<QString, QString> m_names; // holds the tags values before and after rename
+};
+
+#endif // VARIANTTAGDIALOG_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui
new file mode 100644
index 00000000..2c0b2863
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VariantTagDialog</class>
+ <widget class="QDialog" name="VariantTagDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>300</width>
+ <height>100</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Add new Tag</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="labelEditLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Tag name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditTagName"/>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VariantTagDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VariantTagDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp
new file mode 100644
index 00000000..066f912a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp
@@ -0,0 +1,242 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "VariantsGroupModel.h"
+#include "VariantsTagModel.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "MainFrm.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "IDocumentEditor.h"
+#include "VariantTagDialog.h"
+#include "StudioUtils.h"
+#include "Dialogs.h"
+
+#include <QtCore/qsavefile.h>
+
+VariantsGroupModel::VariantsGroupModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+
+}
+
+void VariantsGroupModel::refresh()
+{
+ int instance = g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance();
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+
+ if (instance == 0 || bridge->GetObjectType(instance) & ~OBJTYPE_IS_VARIANT) {
+ m_instance = 0;
+ m_property = 0;
+ return;
+ }
+
+ auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem();
+ m_instance = instance;
+ m_property = bridge->getVariantsProperty(instance);
+
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(m_instance, m_property, sValue)) {
+ beginResetModel();
+
+ // delete tag models
+ for (auto &g : qAsConst(m_data))
+ delete g.m_tagsModel;
+
+ m_data.clear();
+
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ QHash<QString, QStringList> propTags;
+ if (!propVal.isEmpty()) {
+ const QStringList propTagsList = propVal.split(QChar(','));
+ for (auto &propTag : propTagsList) {
+ const QStringList propTagPair = propTag.split(QChar(':'));
+ propTags[propTagPair[0]].append(propTagPair[1]);
+ }
+ }
+
+ // build the variants data model
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ const auto keys = g_StudioApp.GetCore()->getProjectFile().variantsDefKeys();
+ for (auto &group : keys) {
+ TagGroupData g;
+ g.m_title = group;
+ g.m_color = variantsDef[group].m_color;
+
+ QVector<std::pair<QString, bool> > tags;
+ for (int i = 0; i < variantsDef[group].m_tags.length(); ++i) {
+ tags.append({variantsDef[group].m_tags[i],
+ propTags[group].contains(variantsDef[group].m_tags[i])});
+ }
+
+ g.m_tagsModel = new VariantsTagModel(tags);
+ m_data.push_back(g);
+ }
+
+ endResetModel();
+
+ bool isVariantsEmpty = rowCount() == 0;
+ if (m_variantsEmpty != isVariantsEmpty) {
+ m_variantsEmpty = isVariantsEmpty;
+ Q_EMIT varaintsEmptyChanged();
+ }
+ }
+}
+
+int VariantsGroupModel::rowCount(const QModelIndex &parent) const
+{
+ // For list models only the root node (an invalid parent) should return the list's size. For all
+ // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
+ if (parent.isValid())
+ return 0;
+
+ return m_data.size();
+}
+
+QVariant VariantsGroupModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (role == GroupTitleRole)
+ return m_data.at(index.row()).m_title;
+ else if (role == GroupColorRole)
+ return m_data.at(index.row()).m_color;
+ else if (role == TagRole)
+ return QVariant::fromValue(m_data.at(index.row()).m_tagsModel);
+
+ return QVariant();
+}
+
+void VariantsGroupModel::setTagState(const QString &group, const QString &tag, bool selected)
+{
+ QString val;
+ QString tagsStr;
+ bool skipFirst = false;
+ for (auto &g : qAsConst(m_data)) {
+ if (g.m_title == group)
+ g.m_tagsModel->updateTagState(tag, selected);
+
+ tagsStr = g.m_tagsModel->serialize(g.m_title);
+ if (!tagsStr.isEmpty()) {
+ if (skipFirst)
+ val.append(QChar(','));
+ val.append(tagsStr);
+ skipFirst = true;
+ }
+ }
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property"))
+ ->SetInstancePropertyValue(m_instance, m_property, QVariant(val));
+}
+
+void VariantsGroupModel::addNewTag(const QString &group)
+{
+ VariantTagDialog dlg(VariantTagDialog::AddTag, group);
+
+ if (dlg.exec() == QDialog::Accepted) {
+ g_StudioApp.GetCore()->getProjectFile().addVariantTag(group, dlg.getNames().second);
+ refresh();
+
+ if (g_StudioApp.GetCore()->getProjectFile().variantsDef()[group].m_tags.size() == 1)
+ g_StudioApp.m_pMainWnd->updateActionFilterEnableState();
+ }
+}
+
+void VariantsGroupModel::addNewGroup()
+{
+ VariantTagDialog dlg(VariantTagDialog::AddGroup);
+
+ if (dlg.exec() == QDialog::Accepted) {
+ g_StudioApp.GetCore()->getProjectFile().addVariantGroup(dlg.getNames().second);
+ refresh();
+ }
+}
+
+void VariantsGroupModel::importVariants()
+{
+ QString importFilePath = g_StudioApp.GetDialogs()->getImportVariantsDlg();
+
+ if (!importFilePath.isEmpty()) {
+ g_StudioApp.GetCore()->getProjectFile().loadVariants(importFilePath);
+ refresh();
+ }
+}
+
+void VariantsGroupModel::exportVariants()
+{
+ QString exportFilePath = g_StudioApp.GetDialogs()->getExportVariantsDlg();
+
+ if (exportFilePath.isEmpty())
+ return;
+
+ QDomDocument domDoc;
+ domDoc.appendChild(domDoc.createProcessingInstruction(QStringLiteral("xml"),
+ QStringLiteral("version=\"1.0\""
+ " encoding=\"utf-8\"")));
+
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ const auto keys = g_StudioApp.GetCore()->getProjectFile().variantsDefKeys();
+ QDomElement vElem = domDoc.createElement(QStringLiteral("variants"));
+ domDoc.appendChild(vElem);
+ for (auto &g : keys) {
+ const auto group = variantsDef[g];
+ QDomElement gElem = domDoc.createElement(QStringLiteral("variantgroup"));
+ gElem.setAttribute(QStringLiteral("id"), g);
+ gElem.setAttribute(QStringLiteral("color"), group.m_color);
+ vElem.appendChild(gElem);
+
+ for (auto &t : qAsConst(group.m_tags)) {
+ QDomElement tElem = domDoc.createElement(QStringLiteral("variant"));;
+ tElem.setAttribute(QStringLiteral("id"), t);
+ gElem.appendChild(tElem);
+ }
+ }
+
+ QSaveFile file(exportFilePath);
+ if (StudioUtils::openTextSave(file))
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+}
+
+QHash<int, QByteArray> VariantsGroupModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(GroupTitleRole, "group");
+ names.insert(GroupColorRole, "color");
+ names.insert(TagRole, "tags");
+ return names;
+}
+
+Qt::ItemFlags VariantsGroupModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ return Qt::ItemIsEditable;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h
new file mode 100644
index 00000000..75a218f7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef VARIANTSGROUPMODEL_H
+#define VARIANTSGROUPMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+
+class VariantsTagModel;
+
+class VariantsGroupModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(bool variantsEmpty MEMBER m_variantsEmpty NOTIFY varaintsEmptyChanged)
+
+public:
+Q_SIGNALS:
+ void varaintsEmptyChanged();
+
+public:
+ explicit VariantsGroupModel(QObject *parent = nullptr);
+
+ enum Roles {
+ GroupTitleRole = Qt::UserRole + 1,
+ GroupColorRole,
+ TagRole
+ };
+
+ struct TagGroupData
+ {
+ QString m_title;
+ QString m_color;
+ VariantsTagModel *m_tagsModel = nullptr;
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = GroupTitleRole) const override;
+
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+ void refresh();
+
+ Q_INVOKABLE void setTagState(const QString &group, const QString &tag, bool selected);
+ Q_INVOKABLE void addNewTag(const QString &group);
+ Q_INVOKABLE void addNewGroup();
+ Q_INVOKABLE void importVariants();
+ Q_INVOKABLE void exportVariants();
+
+
+protected:
+ QHash<int, QByteArray> roleNames() const override;
+
+private:
+ QVector<TagGroupData> m_data;
+ int m_instance = 0; // selected layer instance
+ int m_property = 0; // variant tags property handler
+ bool m_variantsEmpty = true; // no groups (nor tags)
+};
+
+#endif // VARIANTSGROUPMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp
new file mode 100644
index 00000000..30f33635
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "VariantsTagModel.h"
+
+VariantsTagModel::VariantsTagModel(const QVector<std::pair<QString, bool> > &data, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_data(data)
+{
+
+}
+
+void VariantsTagModel::updateTagState(const QString &tag, bool selected)
+{
+ for (auto &t : m_data) {
+ if (t.first == tag) {
+ t.second = selected;
+ break;
+ }
+ }
+}
+
+// return the tags in a formatted string to be saved to the property
+QString VariantsTagModel::serialize(const QString &groupName) const
+{
+ QString ret;
+ bool skipFirst = false;
+ for (auto &t : qAsConst(m_data)) {
+ if (t.second) {
+ if (skipFirst)
+ ret.append(QLatin1Char(','));
+
+ ret.append(groupName + QLatin1Char(':') + t.first);
+
+ skipFirst = true;
+ }
+ }
+
+ return ret;
+}
+
+int VariantsTagModel::rowCount(const QModelIndex &parent) const
+{
+ // For list models only the root node (an invalid parent) should return the list's size. For all
+ // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
+ if (parent.isValid())
+ return 0;
+
+ return m_data.size();
+}
+
+QVariant VariantsTagModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (role == TagRole)
+ return m_data.at(index.row()).first;
+ else if (role == SelectedRole)
+ return m_data.at(index.row()).second;
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> VariantsTagModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(TagRole, "tag");
+ names.insert(SelectedRole, "selected");
+ return names;
+}
+
+Qt::ItemFlags VariantsTagModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ return Qt::ItemIsEditable; // FIXME: Implement me!
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h
new file mode 100644
index 00000000..392de760
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef VARIANTSTAGMODEL_H
+#define VARIANTSTAGMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+
+class VariantsTagModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit VariantsTagModel(const QVector<std::pair<QString, bool> > &data,
+ QObject *parent = nullptr);
+
+ enum Roles {
+ TagRole = Qt::UserRole + 1,
+ SelectedRole,
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = TagRole) const override;
+
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+ void updateTagState(const QString &tag, bool selected);
+ QString serialize(const QString &groupName) const;
+
+protected:
+ QHash<int, QByteArray> roleNames() const override;
+
+private:
+ QVector<std::pair<QString, bool> > m_data; // [{tagName, selectedState}, ...]
+};
+
+#endif // VARIANTSTAGMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp
new file mode 100644
index 00000000..f83a5df6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp
@@ -0,0 +1,294 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "PaletteManager.h"
+#include "StudioApp.h"
+#include "MainFrm.h"
+#include "TimelineWidget.h"
+#include "BasicObjectsView.h"
+#include "SlideView.h"
+#include "WidgetControl.h"
+#include "InspectorControlView.h"
+#include "ActionView.h"
+#include "IDragable.h"
+#include "ProjectView.h"
+#include "TabOrderHandler.h"
+#include "StudioPreferences.h"
+#include "scenecameraview.h"
+
+#include <QtWidgets/qdockwidget.h>
+#include <QtWidgets/qboxlayout.h>
+
+//==============================================================================
+/**
+ * Constructor
+ */
+CPaletteManager::CPaletteManager(CMainFrame *inMainFrame, QObject *parent)
+ : QObject(parent)
+ , m_MainFrame(inMainFrame)
+{
+ const int defaultBottomDockHeight = int(inMainFrame->height() * 0.25);
+ const int defaultRightDockWidth = 435; // To fit all inspector controls
+ const int defaultProjectHeight = 285; // To fit all new project folders, expanded
+
+ // Position tabs to the right
+ inMainFrame->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::East);
+ inMainFrame->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
+
+ m_projectDock = new QDockWidget(QObject::tr("Project"), inMainFrame);
+ m_projectDock->setObjectName(QStringLiteral("project"));
+ m_projectDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
+ | Qt::BottomDockWidgetArea);
+
+ m_slideDock = new QDockWidget(QObject::tr("Slide"), inMainFrame);
+ m_slideDock->setObjectName(QStringLiteral("slide"));
+ m_slideDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
+ // Slide palette has a fixed size hint
+ auto slideView = new SlideView(m_slideDock);
+ slideView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ m_slideDock->setWidget(slideView);
+ inMainFrame->addDockWidget(Qt::LeftDockWidgetArea, m_slideDock);
+ m_ControlList.insert({CONTROLTYPE_SLIDE, m_slideDock});
+ QObject::connect(m_slideDock, &QDockWidget::dockLocationChanged, slideView,
+ &SlideView::onDockLocationChange);
+
+ m_basicObjectsDock = new QDockWidget(QObject::tr("Basic Objects"), inMainFrame);
+ m_basicObjectsDock->setObjectName(QStringLiteral("basic_objects"));
+ m_basicObjectsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
+ | Qt::BottomDockWidgetArea);
+ // Basic objects palette has a fixed size hint
+ auto basicObjectsView = new BasicObjectsView(m_basicObjectsDock);
+ basicObjectsView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ m_basicObjectsDock->setWidget(basicObjectsView);
+ inMainFrame->addDockWidget(Qt::LeftDockWidgetArea, m_basicObjectsDock);
+ inMainFrame->tabifyDockWidget(m_basicObjectsDock, m_slideDock);
+ m_ControlList.insert({CONTROLTYPE_BASICOBJECTS, m_basicObjectsDock});
+
+ m_timelineDock = new QDockWidget(QObject::tr("Timeline"));
+ m_timelineDock->setObjectName(QStringLiteral("timeline"));
+ m_timelineDock->setAllowedAreas(Qt::BottomDockWidgetArea);
+
+ // Give the preferred size as percentages of the mainframe size
+ m_timelineWidget = new TimelineWidget(QSize(inMainFrame->width() - defaultRightDockWidth,
+ defaultBottomDockHeight));
+ m_timelineWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ WidgetControl *timeLineWidgetControl = new WidgetControl(m_timelineWidget, m_timelineDock);
+ timeLineWidgetControl->RegisterForDnd(timeLineWidgetControl);
+ timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_FILE);
+ timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_UICFILE);
+ timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_LIB);
+ timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_TL);
+ timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_BASIC_OBJECTS);
+
+ m_timelineWidget->setParent(timeLineWidgetControl);
+
+ m_timelineDock->setWidget(timeLineWidgetControl);
+ inMainFrame->addDockWidget(Qt::BottomDockWidgetArea, m_timelineDock);
+ m_ControlList.insert({CONTROLTYPE_TIMELINE, m_timelineDock});
+
+ m_cameraDock = new QDockWidget(QObject::tr("Scene Camera"));
+ m_cameraDock->setObjectName(QStringLiteral("scenecamera"));
+ m_cameraDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::LeftDockWidgetArea
+ | Qt::RightDockWidgetArea);
+
+ m_cameraWidget = new SceneCameraView(inMainFrame, m_cameraDock);
+ m_cameraWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+
+ m_cameraDock->setWidget(m_cameraWidget);
+ inMainFrame->addDockWidget(Qt::BottomDockWidgetArea, m_cameraDock);
+ inMainFrame->tabifyDockWidget(m_timelineDock, m_cameraDock);
+ m_ControlList.insert({CONTROLTYPE_SCENECAMERA, m_cameraDock});
+
+ // Give the preferred size as percentages of the mainframe size
+ m_projectView = new ProjectView(QSize(defaultRightDockWidth, defaultProjectHeight),
+ m_projectDock);
+ m_projectView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ m_projectDock->setWidget(m_projectView);
+ inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_projectDock);
+ m_ControlList.insert({CONTROLTYPE_PROJECT, m_projectDock});
+
+ m_actionDock = new QDockWidget(QObject::tr("Action"), inMainFrame);
+ m_actionDock->setObjectName(QStringLiteral("action"));
+ m_actionDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
+ | Qt::BottomDockWidgetArea);
+ // Give the preferred size as percentages of the mainframe size
+ auto actionView = new ActionView(
+ QSize(defaultRightDockWidth, inMainFrame->height() - defaultProjectHeight),
+ m_actionDock);
+ actionView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ m_actionDock->setWidget(actionView);
+ inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_actionDock);
+ m_ControlList.insert({CONTROLTYPE_ACTION, m_actionDock});
+
+ m_inspectorDock = new QDockWidget(QObject::tr("Inspector"), inMainFrame);
+ m_inspectorDock->setObjectName(QStringLiteral("inspector_control"));
+ m_inspectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
+ | Qt::BottomDockWidgetArea);
+ // Give the preferred size as percentages of the mainframe size
+ auto inspectorView = new InspectorControlView(
+ QSize(defaultRightDockWidth, inMainFrame->height() - defaultProjectHeight),
+ m_inspectorDock);
+ inspectorView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ m_inspectorDock->setWidget(inspectorView);
+ inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_inspectorDock);
+ inMainFrame->tabifyDockWidget(m_inspectorDock, m_actionDock);
+ m_ControlList.insert({CONTROLTYPE_INSPECTOR, m_inspectorDock});
+
+ m_inspectorDock->raise();
+
+ m_basicObjectsDock->setEnabled(false);
+ m_projectDock->setEnabled(false);
+ m_slideDock->setEnabled(false);
+ m_actionDock->setEnabled(false);
+ m_inspectorDock->setEnabled(false);
+ m_timelineDock->setEnabled(false);
+ m_cameraDock->setEnabled(false);
+}
+
+//==============================================================================
+/**
+ * Destructor
+ */
+CPaletteManager::~CPaletteManager()
+{
+ TControlMap::iterator theIterator = m_ControlList.begin();
+ TControlMap::iterator theEndIterator = m_ControlList.end();
+ // Delete all the controls
+ for (theIterator = m_ControlList.begin(); theIterator != theEndIterator; ++theIterator)
+ delete theIterator->second;
+}
+
+//=============================================================================
+/**
+ * Force a control to become invisible
+ */
+void CPaletteManager::HideControl(long inType)
+{
+ auto dock = GetControl(inType);
+
+ if (dock) {
+ // Make sure the control is invisible
+ dock->setVisible(false);
+ }
+}
+//=============================================================================
+/**
+ * Detemine if a control is currently visible
+ */
+bool CPaletteManager::IsControlVisible(long inType) const
+{
+ auto dock = GetControl(inType);
+ return dock && dock->isVisible();
+}
+
+//=============================================================================
+/**
+ * Force a control to become visible
+ */
+void CPaletteManager::ShowControl(long inType)
+{
+ auto dock = GetControl(inType);
+
+ if (dock) {
+ // Make sure the control is visible
+ dock->setVisible(true);
+ dock->setFocus();
+ }
+}
+
+//=============================================================================
+/**
+ * Flip the visible state of a control
+ */
+void CPaletteManager::ToggleControl(long inType)
+{
+ if (IsControlVisible(inType))
+ HideControl(inType);
+ else
+ ShowControl(inType);
+}
+
+//==============================================================================
+/**
+ * Return the Control (Palette) according to its EControlTypes enum.
+ * @param inType EControlTypes
+ */
+QDockWidget *CPaletteManager::GetControl(long inType) const
+{
+ auto dock = m_ControlList.find(inType);
+ if (dock != m_ControlList.end() && dock->second)
+ return dock->second;
+ else
+ return nullptr;
+}
+
+QWidget *CPaletteManager::getFocusWidget() const
+{
+ TControlMap::const_iterator end = m_ControlList.end();
+ for (TControlMap::const_iterator iter = m_ControlList.begin(); iter != end; ++iter) {
+ if (iter->second->widget()->hasFocus())
+ return iter->second->widget();
+ }
+ return nullptr;
+}
+
+bool CPaletteManager::tabNavigateFocusedWidget(bool tabForward)
+{
+ QWidget *palette = getFocusWidget();
+ if (palette) {
+ if (auto inspector = qobject_cast<InspectorControlView *>(palette)) {
+ inspector->tabOrderHandler()->tabNavigate(tabForward);
+ return true;
+ } else if (auto actionview = qobject_cast<ActionView *>(palette)) {
+ actionview->tabOrderHandler()->tabNavigate(tabForward);
+ return true;
+ }
+ }
+ return false;
+}
+
+ProjectView *CPaletteManager::projectView() const
+{
+ return m_projectView;
+}
+
+void CPaletteManager::EnablePalettes()
+{
+ m_basicObjectsDock->setEnabled(true);
+ m_projectDock->setEnabled(true);
+ m_slideDock->setEnabled(true);
+ m_timelineDock->setEnabled(true);
+ m_actionDock->setEnabled(true);
+ m_inspectorDock->setEnabled(true);
+ m_cameraDock->setEnabled(true);
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h
new file mode 100644
index 00000000..0b672216
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_VIEW_MANAGER_H
+#define INCLUDED_VIEW_MANAGER_H 1
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include <QtWidgets/qdockwidget.h>
+#include <QtCore/qobject.h>
+
+//==============================================================================
+// Forwards
+//==============================================================================
+class CMainFrame;
+class WidgetControl;
+class TimeLineToolbar;
+class TimelineView;
+class ProjectView;
+class TimelineWidget;
+class SceneCameraView;
+
+QT_FORWARD_DECLARE_CLASS(QDockWidget)
+
+//==============================================================================
+/**
+ * @class CPaletteManager
+ */
+class CPaletteManager : public QObject
+{
+ Q_OBJECT
+public:
+ enum EControlTypes {
+ CONTROLTYPE_NONE = 0,
+ CONTROLTYPE_ACTION,
+ CONTROLTYPE_BASICOBJECTS,
+ CONTROLTYPE_INSPECTOR,
+ CONTROLTYPE_SLIDE,
+ CONTROLTYPE_TIMELINE,
+ CONTROLTYPE_PROJECT,
+ CONTROLTYPE_SCENECAMERA,
+ };
+
+protected:
+ typedef std::map<long, QDockWidget *> TControlMap;
+
+protected:
+ CMainFrame *m_MainFrame;
+ TControlMap m_ControlList;
+
+ QDockWidget *m_basicObjectsDock;
+ QDockWidget *m_projectDock;
+ QDockWidget *m_slideDock;
+ QDockWidget *m_timelineQmlDock;
+ QDockWidget *m_timelineDock;
+ QDockWidget *m_actionDock;
+ QDockWidget *m_inspectorDock;
+ QDockWidget *m_cameraDock;
+
+ TimelineView *m_timelineView;
+ ProjectView *m_projectView = nullptr;
+ TimelineWidget *m_timelineWidget;
+ SceneCameraView *m_cameraWidget;
+
+public:
+ CPaletteManager(CMainFrame *inMainFrame, QObject *parent = nullptr);
+ virtual ~CPaletteManager();
+
+ // Access
+ void HideControl(long inType);
+ bool IsControlVisible(long inType) const;
+ void ShowControl(long inType);
+ void ToggleControl(long inType);
+ QDockWidget *GetControl(long inType) const;
+ QWidget *getFocusWidget() const;
+ bool tabNavigateFocusedWidget(bool tabForward);
+ ProjectView *projectView() const;
+
+ // Commands
+ void EnablePalettes();
+};
+
+#endif // INCLUDED_VIEW_MANAGER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui
new file mode 100644
index 00000000..4cb14042
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProgressDlg</class>
+ <widget class="QDialog" name="ProgressDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>360</width>
+ <height>112</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Sub-presentations</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="backgroundWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="progressIcon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../images.qrc">:/images/anim_progress.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignJustify|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>24</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="progressActionText">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Action text goes here</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="progressAdditionalText">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Additional text goes here</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>30</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../../images.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp
new file mode 100644
index 00000000..e6f57687
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ProgressView.h"
+#include "ui_ProgressDlg.h"
+
+#include <QtCore/qtimer.h>
+
+//=============================================================================
+/**
+ * Constructor: Protected because the view is always created dynamically.
+ * You must call Initialize() before trying to use this class.
+ */
+CProgressView::CProgressView(QWidget *parent)
+ : QDialog(parent, Qt::SplashScreen)
+ , m_ui(new Ui::ProgressDlg)
+{
+ m_ui->setupUi(this);
+ m_ui->progressAdditionalText->setWordWrap(true);
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CProgressView::~CProgressView()
+{
+ delete m_ui;
+}
+
+void CProgressView::SetActionText(const QString &inActionText)
+{
+ m_ui->progressActionText->setText(inActionText);
+}
+
+void CProgressView::SetAdditionalText(const QString &inAdditionalText)
+{
+ m_ui->progressAdditionalText->setText(inAdditionalText);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h
new file mode 100644
index 00000000..384c72d7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef INCLUDED_PROGRESS_VIEW_H
+#define INCLUDED_PROGRESS_VIEW_H 1
+
+#pragma once
+
+#include <QtWidgets/qdialog.h>
+
+#include "Qt3DSString.h"
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class ProgressDlg;
+}
+QT_END_NAMESPACE
+
+//=============================================================================
+/**
+ * Windows view encapsulating the splash screen.
+ */
+class CProgressView : public QDialog
+{
+public:
+ CProgressView(QWidget *parent = nullptr);
+ virtual ~CProgressView();
+
+ void SetActionText(const QString &inActionText);
+ void SetAdditionalText(const QString &inAdditionalText);
+
+protected:
+ Ui::ProgressDlg *m_ui;
+};
+
+#endif // INCLUDED_PROGRESS_VIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp
new file mode 100644
index 00000000..de515ad7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ChooseImagePropertyDlg.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Qt3DSDMDataCore.h"
+#include "ui_ChooseImagePropertyDlg.h"
+
+// This dialog displays all texture properties of an object and allows the user to choose one
+ChooseImagePropertyDlg::ChooseImagePropertyDlg(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ bool isRefMaterial,
+ QWidget *parent)
+ : QDialog(parent)
+ , m_instance(inTarget)
+ , m_ui(new Ui::ChooseImagePropertyDlg)
+{
+ m_ui->setupUi(this);
+
+ connect(m_ui->listProps, &QListWidget::itemClicked, this, [this](QListWidgetItem *item) {
+ m_selectedPropHandle = item->isSelected() ? item->data(Qt::UserRole).toInt() : -1;
+ });
+
+ connect(m_ui->listProps, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
+ Q_UNUSED(item)
+ QDialog::accept();
+ });
+
+ if (!isRefMaterial)
+ m_ui->cbDetach->setVisible(false);
+
+ fillList();
+
+ window()->setFixedSize(size());
+}
+
+ChooseImagePropertyDlg::~ChooseImagePropertyDlg()
+{
+ delete m_ui;
+}
+
+void ChooseImagePropertyDlg::setTextureTitle()
+{
+ setWindowTitle(tr("Set texture"));
+ m_ui->label->setText(tr("Set texture to:"));
+}
+
+bool ChooseImagePropertyDlg::detachMaterial() const
+{
+ return m_ui->cbDetach->checkState() == Qt::Checked;
+}
+
+// fill the list with all material properties of type image
+void ChooseImagePropertyDlg::fillList()
+{
+ qt3dsdm::IPropertySystem *propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem();
+ qt3dsdm::TPropertyHandleList props;
+ propertySystem->GetAggregateInstanceProperties(m_instance, props);
+ for (auto &p : props) {
+ auto metaDataType = propertySystem->GetAdditionalMetaDataType(m_instance, p);
+ if (metaDataType == qt3dsdm::AdditionalMetaDataType::Value::Image) {
+ QString propName = QString::fromStdWString(propertySystem->GetFormalName(m_instance, p)
+ .wide_str());
+ QListWidgetItem *newItem = new QListWidgetItem(propName);
+ newItem->setData(Qt::UserRole, QVariant(p));
+ m_ui->listProps->addItem(newItem);
+ }
+ }
+
+ if (m_ui->listProps->count() > 0) {
+ // select the first item by default
+ m_ui->listProps->setCurrentRow(0);
+ m_selectedPropHandle = m_ui->listProps->currentItem()->data(Qt::UserRole).toInt();
+ }
+}
+
+int ChooseImagePropertyDlg::getSelectedPropertyHandle() const
+{
+ return m_selectedPropHandle;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h
new file mode 100644
index 00000000..3b6153ea
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CHOOSEIMAGEPROPERTYDLG_H
+#define CHOOSEIMAGEPROPERTYDLG_H
+
+#include "Qt3DSDMHandles.h"
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class ChooseImagePropertyDlg;
+}
+QT_END_NAMESPACE
+
+class ChooseImagePropertyDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit ChooseImagePropertyDlg(qt3dsdm::Qt3DSDMInstanceHandle instance,
+ bool isRefMaterial = false,
+ QWidget *parent = 0);
+ ~ChooseImagePropertyDlg();
+
+ int getSelectedPropertyHandle() const;
+ bool detachMaterial() const;
+ void setTextureTitle(); // make title/label show 'texture' instead of 'sub-presentation'
+
+private:
+ Ui::ChooseImagePropertyDlg *m_ui;
+ qt3dsdm::Qt3DSDMInstanceHandle m_instance;
+ int m_selectedPropHandle = -1;
+
+ void fillList();
+};
+
+#endif // CHOOSEIMAGEPROPERTYDLG_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui
new file mode 100644
index 00000000..3e3d3924
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChooseImagePropertyDlg</class>
+ <widget class="QDialog" name="ChooseImagePropertyDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>250</width>
+ <height>250</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>150</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>1000</width>
+ <height>1000</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Set sub-presentation</string>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Set sub-presentation to:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="listProps"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cbDetach">
+ <property name="toolTip">
+ <string>Detach from referenced material</string>
+ </property>
+ <property name="text">
+ <string>Detach Material</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ChooseImagePropertyDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>240</x>
+ <y>240</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ChooseImagePropertyDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>240</x>
+ <y>240</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp
new file mode 100644
index 00000000..323a6ed1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp
@@ -0,0 +1,197 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "EditPresentationIdDlg.h"
+#include "ui_EditPresentationIdDlg.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Dialogs.h"
+
+EditPresentationIdDlg::EditPresentationIdDlg(const QString &src, DialogType type, QWidget *parent)
+ : QDialog(parent)
+ , m_src(src)
+ , m_ui(new Ui::EditPresentationIdDlg)
+ , m_dialogType(type)
+{
+ m_ui->setupUi(this);
+
+ switch (m_dialogType) {
+ case EditPresentationId:
+ m_ui->label->setText(tr("Presentation Id"));
+ setWindowTitle(tr("Edit Presentation Id"));
+ break;
+ case EditQmlStreamId:
+ m_ui->label->setText(tr("Qml Stream Id"));
+ setWindowTitle(tr("Edit Qml Stream Id"));
+ break;
+ case EditPresentationName:
+ m_ui->label->setText(tr("Presentation Name"));
+ setWindowTitle(tr("Rename Presentation"));
+ break;
+ case EditQmlStreamName:
+ m_ui->label->setText(tr("Qml Stream Name"));
+ setWindowTitle(tr("Rename Qml Stream"));
+ break;
+ case DuplicatePresentation:
+ m_ui->label->setText(tr("Presentation Name"));
+ setWindowTitle(tr("Duplicate Presentation"));
+ break;
+ case DuplicateQmlStream:
+ m_ui->label->setText(tr("Qml Stream Name"));
+ setWindowTitle(tr("Duplicate Qml Stream"));
+ break;
+ default:
+ break;
+ }
+
+ if (m_dialogType == EditPresentationId || m_dialogType == EditQmlStreamId) {
+ m_presentationId = g_StudioApp.GetCore()->getProjectFile().getPresentationId(src);
+ m_ui->lineEditPresentationId->setText(m_presentationId);
+ } else {
+ QFileInfo fi(src);
+ QString initialText = fi.completeBaseName();
+ if (m_dialogType == DuplicatePresentation || m_dialogType == DuplicateQmlStream)
+ initialText = g_StudioApp.GetCore()->getProjectFile().getUniquePresentationName(src);
+ m_ui->lineEditPresentationId->setText(initialText);
+ }
+
+ window()->setFixedSize(size());
+}
+
+void EditPresentationIdDlg::accept()
+{
+ QString newValue = m_ui->lineEditPresentationId->text();
+ if (newValue.isEmpty()) {
+ displayWarning(EmptyWarning);
+ } else if (m_dialogType == EditPresentationId || m_dialogType == EditQmlStreamId) {
+ if (newValue != m_presentationId) {
+ if (!g_StudioApp.GetCore()->getProjectFile().isUniquePresentationId(newValue, m_src)) {
+ displayWarning(UniqueWarning);
+ } else {
+ g_StudioApp.GetCore()->getProjectFile().writePresentationId(newValue, m_src);
+ QDialog::accept();
+ }
+ } else {
+ QDialog::accept();
+ }
+ } else { // editing name
+ QFileInfo fi(m_src);
+ QString suffix = QStringLiteral(".") + fi.suffix();
+ if (!newValue.endsWith(suffix))
+ newValue.append(suffix);
+
+ if (newValue == suffix) {
+ // If we are left with just the suffix, treat it as an empty name
+ displayWarning(EmptyWarning);
+ } else {
+ int slashIndex = m_src.lastIndexOf(QLatin1Char('/'));
+ if (slashIndex >= 0)
+ newValue.prepend(m_src.left(slashIndex + 1));
+
+ if (newValue != m_src) {
+ bool success = false;
+ if (m_dialogType == DuplicatePresentation || m_dialogType == DuplicateQmlStream) {
+ success = g_StudioApp.GetCore()->getProjectFile().duplicatePresentation(
+ m_src, newValue);
+ if (success) {
+ m_duplicateFile = g_StudioApp.GetCore()->getProjectFile()
+ .getAbsoluteFilePathTo(newValue);
+ }
+ } else {
+ success = g_StudioApp.GetCore()->getProjectFile().renamePresentationFile(
+ m_src, newValue);
+ }
+ if (success)
+ QDialog::accept();
+ else
+ displayWarning(UniqueWarning);
+ } else {
+ QDialog::accept();
+ }
+ }
+ }
+}
+
+void EditPresentationIdDlg::displayWarning(WarningType warningType)
+{
+ QString warning;
+ QString uniqueFileNote;
+ if (warningType == UniqueWarning)
+ uniqueFileNote = tr("The new name must be unique within its folder and a valid filename.");
+
+ switch (m_dialogType) {
+ // Presentation Id warnings are also displayed from preferences dialog, so they are handled
+ // by CStudioApp.
+ case EditPresentationId:
+ if (warningType == EmptyWarning)
+ g_StudioApp.showPresentationIdEmptyWarning();
+ else
+ g_StudioApp.showPresentationIdUniqueWarning();
+ return;
+ case EditQmlStreamId:
+ if (warningType == EmptyWarning)
+ warning = tr("Qml stream Id must not be empty.");
+ else
+ warning = tr("Qml stream Id must be unique.");
+ break;
+ case EditPresentationName:
+ if (warningType == EmptyWarning)
+ warning = tr("Presentation name must not be empty.");
+ else
+ warning = tr("Renaming presentation failed.\n") + uniqueFileNote;
+ break;
+ case EditQmlStreamName:
+ if (warningType == EmptyWarning)
+ warning = tr("Qml stream name must not be empty.");
+ else
+ warning = tr("Renaming Qml stream failed.\n") + uniqueFileNote;
+ break;
+ case DuplicatePresentation:
+ if (warningType == EmptyWarning)
+ warning = tr("Presentation name must not be empty.");
+ else
+ warning = tr("Duplicating presentation failed.\n") + uniqueFileNote;
+ break;
+ case DuplicateQmlStream:
+ if (warningType == EmptyWarning)
+ warning = tr("Qml stream name must not be empty.");
+ else
+ warning = tr("Duplicating Qml stream failed.\n") + uniqueFileNote;
+ break;
+ default:
+ break;
+ }
+
+ g_StudioApp.GetDialogs()->DisplayMessageBox(tr("Warning"), warning,
+ Qt3DSMessageBox::ICON_WARNING, false);
+}
+
+EditPresentationIdDlg::~EditPresentationIdDlg()
+{
+ delete m_ui;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h
new file mode 100644
index 00000000..30bfccbd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef EDITPRESENTATIONIDDLG_H
+#define EDITPRESENTATIONIDDLG_H
+
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class EditPresentationIdDlg;
+}
+QT_END_NAMESPACE
+
+class EditPresentationIdDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ enum DialogType {
+ EditPresentationId,
+ EditQmlStreamId,
+ EditPresentationName,
+ EditQmlStreamName,
+ DuplicatePresentation,
+ DuplicateQmlStream
+ };
+
+ explicit EditPresentationIdDlg(const QString &src, DialogType type = EditPresentationId,
+ QWidget *parent = nullptr);
+ ~EditPresentationIdDlg();
+
+ QString getDuplicateFile() const { return m_duplicateFile; }
+
+public Q_SLOTS:
+ void accept() override;
+
+private:
+ enum WarningType {
+ EmptyWarning,
+ UniqueWarning
+ };
+ void displayWarning(WarningType warningType);
+
+ Ui::EditPresentationIdDlg *m_ui;
+ QString m_src; // src attribute value for the current presentation in the project file
+ QString m_presentationId;
+ DialogType m_dialogType;
+ QString m_duplicateFile;
+};
+
+#endif // EDITPRESENTATIONIDDLG_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui
new file mode 100644
index 00000000..23fbf8e9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditPresentationIdDlg</class>
+ <widget class="QDialog" name="EditPresentationIdDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>300</width>
+ <height>100</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Edit Presentation Id</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="labelEditLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Presentation Id</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditPresentationId"/>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>EditPresentationIdDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>EditPresentationIdDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp
new file mode 100644
index 00000000..5c985359
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp
@@ -0,0 +1,231 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ProjectContextMenu.h"
+#include "ProjectView.h"
+
+ProjectContextMenu::ProjectContextMenu(ProjectView *parent, int index)
+ : QMenu(parent)
+ , m_view(parent)
+ , m_index(index)
+{
+ QAction *action = nullptr;
+ QAction *openFileAction = nullptr;
+ QAction *deleteFileAction = nullptr;
+
+ if (!m_view->isFolder(m_index)) {
+ openFileAction = new QAction(tr("Open"));
+ connect(openFileAction, &QAction::triggered, this, &ProjectContextMenu::handleOpenFile);
+ addAction(openFileAction);
+
+ deleteFileAction = new QAction(tr("Delete"));
+ connect(deleteFileAction, &QAction::triggered, this, &ProjectContextMenu::handleDeleteFile);
+ // Delete file action is added after other file actions later
+ }
+
+ if (m_view->isPresentation(m_index)) {
+ const bool currentPresentation = m_view->isCurrentPresentation(m_index);
+ openFileAction->setText(tr("Open Presentation"));
+ openFileAction->setEnabled(!currentPresentation);
+
+ deleteFileAction->setEnabled(!currentPresentation);
+
+ action = new QAction(tr("Rename Presentation"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRenamePresentation);
+ addAction(action);
+
+ action = new QAction(tr("Edit Presentation Id"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleEditPresentationId);
+ addAction(action);
+
+ action = new QAction(tr("Duplicate Presentation"));
+ connect(action, &QAction::triggered,
+ this, &ProjectContextMenu::handleDuplicatePresentation);
+ addAction(action);
+
+ static const QIcon iconInitial = QIcon(QStringLiteral(":/images/initial_notUsed.png"));
+
+ if (m_view->isInitialPresentation(m_index)) {
+ action = new QAction(iconInitial, tr("Initial Presentation"));
+ // This action does nothing, it's merely informative, so let's disable it
+ action->setEnabled(false);
+ } else {
+ action = new QAction(tr("Set as Initial Presentation"));
+ if (m_view->presentationId(m_index).isEmpty()) {
+ action->setEnabled(false);
+ } else {
+ connect(action, &QAction::triggered,
+ this, &ProjectContextMenu::handleInitialPresentation);
+ }
+ }
+ addAction(action);
+ } else if (m_view->isQmlStream(m_index)) {
+ action = new QAction(tr("Rename Qml Stream"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRenameQmlStream);
+ addAction(action);
+
+ action = new QAction(tr("Edit Qml Stream Id"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleEditQmlStreamId);
+ addAction(action);
+
+ action = new QAction(tr("Duplicate Qml Stream"));
+ connect(action, &QAction::triggered,
+ this, &ProjectContextMenu::handleDuplicatePresentation);
+ addAction(action);
+ }
+
+ if (m_view->isMaterialData(m_index)) {
+ openFileAction->setText(tr("Edit"));
+
+ action = new QAction(tr("Duplicate"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleDuplicate);
+ addAction(action);
+ }
+
+ if (deleteFileAction) {
+ deleteFileAction->setEnabled(deleteFileAction->isEnabled()
+ && !m_view->isReferenced(m_index));
+ addAction(deleteFileAction);
+ }
+
+ addSeparator();
+
+ action = new QAction(tr("Show Containing Folder"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleShowContainingFolder);
+ addAction(action);
+
+ addSeparator();
+
+ action = new QAction(tr("Copy Path"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleCopyPath);
+ addAction(action);
+
+ action = new QAction(tr("Copy Full Path"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleCopyFullPath);
+ addAction(action);
+
+ addSeparator();
+
+ action = new QAction(tr("Import Assets..."));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleImportAssets);
+ addAction(action);
+
+ if (m_view->isMaterialFolder(m_index) || m_view->isInMaterialFolder(m_index)) {
+ addSeparator();
+ QMenu *createMenu = addMenu(tr("Create"));
+ action = new QAction(tr("Basic Material"));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleAddMaterial);
+ createMenu->addAction(action);
+ }
+
+ if (m_view->isRefreshable(m_index)) {
+ addSeparator();
+ action = new QAction(tr("Refresh Import..."));
+ connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRefreshImport);
+ addAction(action);
+ }
+}
+
+ProjectContextMenu::~ProjectContextMenu()
+{
+}
+
+void ProjectContextMenu::handleOpenFile()
+{
+ m_view->openFile(m_index);
+}
+
+void ProjectContextMenu::handleEditPresentationId()
+{
+ m_view->editPresentationId(m_index, false);
+}
+
+void ProjectContextMenu::handleEditQmlStreamId()
+{
+ m_view->editPresentationId(m_index, true);
+}
+
+void ProjectContextMenu::handleShowContainingFolder()
+{
+ m_view->showContainingFolder(m_index);
+}
+
+void ProjectContextMenu::handleCopyPath()
+{
+ m_view->copyPath(m_index);
+}
+
+void ProjectContextMenu::handleCopyFullPath()
+{
+ m_view->copyFullPath(m_index);
+}
+
+void ProjectContextMenu::handleRefreshImport()
+{
+ m_view->refreshImport(m_index);
+}
+
+void ProjectContextMenu::handleImportAssets()
+{
+ m_view->assetImportInContext(m_index);
+}
+
+void ProjectContextMenu::handleAddMaterial()
+{
+ m_view->addMaterial(m_index);
+}
+
+void ProjectContextMenu::handleDuplicate()
+{
+ m_view->duplicate(m_index);
+}
+
+void ProjectContextMenu::handleDuplicatePresentation()
+{
+ m_view->duplicatePresentation(m_index);
+}
+
+void ProjectContextMenu::handleInitialPresentation()
+{
+ m_view->setInitialPresentation(m_index);
+}
+
+void ProjectContextMenu::handleRenamePresentation()
+{
+ m_view->renamePresentation(m_index, false);
+}
+
+void ProjectContextMenu::handleRenameQmlStream()
+{
+ m_view->renamePresentation(m_index, true);
+}
+
+void ProjectContextMenu::handleDeleteFile()
+{
+ m_view->deleteFile(m_index);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h
new file mode 100644
index 00000000..12cd2367
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PROJECT_CONTEXT_MENU_H
+#define PROJECT_CONTEXT_MENU_H
+
+#include <QtWidgets/qmenu.h>
+
+class ProjectView;
+
+class ProjectContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ explicit ProjectContextMenu(ProjectView *parent, int index);
+ virtual ~ProjectContextMenu();
+
+private Q_SLOTS:
+ void handleOpenFile();
+ void handleEditPresentationId();
+ void handleEditQmlStreamId();
+ void handleShowContainingFolder();
+ void handleCopyPath();
+ void handleCopyFullPath();
+ void handleRefreshImport();
+ void handleImportAssets();
+ void handleAddMaterial();
+ void handleDuplicate();
+ void handleDuplicatePresentation();
+ void handleInitialPresentation();
+ void handleRenamePresentation();
+ void handleRenameQmlStream();
+ void handleDeleteFile();
+
+private:
+ ProjectView *m_view;
+ int m_index;
+};
+#endif // PROJECT_CONTEXT_MENU_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp
new file mode 100644
index 00000000..4f62cd59
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp
@@ -0,0 +1,1372 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qtAuthoring-config.h"
+#include <QtCore/qset.h>
+#include <QtCore/qtimer.h>
+
+#include "PresentationFile.h"
+#include "Qt3DSCommonPrecompile.h"
+#include "ProjectFileSystemModel.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSFileTools.h"
+#include "ImportUtils.h"
+#include "Dialogs.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSImportTranslation.h"
+#include "Qt3DSMessageBox.h"
+#include "IDocumentEditor.h"
+#include "IDragable.h"
+#include "IObjectReferenceHelper.h"
+#include "IDirectoryWatchingSystem.h"
+
+ProjectFileSystemModel::ProjectFileSystemModel(QObject *parent) : QAbstractListModel(parent)
+ , m_model(new QFileSystemModel(this))
+{
+ connect(m_model, &QAbstractItemModel::rowsInserted, this, &ProjectFileSystemModel::modelRowsInserted);
+ connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ProjectFileSystemModel::modelRowsRemoved);
+ connect(m_model, &QAbstractItemModel::layoutChanged, this, &ProjectFileSystemModel::modelLayoutChanged);
+ connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::presentationIdChanged,
+ this, &ProjectFileSystemModel::handlePresentationIdChange);
+ connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::assetNameChanged,
+ this, &ProjectFileSystemModel::asyncUpdateReferences);
+
+ m_projectReferencesUpdateTimer.setSingleShot(true);
+ m_projectReferencesUpdateTimer.setInterval(0);
+
+ connect(&m_projectReferencesUpdateTimer, &QTimer::timeout,
+ this, &ProjectFileSystemModel::updateProjectReferences);
+}
+
+QHash<int, QByteArray> ProjectFileSystemModel::roleNames() const
+{
+ auto modelRoleNames = m_model->roleNames();
+ modelRoleNames.insert(IsExpandableRole, "_isExpandable");
+ modelRoleNames.insert(IsDraggableRole, "_isDraggable");
+ modelRoleNames.insert(IsReferencedRole, "_isReferenced");
+ modelRoleNames.insert(IsProjectReferencedRole, "_isProjectReferenced");
+ modelRoleNames.insert(DepthRole, "_depth");
+ modelRoleNames.insert(ExpandedRole, "_expanded");
+ modelRoleNames.insert(FileIdRole, "_fileId");
+ modelRoleNames.insert(ExtraIconRole, "_extraIcon");
+ return modelRoleNames;
+}
+
+int ProjectFileSystemModel::rowCount(const QModelIndex &) const
+{
+ return m_items.count();
+}
+
+QVariant ProjectFileSystemModel::data(const QModelIndex &index, int role) const
+{
+ const auto &item = m_items.at(index.row());
+
+ switch (role) {
+ case Qt::DecorationRole: {
+ QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return StudioUtils::resourceImageUrl() + getIconName(path);
+ }
+
+ case IsExpandableRole: {
+ if (item.index == m_rootIndex) {
+ return false;
+ } else {
+ return hasVisibleChildren(item.index);
+ }
+ }
+
+ case IsDraggableRole:
+ return QFileInfo(item.index.data(QFileSystemModel::FilePathRole).toString()).isFile();
+
+ case IsReferencedRole: {
+ const QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return m_references.contains(path);
+ }
+
+ case IsProjectReferencedRole: {
+ const QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ return m_projectReferences.contains(path);
+ }
+
+ case DepthRole:
+ return item.depth;
+
+ case ExpandedRole:
+ return item.expanded;
+
+ case FileIdRole: {
+ const QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ EStudioObjectType iconType = getIconType(path);
+ if (iconType == OBJTYPE_PRESENTATION || iconType == OBJTYPE_QML_STREAM)
+ return presentationId(path);
+ else
+ return {};
+ }
+
+ case ExtraIconRole: {
+ const QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+ EStudioObjectType iconType = getIconType(path);
+ if (iconType == OBJTYPE_PRESENTATION || iconType == OBJTYPE_QML_STREAM) {
+ if (presentationId(path).isEmpty())
+ return QStringLiteral("warning.png");
+ else
+ return {};
+ } else {
+ return {};
+ }
+ }
+
+ default:
+ return m_model->data(item.index, role);
+ }
+}
+
+QMimeData *ProjectFileSystemModel::mimeData(const QModelIndexList &indexes) const
+{
+ const QString path = filePath(indexes.first().row()); // can only drag one item
+ return CDropSourceFactory::Create(QT3DS_FLAVOR_ASSET_UICFILE, path);
+}
+
+QString ProjectFileSystemModel::filePath(int row) const
+{
+ if (row < 0 || row >= m_items.size())
+ return QString();
+ const auto &item = m_items.at(row);
+ return item.index.data(QFileSystemModel::FilePathRole).toString();
+}
+
+bool ProjectFileSystemModel::isRefreshable(int row) const
+{
+ const QString path = filePath(row);
+ // Import needs to be refreshable even if it is not referenced, as user may drag just individual
+ // meshes into the scene, and not the whole import.
+ return path.endsWith(QLatin1String(".import"));
+}
+
+void ProjectFileSystemModel::updateReferences()
+{
+ m_references.clear();
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const auto sourcePathList = bridge->GetSourcePathList();
+ const auto fontFileList = bridge->GetFontFileList();
+ const auto effectTextureList = bridge->GetDynamicObjectTextureList();
+ auto renderableList = bridge->getRenderableList();
+ auto subpresentationRecord = g_StudioApp.m_subpresentations;
+
+ const QDir projectDir(doc->GetCore()->getProjectFile().getProjectPath());
+ const QString projectPath = QDir::cleanPath(projectDir.absolutePath());
+ const QString projectPathSlash = projectPath + QLatin1Char('/');
+
+ // Add current presentation to renderables list
+ renderableList.insert(doc->getPresentationId());
+ subpresentationRecord.push_back(
+ SubPresentationRecord({}, doc->getPresentationId(),
+ projectDir.relativeFilePath(doc->GetDocumentPath())));
+
+ auto addReferencesPresentation = [this, doc, &projectPath](const QString &str) {
+ addPathsToReferences(m_references, projectPath, doc->GetResolvedPathToDoc(str));
+ };
+ auto addReferencesRenderable = [this, &projectPath, &projectPathSlash, &subpresentationRecord]
+ (const QString &id) {
+ for (SubPresentationRecord r : qAsConst(subpresentationRecord)) {
+ if (r.m_id == id)
+ addPathsToReferences(m_references, projectPath, projectPathSlash + r.m_argsOrSrc);
+ }
+ };
+
+ std::for_each(sourcePathList.begin(), sourcePathList.end(), addReferencesPresentation);
+ std::for_each(fontFileList.begin(), fontFileList.end(), addReferencesPresentation);
+ std::for_each(effectTextureList.begin(), effectTextureList.end(), addReferencesPresentation);
+ std::for_each(renderableList.begin(), renderableList.end(), addReferencesRenderable);
+
+ m_references.insert(projectPath);
+
+ updateRoles({IsReferencedRole, Qt::DecorationRole});
+}
+
+/**
+ * Checks if file is already imported and if not, adds it to outImportedFiles
+ *
+ * @param importFile The new imported file to check
+ * @param outImportedFiles List of already imported files
+ * @return true if importFile was added
+ */
+bool ProjectFileSystemModel::addUniqueImportFile(const QString &importFile,
+ QStringList &outImportedFiles) const
+{
+ const QString cleanPath = QFileInfo(importFile).canonicalFilePath();
+ if (outImportedFiles.contains(cleanPath)) {
+ return false;
+ } else {
+ outImportedFiles.append(cleanPath);
+ return true;
+ }
+}
+
+/**
+ * Copy a file with option to override an existing file or skip the override.
+ *
+ * @param srcFile The source file to copy.
+ * @param targetFile The destination file path.
+ * @param outImportedFiles list of absolute source paths of the dependent assets that are imported
+ * in the same import context.
+ * @param outOverrideChoice The copy skip/override choice used in this import context.
+ */
+void ProjectFileSystemModel::overridableCopyFile(const QString &srcFile, const QString &targetFile,
+ QStringList &outImportedFiles,
+ int &outOverrideChoice) const
+{
+ QFileInfo srcFileInfo(srcFile);
+ if (srcFileInfo.exists() && addUniqueImportFile(srcFile, outImportedFiles)) {
+ QFileInfo targetFileInfo(targetFile);
+ if (srcFileInfo == targetFileInfo)
+ return; // Autoskip when source and target is the same
+ if (!targetFileInfo.dir().exists())
+ targetFileInfo.dir().mkpath(QStringLiteral("."));
+
+ if (targetFileInfo.exists()) { // asset exists, show override / skip box
+ if (outOverrideChoice == QMessageBox::YesToAll) {
+ QFile::remove(targetFile);
+ } else if (outOverrideChoice == QMessageBox::NoToAll) {
+ // QFile::copy() does not override files
+ } else {
+ QString pathFromRoot = QDir(g_StudioApp.GetCore()->getProjectFile()
+ .getProjectPath())
+ .relativeFilePath(targetFile);
+ outOverrideChoice = g_StudioApp.GetDialogs()
+ ->displayOverrideAssetBox(pathFromRoot);
+ if (outOverrideChoice & (QMessageBox::Yes | QMessageBox::YesToAll))
+ QFile::remove(targetFile);
+ }
+ }
+ QFile::copy(srcFile, targetFile);
+ }
+}
+
+void ProjectFileSystemModel::updateProjectReferences()
+{
+ m_projectReferences.clear();
+
+ const QDir projectDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+ const QString projectPath = QDir::cleanPath(projectDir.absolutePath());
+
+ QHashIterator<QString, bool> updateIt(m_projectReferencesUpdateMap);
+ while (updateIt.hasNext()) {
+ updateIt.next();
+ m_presentationReferences.remove(updateIt.key());
+ if (updateIt.value()) {
+ QFileInfo fi(updateIt.key());
+ QDir fileDir = fi.dir();
+ const QString suffix = fi.suffix();
+ QHash<QString, QString> importPathMap;
+ QSet<QString> newReferences;
+
+ const auto addReferencesFromImportMap = [&]() {
+ QHashIterator<QString, QString> pathIter(importPathMap);
+ while (pathIter.hasNext()) {
+ pathIter.next();
+ const QString path = pathIter.key();
+ QString targetAssetPath;
+ if (path.startsWith(QLatin1String("./"))) // path from project root
+ targetAssetPath = projectDir.absoluteFilePath(path);
+ else // relative path
+ targetAssetPath = fileDir.absoluteFilePath(path);
+ newReferences.insert(QDir::cleanPath(targetAssetPath));
+ }
+ };
+
+ if (CDialogs::presentationExtensions().contains(suffix)
+ || CDialogs::qmlStreamExtensions().contains(suffix)) {
+ // Presentation file added/modified, check that it is one of the subpresentations,
+ // or we don't care about it
+ const QString relPath = g_StudioApp.GetCore()->getProjectFile()
+ .getRelativeFilePathTo(updateIt.key());
+ for (int i = 0, count = g_StudioApp.m_subpresentations.size(); i < count; ++i) {
+ SubPresentationRecord &rec = g_StudioApp.m_subpresentations[i];
+ if (rec.m_argsOrSrc == relPath) {
+ if (rec.m_type == QLatin1String("presentation")) {
+ // Since this is not actual import, source and target uip is the same,
+ // and we are only interested in the absolute paths of the "imported"
+ // asset files
+ QString dummyStr;
+ QHash<QString, QString> dummyMap;
+ QSet<QString> dummyDataInputSet;
+ QSet<QString> dummyDataOutputSet;
+ PresentationFile::getSourcePaths(fi, fi, importPathMap,
+ dummyStr, dummyMap, dummyDataInputSet,
+ dummyDataOutputSet);
+ addReferencesFromImportMap();
+ } else { // qml-stream
+ QQmlApplicationEngine qmlEngine;
+ bool isQmlStream = false;
+ QObject *qmlRoot = getQmlStreamRootNode(qmlEngine, updateIt.key(),
+ isQmlStream);
+ if (qmlRoot && isQmlStream) {
+ QSet<QString> assetPaths;
+ getQmlAssets(qmlRoot, assetPaths);
+ QDir qmlDir = fi.dir();
+ for (auto &assetSrc : qAsConst(assetPaths)) {
+ QString targetAssetPath;
+ targetAssetPath = qmlDir.absoluteFilePath(assetSrc);
+ newReferences.insert(QDir::cleanPath(targetAssetPath));
+ }
+ }
+ }
+ break;
+ }
+ }
+ } else if (CDialogs::materialExtensions().contains(suffix)
+ || CDialogs::effectExtensions().contains(suffix)) {
+ // Use dummy set, as we are only interested in values set in material files
+ QSet<QString> dummySet;
+ g_StudioApp.GetCore()->GetDoc()->GetDocumentReader()
+ .ParseSourcePathsOutOfEffectFile(
+ updateIt.key(),
+ g_StudioApp.GetCore()->getProjectFile().getProjectPath(),
+ false, // No need to recurse src mats; those get handled individually
+ importPathMap, dummySet);
+ addReferencesFromImportMap();
+ }
+ if (!newReferences.isEmpty())
+ m_presentationReferences.insert(updateIt.key(), newReferences);
+ }
+ }
+
+ // Update reference cache
+ QHashIterator<QString, QSet<QString>> presIt(m_presentationReferences);
+ while (presIt.hasNext()) {
+ presIt.next();
+ const auto &refs = presIt.value();
+ for (auto &ref : refs)
+ addPathsToReferences(m_projectReferences, projectPath, ref);
+ }
+
+ m_projectReferencesUpdateMap.clear();
+ updateRoles({IsProjectReferencedRole});
+}
+
+void ProjectFileSystemModel::getQmlAssets(const QObject *qmlNode,
+ QSet<QString> &outAssetPaths) const
+{
+ QString assetSrc = qmlNode->property("source").toString(); // absolute file path
+
+ if (!assetSrc.isEmpty()) {
+ // remove file:///
+ if (assetSrc.startsWith(QLatin1String("file:///")))
+ assetSrc = assetSrc.mid(8);
+ else if (assetSrc.startsWith(QLatin1String("file://")))
+ assetSrc = assetSrc.mid(7);
+
+#if !defined(Q_OS_WIN)
+ // Only windows has drive letter in the path, other platforms need to start with /
+ assetSrc.prepend(QLatin1Char('/'));
+#endif
+ outAssetPaths.insert(assetSrc);
+ }
+
+ // recursively load child nodes
+ const QObjectList qmlNodeChildren = qmlNode->children();
+ for (auto &node : qmlNodeChildren)
+ getQmlAssets(node, outAssetPaths);
+}
+
+QObject *ProjectFileSystemModel::getQmlStreamRootNode(QQmlApplicationEngine &qmlEngine,
+ const QString &filePath,
+ bool &outIsQmlStream) const
+{
+ QObject *qmlRoot = nullptr;
+ outIsQmlStream = false;
+
+ qmlEngine.load(filePath);
+ if (qmlEngine.rootObjects().size() > 0) {
+ qmlRoot = qmlEngine.rootObjects().at(0);
+ const char *rootClassName = qmlEngine.rootObjects().at(0)
+ ->metaObject()->superClass()->className();
+ // The assumption here is that any qml that is not a behavior is a qml stream
+ if (strcmp(rootClassName, "Q3DStudio::Q3DSQmlBehavior") != 0)
+ outIsQmlStream = true;
+ }
+
+ return qmlRoot;
+}
+
+Q3DStudio::DocumentEditorFileType::Enum ProjectFileSystemModel::assetTypeForRow(int row)
+{
+ if (row <= 0 || row >= m_items.size())
+ return Q3DStudio::DocumentEditorFileType::Unknown;
+
+ const QString rootPath = m_items[0].index.data(QFileSystemModel::FilePathRole).toString();
+ QString path = m_items[row].index.data(QFileSystemModel::FilePathRole).toString();
+ QFileInfo fi(path);
+ if (!fi.isDir())
+ path = fi.absolutePath();
+ path = path.mid(rootPath.length() + 1);
+ const int slash = path.indexOf(QLatin1String("/"));
+ if (slash >= 0)
+ path = path.left(slash);
+ if (path == QLatin1String("effects"))
+ return Q3DStudio::DocumentEditorFileType::Effect;
+ else if (path == QLatin1String("fonts"))
+ return Q3DStudio::DocumentEditorFileType::Font;
+ else if (path == QLatin1String("maps"))
+ return Q3DStudio::DocumentEditorFileType::Image;
+ else if (path == QLatin1String("materials"))
+ return Q3DStudio::DocumentEditorFileType::Material;
+ else if (path == QLatin1String("models"))
+ return Q3DStudio::DocumentEditorFileType::DAE;
+ else if (path == QLatin1String("scripts"))
+ return Q3DStudio::DocumentEditorFileType::Behavior;
+ else if (path == QLatin1String("presentations"))
+ return Q3DStudio::DocumentEditorFileType::Presentation;
+ else if (path == QLatin1String("qml"))
+ return Q3DStudio::DocumentEditorFileType::QmlStream;
+
+ return Q3DStudio::DocumentEditorFileType::Unknown;
+}
+
+void ProjectFileSystemModel::setRootPath(const QString &path)
+{
+ m_projectReferences.clear();
+ m_presentationReferences.clear();
+ m_projectReferencesUpdateMap.clear();
+ m_projectReferencesUpdateTimer.stop();
+
+ // Delete the old model. If the new project is in a totally different directory tree, not
+ // doing this will result in unexplicable crashes when trying to parse something that should
+ // not be parsed.
+ disconnect(m_model, &QAbstractItemModel::rowsInserted,
+ this, &ProjectFileSystemModel::modelRowsInserted);
+ disconnect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, &ProjectFileSystemModel::modelRowsRemoved);
+ disconnect(m_model, &QAbstractItemModel::layoutChanged,
+ this, &ProjectFileSystemModel::modelLayoutChanged);
+ delete m_model;
+ m_model = new QFileSystemModel(this);
+ connect(m_model, &QAbstractItemModel::rowsInserted,
+ this, &ProjectFileSystemModel::modelRowsInserted);
+ connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, &ProjectFileSystemModel::modelRowsRemoved);
+ connect(m_model, &QAbstractItemModel::layoutChanged,
+ this, &ProjectFileSystemModel::modelLayoutChanged);
+
+ setRootIndex(m_model->setRootPath(path));
+
+ // Open the presentations folder by default
+ connect(this, &ProjectFileSystemModel::dataChanged,
+ this, &ProjectFileSystemModel::asyncExpandPresentations);
+
+ QTimer::singleShot(0, [this]() {
+ // Watch the project directory for changes to .uip files.
+ // Note that this initial connection will notify creation for all files, so we call it
+ // asynchronously to ensure the subpresentations are registered.
+ m_directoryConnection = g_StudioApp.getDirectoryWatchingSystem().AddDirectory(
+ g_StudioApp.GetCore()->getProjectFile().getProjectPath(),
+ std::bind(&ProjectFileSystemModel::onFilesChanged, this,
+ std::placeholders::_1));
+ });
+}
+
+void ProjectFileSystemModel::setRootIndex(const QModelIndex &rootIndex)
+{
+ if (rootIndex == m_rootIndex)
+ return;
+
+ clearModelData();
+
+ m_rootIndex = rootIndex;
+
+ beginInsertRows({}, 0, 0);
+ m_items.append({ m_rootIndex, 0, true, nullptr, 0 });
+ endInsertRows();
+
+ showModelTopLevelItems();
+}
+
+void ProjectFileSystemModel::clearModelData()
+{
+ beginResetModel();
+ m_defaultDirToAbsPathMap.clear();
+ m_items.clear();
+ endResetModel();
+}
+
+void ProjectFileSystemModel::showModelTopLevelItems()
+{
+ int rowCount = m_model->rowCount(m_rootIndex);
+
+ if (rowCount == 0) {
+ if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex))
+ m_model->fetchMore(m_rootIndex);
+ } else {
+ showModelChildItems(m_rootIndex, 0, rowCount - 1);
+ }
+}
+
+void ProjectFileSystemModel::showModelChildItems(const QModelIndex &parentIndex, int start, int end)
+{
+ const int parentRow = modelIndexRow(parentIndex);
+ if (parentRow == -1)
+ return;
+
+ Q_ASSERT(isVisible(parentIndex));
+
+ QVector<QModelIndex> rowsToInsert;
+ for (int i = start; i <= end; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (isVisible(childIndex))
+ rowsToInsert.append(childIndex);
+ }
+
+ const int insertCount = rowsToInsert.count();
+ if (insertCount == 0)
+ return;
+
+ auto parent = &m_items[parentRow];
+
+ const int depth = parent->depth + 1;
+ const int startRow = parentRow + parent->childCount + 1;
+
+ beginInsertRows({}, startRow, startRow + insertCount - 1);
+
+ for (auto it = rowsToInsert.rbegin(); it != rowsToInsert.rend(); ++it)
+ m_items.insert(startRow, { *it, depth, false, parent, 0 });
+
+ for (; parent != nullptr; parent = parent->parent)
+ parent->childCount += insertCount;
+
+ endInsertRows();
+
+ // also fetch children so we're notified when files are added or removed in immediate subdirs
+ for (const auto &childIndex : rowsToInsert) {
+ if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex))
+ m_model->fetchMore(childIndex);
+ }
+}
+
+void ProjectFileSystemModel::expand(int row)
+{
+ if (row < 0 || row > m_items.size() - 1 || m_items[row].expanded)
+ return;
+
+ auto &item = m_items[row];
+ const auto &modelIndex = item.index;
+
+ const int rowCount = m_model->rowCount(modelIndex);
+ if (rowCount == 0) {
+ if (m_model->hasChildren(modelIndex) && m_model->canFetchMore(modelIndex))
+ m_model->fetchMore(modelIndex);
+ } else {
+ showModelChildItems(modelIndex, 0, rowCount - 1);
+ }
+
+ item.expanded = true;
+ Q_EMIT dataChanged(index(row), index(row));
+}
+
+bool ProjectFileSystemModel::hasValidUrlsForDropping(const QList<QUrl> &urls) const
+{
+ for (const auto &url : urls) {
+ if (url.isLocalFile()) {
+ const QString path = url.toLocalFile();
+ const QFileInfo fileInfo(path);
+ if (fileInfo.isFile()) {
+ const QString extension = fileInfo.suffix();
+ return extension.compare(QLatin1String(CDialogs::GetDAEFileExtension()),
+ Qt::CaseInsensitive) == 0
+#ifdef QT_3DSTUDIO_FBX
+ || extension.compare(QLatin1String(CDialogs::GetFbxFileExtension()),
+ Qt::CaseInsensitive) == 0
+#endif
+ || getIconType(path) != OBJTYPE_UNKNOWN;
+ }
+ }
+ }
+
+ return false;
+}
+
+void ProjectFileSystemModel::showInfo(int row)
+{
+ if (row < 0 || row >= m_items.size())
+ row = 0;
+
+ const TreeItem &item = m_items.at(row);
+ QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+
+ QFileInfo fi(path);
+
+ if (fi.suffix() == QLatin1String("materialdef")) {
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ bool isDocModified = doc->isModified();
+ { // Scope for the ScopedDocumentEditor
+ Q3DStudio::ScopedDocumentEditor sceneEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString()));
+ const auto material = sceneEditor->getOrCreateMaterial(path);
+ QString name;
+ QMap<QString, QString> values;
+ QMap<QString, QMap<QString, QString>> textureValues;
+ sceneEditor->getMaterialInfo(fi.absoluteFilePath(), name, values, textureValues);
+ sceneEditor->setMaterialValues(fi.absoluteFilePath(), values, textureValues);
+ if (material.Valid())
+ doc->SelectDataModelObject(material);
+ }
+ // Several aspects of the editor are not updated correctly
+ // if the data core is changed without a transaction
+ // The above scope completes the transaction for creating a new material
+ // Next the added undo has to be popped from the stack
+ // and the modified flag has to be restored
+ // TODO: Find a way to update the editor fully without a transaction
+ doc->SetModifiedFlag(isDocModified);
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+ }
+}
+
+void ProjectFileSystemModel::duplicate(int row)
+{
+ if (row < 0 || row >= m_items.size())
+ row = 0;
+
+ const TreeItem &item = m_items.at(row);
+ QString path = item.index.data(QFileSystemModel::FilePathRole).toString();
+
+ QFileInfo srcFile(path);
+ const QString destPathStart = srcFile.dir().absolutePath() + QLatin1Char('/')
+ + srcFile.completeBaseName() + QStringLiteral(" Copy");
+ const QString destPathEnd = QStringLiteral(".") + srcFile.suffix();
+ QString destPath = destPathStart + destPathEnd;
+
+ int i = 1;
+ while (QFile::exists(destPath)) {
+ i++;
+ destPath = destPathStart + QString::number(i) + destPathEnd;
+ }
+
+ QFile::copy(path, destPath);
+}
+
+void ProjectFileSystemModel::importUrls(const QList<QUrl> &urls, int row, bool autoSort)
+{
+ if (row < 0 || row >= m_items.size())
+ row = 0; // Import to root folder row not specified
+
+ // If importing via buttons or doing in-context import to project root,
+ // sort imported items to default folders according to their type
+ const bool sortToDefaults = autoSort || row == 0;
+ if (sortToDefaults)
+ updateDefaultDirMap();
+
+ const TreeItem &item = m_items.at(row);
+ QString targetPath = item.index.data(QFileSystemModel::FilePathRole).toString();
+
+ QFileInfo fi(targetPath);
+ if (!fi.isDir())
+ targetPath = fi.absolutePath();
+ const QDir targetDir(targetPath);
+
+ QStringList expandPaths;
+ QHash<QString, QString> presentationNodes; // <relative path to presentation, presentation id>
+ // List of all files that have been copied by this import. Used to avoid duplicate imports
+ // due to some of the imported files also being assets used by other imported files.
+ QStringList importedFiles;
+ QMap<QString, CDataInputDialogItem *> importedDataInputs;
+ int overrideChoice = QMessageBox::NoButton;
+
+ for (const auto &url : urls) {
+ QString sortedPath = targetPath;
+ QDir sortedDir = targetDir;
+
+ if (sortToDefaults) {
+ const QString defaultDir = m_defaultDirToAbsPathMap.value(
+ g_StudioApp.GetDialogs()->defaultDirForUrl(url));
+ if (!defaultDir.isEmpty()) {
+ sortedPath = defaultDir;
+ sortedDir.setPath(sortedPath);
+ }
+ }
+
+ if (sortedDir.exists()) {
+ importUrl(sortedDir, url, presentationNodes, importedFiles, importedDataInputs,
+ overrideChoice);
+ expandPaths << sortedDir.path();
+ }
+ }
+
+ // Batch update all imported presentation nodes
+ g_StudioApp.GetCore()->getProjectFile().addPresentationNodes(presentationNodes);
+
+ // Add new data inputs that are missing from project's data inputs. Duplicates are ignored,
+ // even if they are different type.
+ QMapIterator<QString, CDataInputDialogItem *> diIt(importedDataInputs);
+ bool addedDi = false;
+ while (diIt.hasNext()) {
+ diIt.next();
+ if (!g_StudioApp.m_dataInputDialogItems.contains(diIt.key())) {
+ g_StudioApp.m_dataInputDialogItems.insert(diIt.key(), diIt.value());
+ addedDi = true;
+ } else {
+ delete diIt.value();
+ }
+ }
+ if (addedDi) {
+ g_StudioApp.saveDataInputsToProjectFile();
+ g_StudioApp.checkDeletedDatainputs(); // Updates externalPresBoundTypes
+ }
+
+ for (const QString &expandPath : qAsConst(expandPaths)) {
+ int expandRow = rowForPath(expandPath);
+ if (expandRow >= 0 && !m_items[expandRow].expanded)
+ expand(expandRow);
+ }
+}
+
+/**
+ * Imports a single asset and the assets it depends on.
+ *
+ * @param targetDir Target path where the asset is imported to
+ * @param url Source url where the asset is imported from
+ * @param outPresentationNodes Map where presentation node information is stored for later
+ * registration. The key is relative path to presentation. The value
+ * is presentation id.
+ * @param outImportedFiles List of absolute source paths of the dependent assets that are imported
+ * in the same import context.
+ * @param outDataInputs Map of data inputs that are in use in this import context.
+ * @param outOverrideChoice The copy skip/override choice used in this import context.
+ */
+void ProjectFileSystemModel::importUrl(QDir &targetDir, const QUrl &url,
+ QHash<QString, QString> &outPresentationNodes,
+ QStringList &outImportedFiles,
+ QMap<QString, CDataInputDialogItem *> &outDataInputs,
+ int &outOverrideChoice) const
+{
+ using namespace Q3DStudio;
+ using namespace qt3dsimp;
+ // Drag and Drop - From Explorer window to Project Palette
+ // For all valid Project File Types:
+ // - This performs a file copy from the source Explorer location to the selected Project Palette
+ // Folder
+ // - The destination copy must NOT be read-only even if the source is read-only
+ // For DAE, it will import the file.
+
+ if (!url.isLocalFile())
+ return;
+
+ const QString sourceFile = url.toLocalFile();
+
+ const QFileInfo fileInfo(sourceFile);
+ if (!fileInfo.isFile())
+ return;
+
+ // Skip importing if the file has already been imported
+ if (!addUniqueImportFile(sourceFile, outImportedFiles))
+ return;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+
+ const QString extension = fileInfo.suffix();
+ const QString fileStem = fileInfo.baseName();
+ const QString outputFileName = QStringLiteral("%1.%2").arg(fileStem).arg(CDialogs::GetImportFileExtension());
+
+ if (extension.compare(QLatin1String(CDialogs::GetDAEFileExtension()), Qt::CaseInsensitive) == 0) {
+ SColladaTranslator translator(sourceFile);
+ const QDir outputDir = SFileTools::FindUniqueDestDirectory(targetDir, fileStem);
+ const QString fullOutputFile = outputDir.filePath(outputFileName);
+ const SImportResult importResult =
+ CPerformImport::TranslateToImportFile(translator, CFilePath(fullOutputFile));
+ bool forceError = QFileInfo(fullOutputFile).isFile() == false;
+ IDocumentEditor::DisplayImportErrors(
+ sourceFile, importResult.m_Error, doc->GetImportFailedHandler(),
+ translator.m_TranslationLog, forceError);
+#ifdef QT_3DSTUDIO_FBX
+ } else if (extension.compare(QLatin1String(CDialogs::GetFbxFileExtension()), Qt::CaseInsensitive) == 0) {
+ SFbxTranslator translator(sourceFile);
+ const QDir outputDir = SFileTools::FindUniqueDestDirectory(targetDir, fileStem);
+ const QString fullOutputFile = outputDir.filePath(outputFileName);
+ const SImportResult importResult =
+ CPerformImport::TranslateToImportFile(translator, CFilePath(fullOutputFile));
+ bool forceError = QFileInfo(fullOutputFile).isFile() == false;
+ IDocumentEditor::DisplayImportErrors(
+ sourceFile, importResult.m_Error, doc->GetImportFailedHandler(),
+ translator.m_TranslationLog, forceError);
+#endif
+ } else {
+ QQmlApplicationEngine qmlEngine;
+ QObject *qmlRoot = nullptr;
+ bool isQmlStream = false;
+ if (extension == QLatin1String("qml")) {
+ qmlRoot = getQmlStreamRootNode(qmlEngine, sourceFile, isQmlStream);
+ if (qmlRoot) {
+ if (isQmlStream && targetDir.path().endsWith(QLatin1String("/scripts"))) {
+ const QString path(QStringLiteral("../qml"));
+ targetDir.mkpath(path); // create the folder if doesn't exist
+ targetDir.cd(path);
+ }
+ } else {
+ // Invalid qml file, block import
+ g_StudioApp.GetDialogs()->DisplayKnownErrorDialog(
+ tr("Failed to parse '%1'\nAborting import.").arg(sourceFile));
+ return;
+ }
+ }
+ // Copy the file to target directory
+ // FindAndCopyDestFile will make sure the file name is unique and make sure it is
+ // not read only.
+ QString destPath; // final file path (after copying and renaming)
+ bool copyResult = SFileTools::FindAndCopyDestFile(targetDir, sourceFile, destPath);
+ Q_ASSERT(copyResult);
+
+ QString presentationPath;
+ if (CDialogs::isPresentationFileExtension(extension.toLatin1().data())) {
+ presentationPath = doc->GetCore()->getProjectFile().getRelativeFilePathTo(destPath);
+ QSet<QString> dataInputs;
+ QSet<QString> dataOutputs;
+ importPresentationAssets(fileInfo, QFileInfo(destPath), outPresentationNodes,
+ outImportedFiles, dataInputs, dataOutputs, outOverrideChoice);
+ const QString projFile = PresentationFile::findProjectFile(fileInfo.absoluteFilePath());
+
+ // #TODO: Handle DataOutputs QT3DS-3510
+ QMap<QString, CDataInputDialogItem *> allDataInputs;
+ ProjectFile::loadDataInputs(projFile, allDataInputs);
+ for (auto &di : dataInputs) {
+ if (allDataInputs.contains(di))
+ outDataInputs.insert(di, allDataInputs[di]);
+ }
+ } else if (qmlRoot && isQmlStream) { // importing a qml stream
+ presentationPath = doc->GetCore()->getProjectFile().getRelativeFilePathTo(destPath);
+ importQmlAssets(qmlRoot, fileInfo.dir(), targetDir, outImportedFiles,
+ outOverrideChoice);
+ }
+
+ // outPresentationNodes can already contain this presentation in case of multi-importing
+ // both a presentation and its subpresentation
+ if (!presentationPath.isEmpty() && !outPresentationNodes.contains(presentationPath)) {
+ const QString srcProjFile = PresentationFile::findProjectFile(sourceFile);
+ QString presId;
+ if (!srcProjFile.isEmpty()) {
+ QVector<SubPresentationRecord> subpresentations;
+ ProjectFile::getPresentations(srcProjFile, subpresentations);
+ QDir srcProjDir(QFileInfo(srcProjFile).path());
+ const QString relSrcPresFilePath = srcProjDir.relativeFilePath(sourceFile);
+ auto *sp = std::find_if(
+ subpresentations.begin(), subpresentations.end(),
+ [&relSrcPresFilePath](const SubPresentationRecord &spr) -> bool {
+ return spr.m_argsOrSrc == relSrcPresFilePath;
+ });
+ // Make sure we are not adding a duplicate id. In that case presId will be empty
+ // which causes autogeneration of an unique id.
+ if (sp != subpresentations.end()
+ && g_StudioApp.GetCore()->getProjectFile().isUniquePresentationId(sp->m_id)) {
+ presId = sp->m_id;
+ }
+ }
+ outPresentationNodes.insert(presentationPath, presId);
+ }
+
+ // For effect and custom material files, automatically copy related resources
+ if (CDialogs::IsEffectFileExtension(extension.toLatin1().data())
+ || CDialogs::IsMaterialFileExtension(extension.toLatin1().data())) {
+ QHash<QString, QString> effectFileSourcePaths;
+ QString absSrcPath = fileInfo.absoluteFilePath();
+ QString projectPath
+ = QFileInfo(PresentationFile::findProjectFile(absSrcPath)).absolutePath();
+ // Since we are importing a bare material/effect, we don't care about possible dynamic
+ // values of texture properties
+ QSet<QString> dummyPropertySet;
+ g_StudioApp.GetCore()->GetDoc()->GetDocumentReader()
+ .ParseSourcePathsOutOfEffectFile(absSrcPath, projectPath, true,
+ effectFileSourcePaths, dummyPropertySet);
+
+ QHashIterator<QString, QString> pathIter(effectFileSourcePaths);
+ while (pathIter.hasNext()) {
+ pathIter.next();
+ overridableCopyFile(pathIter.value(),
+ QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath())
+ .absoluteFilePath(pathIter.key()),
+ outImportedFiles, outOverrideChoice);
+ }
+ }
+ }
+}
+
+/**
+ * Import all assets used in a uip file, this includes materials and effects (and their assets),
+ * images, fonts, subpresentations (and their recursive assets), models and scripts. Assets are
+ * imported in the same relative structure in the imported-from folder in order not to break the
+ * assets paths.
+ *
+ * @param uipSrc source file path where the uip is imported from
+ * @param uipTarget target path where the uip is imported to
+ * @param outPresentationNodes map where presentation node information is stored for later
+ * registration. The key is relative path to presentation. The value
+ * is presentation id.
+ * @param overrideChoice tracks user choice (yes to all / no to all) to maintain the value through
+ * recursive calls
+ * @param outImportedFiles list of absolute source paths of the dependent assets that are imported
+ * in the same import context.
+ * @param outDataInputs set of data input identifiers that are in use by this presentation and its
+ * subpresentations.
+ * @param outOverrideChoice The copy skip/override choice used in this import context.
+ */
+void ProjectFileSystemModel::importPresentationAssets(
+ const QFileInfo &uipSrc, const QFileInfo &uipTarget,
+ QHash<QString, QString> &outPresentationNodes, QStringList &outImportedFiles,
+ QSet<QString> &outDataInputs, QSet<QString> &outDataOutputs, int &outOverrideChoice) const
+{
+ QHash<QString, QString> importPathMap;
+ QString projPathSrc; // project absolute path for the source uip
+ PresentationFile::getSourcePaths(uipSrc, uipTarget, importPathMap, projPathSrc,
+ outPresentationNodes, outDataInputs, outDataOutputs);
+ const QDir projDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+ const QDir uipSrcDir = uipSrc.dir();
+ const QDir uipTargetDir = uipTarget.dir();
+
+ QHashIterator<QString, QString> pathIter(importPathMap);
+ while (pathIter.hasNext()) {
+ pathIter.next();
+ QString srcAssetPath = pathIter.value();
+ const QString path = pathIter.key();
+ QString targetAssetPath;
+ if (srcAssetPath.isEmpty())
+ srcAssetPath = uipSrcDir.absoluteFilePath(path);
+ targetAssetPath = uipTargetDir.absoluteFilePath(path);
+
+ overridableCopyFile(srcAssetPath, targetAssetPath, outImportedFiles, outOverrideChoice);
+
+ if (path.endsWith(QLatin1String(".uip"))) {
+ // recursively load any uip asset's assets
+ importPresentationAssets(QFileInfo(srcAssetPath), QFileInfo(targetAssetPath),
+ outPresentationNodes, outImportedFiles, outDataInputs,
+ outDataOutputs, outOverrideChoice);
+
+ // update the path in outPresentationNodes to be correctly relative in target project
+ const QString subId = outPresentationNodes.take(path);
+ if (!subId.isEmpty())
+ outPresentationNodes.insert(projDir.relativeFilePath(targetAssetPath), subId);
+ } else if (path.endsWith(QLatin1String(".qml"))) {
+ // recursively load any qml stream assets
+ QQmlApplicationEngine qmlEngine;
+ bool isQmlStream = false;
+ QObject *qmlRoot = getQmlStreamRootNode(qmlEngine, srcAssetPath, isQmlStream);
+ if (qmlRoot && isQmlStream) {
+ importQmlAssets(qmlRoot, QFileInfo(srcAssetPath).dir(),
+ QFileInfo(targetAssetPath).dir(), outImportedFiles,
+ outOverrideChoice);
+ // update path in outPresentationNodes to be correctly relative in target project
+ const QString subId = outPresentationNodes.take(path);
+ if (!subId.isEmpty())
+ outPresentationNodes.insert(projDir.relativeFilePath(targetAssetPath), subId);
+ }
+ }
+ }
+}
+
+/**
+ * Import all assets specified in "source" properties in a qml file.
+ *
+ * @param qmlNode The qml node to checkfor assets. Recursively checks all child nodes, too.
+ * @param srcDir target dir where the assets are imported to
+ * @param outImportedFiles list of absolute source paths of the dependent assets that are imported
+ * in the same import context.
+ * @param outOverrideChoice The copy skip/override choice used in this import context.
+ */
+void ProjectFileSystemModel::importQmlAssets(const QObject *qmlNode, const QDir &srcDir,
+ const QDir &targetDir,
+ QStringList &outImportedFiles,
+ int &outOverrideChoice) const
+{
+ QSet<QString> assetPaths;
+ getQmlAssets(qmlNode, assetPaths);
+
+ for (auto &assetSrc : qAsConst(assetPaths)) {
+ overridableCopyFile(srcDir.absoluteFilePath(assetSrc),
+ targetDir.absoluteFilePath(srcDir.relativeFilePath(assetSrc)),
+ outImportedFiles, outOverrideChoice);
+ }
+}
+
+int ProjectFileSystemModel::rowForPath(const QString &path) const
+{
+ for (int i = m_items.size() - 1; i >= 0 ; --i) {
+ const QString itemPath = m_items[i].index.data(QFileSystemModel::FilePathRole).toString();
+ if (path == itemPath)
+ return i;
+ }
+ return -1;
+}
+
+void ProjectFileSystemModel::updateRoles(const QVector<int> &roles, int startRow, int endRow)
+{
+ Q_EMIT dataChanged(index(startRow, 0),
+ index(endRow < 0 ? rowCount() - 1 : endRow, 0), roles);
+}
+
+void ProjectFileSystemModel::collapse(int row)
+{
+ Q_ASSERT(row >= 0 && row < m_items.size());
+
+ auto &item = m_items[row];
+ Q_ASSERT(item.expanded == true);
+
+ const int childCount = item.childCount;
+
+ if (childCount > 0) {
+ beginRemoveRows({}, row + 1, row + childCount);
+
+ m_items.erase(std::begin(m_items) + row + 1, std::begin(m_items) + row + 1 + childCount);
+
+ for (auto parent = &item; parent != nullptr; parent = parent->parent)
+ parent->childCount -= childCount;
+
+ endRemoveRows();
+ }
+
+ item.expanded = false;
+ Q_EMIT dataChanged(index(row), index(row));
+}
+
+int ProjectFileSystemModel::modelIndexRow(const QModelIndex &modelIndex) const
+{
+ auto it = std::find_if(
+ std::begin(m_items),
+ std::end(m_items),
+ [&modelIndex](const TreeItem &item)
+ {
+ return item.index == modelIndex;
+ });
+
+ return it != std::end(m_items) ? std::distance(std::begin(m_items), it) : -1;
+}
+
+bool ProjectFileSystemModel::isExpanded(const QModelIndex &modelIndex) const
+{
+ if (modelIndex == m_rootIndex)
+ return true;
+ const int row = modelIndexRow(modelIndex);
+ return row != -1 && m_items.at(row).expanded;
+}
+
+EStudioObjectType ProjectFileSystemModel::getIconType(const QString &path) const
+{
+ return Q3DStudio::ImportUtils::GetObjectFileTypeForFile(path).m_IconType;
+}
+
+QString ProjectFileSystemModel::getIconName(const QString &path) const
+{
+ QString iconName;
+
+ bool referenced = m_references.contains(path);
+
+ QFileInfo fileInfo(path);
+ if (fileInfo.isFile()) {
+ EStudioObjectType type = getIconType(path);
+
+ if (type == OBJTYPE_PRESENTATION) {
+ const bool isCurrent = isCurrentPresentation(path);
+ const bool isInitial = isInitialPresentation(path);
+ if (isInitial) {
+ iconName = isCurrent ? QStringLiteral("initial_used.png")
+ : QStringLiteral("initial_notUsed.png");
+ } else if (isCurrent) {
+ iconName = QStringLiteral("presentation_edit.png");
+ }
+ }
+
+ if (iconName.isEmpty()) {
+ if (type != OBJTYPE_UNKNOWN) {
+ iconName = referenced ? CStudioObjectTypes::GetNormalIconName(type)
+ : CStudioObjectTypes::GetDisabledIconName(type);
+ } else {
+ iconName = referenced ? QStringLiteral("Objects-Layer-Normal.png")
+ : QStringLiteral("Objects-Layer-Disabled.png");
+ }
+ }
+ } else {
+ iconName = referenced ? QStringLiteral("Objects-Folder-Normal.png")
+ : QStringLiteral("Objects-Folder-Disabled.png");
+ }
+
+ return iconName;
+}
+
+bool ProjectFileSystemModel::hasVisibleChildren(const QModelIndex &modelIndex) const
+{
+ const QDir dir(modelIndex.data(QFileSystemModel::FilePathRole).toString());
+ if (!dir.exists() || dir.isEmpty())
+ return false;
+
+ const auto fileInfoList = dir.entryInfoList(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot);
+ for (const auto &fileInfo : fileInfoList) {
+ if (fileInfo.isDir() || getIconType(fileInfo.filePath()) != OBJTYPE_UNKNOWN)
+ return true;
+ }
+
+ return false;
+}
+
+bool ProjectFileSystemModel::isVisible(const QModelIndex &modelIndex) const
+{
+ QString path = modelIndex.data(QFileSystemModel::FilePathRole).toString();
+
+ if (modelIndex == m_rootIndex || QFileInfo(path).isDir())
+ return true;
+
+ if (path.endsWith(QLatin1String("_autosave.uip"))
+ || path.endsWith(QLatin1String("_@preview@.uip"))
+ || path.endsWith(QLatin1String(".uia"))) {
+ return false;
+ }
+
+ return getIconType(path) != OBJTYPE_UNKNOWN;
+}
+
+void ProjectFileSystemModel::modelRowsInserted(const QModelIndex &parent, int start, int end)
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ if (isExpanded(parent)) {
+ showModelChildItems(parent, start, end);
+ } else {
+ if (hasVisibleChildren(parent)) {
+ // show expand arrow
+ const int row = modelIndexRow(parent);
+ Q_EMIT dataChanged(index(row), index(row));
+ }
+ }
+}
+
+void ProjectFileSystemModel::modelRowsRemoved(const QModelIndex &parent, int start, int end)
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ if (isExpanded(parent)) {
+ for (int i = start; i <= end; ++i) {
+ const int row = modelIndexRow(m_model->index(i, 0, parent));
+
+ if (row != -1) {
+ const auto &item = m_items.at(row);
+
+ beginRemoveRows({}, row, row + item.childCount);
+
+ for (auto parent = item.parent; parent != nullptr; parent = parent->parent)
+ parent->childCount -= 1 + item.childCount;
+
+ m_items.erase(std::begin(m_items) + row,
+ std::begin(m_items) + row + item.childCount + 1);
+
+ endRemoveRows();
+ }
+ }
+ }
+
+ if (!hasVisibleChildren(parent)) {
+ // collapse the now empty folder
+ const int row = modelIndexRow(parent);
+ if (m_items[row].expanded)
+ collapse(row);
+ else
+ Q_EMIT dataChanged(index(row), index(row));
+ }
+}
+
+void ProjectFileSystemModel::modelLayoutChanged()
+{
+ if (!m_rootIndex.isValid())
+ return;
+
+ QSet<QPersistentModelIndex> expandedItems;
+ for (const auto &item : m_items) {
+ if (item.expanded)
+ expandedItems.insert(item.index);
+ }
+
+ const std::function<int(const QModelIndex &, TreeItem *)> insertChildren = [this, &expandedItems, &insertChildren](const QModelIndex &parentIndex, TreeItem *parent)
+ {
+ Q_ASSERT(isVisible(parentIndex));
+
+ const int rowCount = m_model->rowCount(parentIndex);
+ const int depth = parent->depth + 1;
+
+ int childCount = 0;
+
+ for (int i = 0; i < rowCount; ++i) {
+ const auto &childIndex = m_model->index(i, 0, parentIndex);
+ if (isVisible(childIndex)) {
+ const bool expanded = expandedItems.contains(childIndex);
+ m_items.append({ childIndex, depth, expanded, parent, 0 });
+ auto &item = m_items.last();
+ if (expanded) {
+ item.childCount = insertChildren(childIndex, &item);
+ childCount += item.childCount;
+ }
+ ++childCount;
+ }
+ }
+
+ return childCount;
+ };
+
+ const int itemCount = m_items.count();
+
+ m_items.erase(std::begin(m_items) + 1, std::end(m_items));
+ m_items.reserve(itemCount);
+ insertChildren(m_rootIndex, &m_items.first());
+
+ Q_ASSERT(m_items.count() == itemCount);
+
+ Q_EMIT dataChanged(index(0), index(itemCount - 1));
+}
+
+void ProjectFileSystemModel::updateDefaultDirMap()
+{
+ if (m_defaultDirToAbsPathMap.isEmpty()) {
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("effects"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("fonts"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("maps"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("materials"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("models"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("scripts"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("presentations"), QString());
+ m_defaultDirToAbsPathMap.insert(QStringLiteral("qml"), QString());
+ }
+
+ const QString rootPath = m_items[0].index.data(QFileSystemModel::FilePathRole).toString();
+ const QStringList keys = m_defaultDirToAbsPathMap.keys();
+ for (const QString &key : keys) {
+ QString currentValue = m_defaultDirToAbsPathMap[key];
+ if (currentValue.isEmpty()) {
+ const QString defaultPath = rootPath + QLatin1Char('/') + key;
+ const QFileInfo fi(defaultPath);
+ if (fi.exists() && fi.isDir())
+ m_defaultDirToAbsPathMap.insert(key, defaultPath);
+ } else {
+ const QFileInfo fi(currentValue);
+ if (!fi.exists())
+ m_defaultDirToAbsPathMap.insert(key, QString());
+ }
+ }
+}
+
+void ProjectFileSystemModel::addPathsToReferences(QSet<QString> &references,
+ const QString &projectPath,
+ const QString &origPath)
+{
+ references.insert(origPath);
+ QString path = origPath;
+ QString parentPath = QFileInfo(path).path();
+ do {
+ references.insert(path);
+ path = parentPath;
+ parentPath = QFileInfo(path).path();
+ } while (path != projectPath && parentPath != path);
+}
+
+void ProjectFileSystemModel::handlePresentationIdChange(const QString &path, const QString &id)
+{
+ const QString cleanPath = QDir::cleanPath(
+ QDir(g_StudioApp.GetCore()->GetDoc()->GetCore()->getProjectFile()
+ .getProjectPath()).absoluteFilePath(path));
+ int row = rowForPath(cleanPath);
+ m_projectReferencesUpdateMap.insert(cleanPath, true);
+ m_projectReferencesUpdateTimer.start();
+ updateRoles({FileIdRole, ExtraIconRole}, row, row);
+}
+
+void ProjectFileSystemModel::asyncExpandPresentations()
+{
+ disconnect(this, &ProjectFileSystemModel::dataChanged,
+ this, &ProjectFileSystemModel::asyncExpandPresentations);
+
+ // expand presentation folder by default (if it exists).
+ QTimer::singleShot(0, [this]() {
+ QString path = g_StudioApp.GetCore()->getProjectFile().getProjectPath()
+ + QStringLiteral("/presentations");
+ expand(rowForPath(path));
+ });
+}
+
+void ProjectFileSystemModel::asyncUpdateReferences()
+{
+ QTimer::singleShot(0, this, &ProjectFileSystemModel::updateReferences);
+}
+
+void ProjectFileSystemModel::onFilesChanged(
+ const Q3DStudio::TFileModificationList &inFileModificationList)
+{
+ // If any presentation file changes, update asset reference caches
+ for (size_t idx = 0, end = inFileModificationList.size(); idx < end; ++idx) {
+ const Q3DStudio::SFileModificationRecord &record(inFileModificationList[idx]);
+ if (record.m_File.isFile()) {
+ const QString suffix = record.m_File.suffix();
+ const bool isQml = CDialogs::qmlStreamExtensions().contains(suffix);
+ if (isQml || CDialogs::presentationExtensions().contains(suffix)
+ || CDialogs::materialExtensions().contains(suffix)
+ || CDialogs::effectExtensions().contains(suffix)) {
+ const QString filePath = record.m_File.absoluteFilePath();
+ if (record.m_ModificationType == Q3DStudio::FileModificationType::Created
+ || record.m_ModificationType == Q3DStudio::FileModificationType::Modified) {
+ if (isQml && !g_StudioApp.isQmlStream(filePath))
+ continue; // Skip non-stream qml's to match import logic
+ m_projectReferencesUpdateMap.insert(filePath, true);
+ } else if (record.m_ModificationType
+ == Q3DStudio::FileModificationType::Destroyed) {
+ m_projectReferencesUpdateMap.insert(filePath, false);
+ }
+ m_projectReferencesUpdateTimer.start();
+ }
+ }
+ }
+}
+
+bool ProjectFileSystemModel::isCurrentPresentation(const QString &path) const
+{
+ return path == g_StudioApp.GetCore()->GetDoc()->GetDocumentPath();
+}
+
+bool ProjectFileSystemModel::isInitialPresentation(const QString &path) const
+{
+ QString checkId = presentationId(path);
+
+ return !checkId.isEmpty()
+ && checkId == g_StudioApp.GetCore()->getProjectFile().initialPresentation();
+}
+
+QString ProjectFileSystemModel::presentationId(const QString &path) const
+{
+ QString presId;
+ if (isCurrentPresentation(path))
+ presId = g_StudioApp.GetCore()->GetDoc()->getPresentationId();
+ else
+ presId = g_StudioApp.getRenderableId(QFileInfo(path).absoluteFilePath());
+
+ return presId;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h
new file mode 100644
index 00000000..8e9cefb8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h
@@ -0,0 +1,166 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef TREEVIEWADAPTOR_H
+#define TREEVIEWADAPTOR_H
+
+#include "StudioObjectTypes.h"
+#include "DocumentEditorEnumerations.h"
+#include "Qt3DSFileTools.h"
+#include "Dispatch.h"
+
+#include <QtWidgets/qfilesystemmodel.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlapplicationengine.h>
+
+QT_FORWARD_DECLARE_CLASS(QFileSystemModel)
+class CDataInputDialogItem;
+
+class ProjectFileSystemModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit ProjectFileSystemModel(QObject *parent = nullptr);
+
+ enum {
+ IsExpandableRole = QFileSystemModel::FilePermissions + 1,
+ IsDraggableRole,
+ IsReferencedRole,
+ IsProjectReferencedRole, // Means some other presentation in the project uses this file
+ DepthRole,
+ ExpandedRole,
+ FileIdRole,
+ ExtraIconRole
+ };
+
+ void setRootPath(const QString &path);
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = {}) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QMimeData *mimeData(const QModelIndexList &indexes) const override;
+
+ QString filePath(int row) const;
+ bool isRefreshable(int row) const;
+ bool isCurrentPresentation(const QString &path) const;
+ bool isInitialPresentation(const QString &path) const;
+ QString presentationId(const QString &path) const;
+
+ Q3DStudio::DocumentEditorFileType::Enum assetTypeForRow(int row);
+ int rowForPath(const QString &path) const;
+ void updateRoles(const QVector<int> &roles, int startRow = 0, int endRow = -1);
+
+ Q_INVOKABLE void expand(int row);
+ Q_INVOKABLE void collapse(int row);
+
+ Q_INVOKABLE void importUrls(const QList<QUrl> &urls, int row, bool autoSort = true);
+ Q_INVOKABLE bool hasValidUrlsForDropping(const QList<QUrl> &urls) const;
+ Q_INVOKABLE void showInfo(int row);
+ Q_INVOKABLE void duplicate(int row);
+
+ void asyncUpdateReferences();
+ void onFilesChanged(const Q3DStudio::TFileModificationList &inFileModificationList);
+
+Q_SIGNALS:
+ void modelChanged(QAbstractItemModel *model);
+
+private:
+ void setRootIndex(const QModelIndex &rootIndex);
+ void clearModelData();
+ void showModelTopLevelItems();
+ void showModelChildItems(const QModelIndex &parentItem, int start, int end);
+ int modelIndexRow(const QModelIndex &modelIndex) const;
+ bool isExpanded(const QModelIndex &modelIndex) const;
+ QString getIconName(const QString &path) const;
+ EStudioObjectType getIconType(const QString &path) const;
+ bool isVisible(const QModelIndex& modelIndex) const;
+ bool hasVisibleChildren(const QModelIndex &modelIndex) const;
+ void importUrl(QDir &targetDir, const QUrl &url,
+ QHash<QString, QString> &outPresentationNodes,
+ QStringList &outImportedFiles,
+ QMap<QString, CDataInputDialogItem *> &outDataInputs,
+ int &outOverrideChoice) const;
+ void importPresentationAssets(const QFileInfo &uipSrc, const QFileInfo &uipTarget,
+ QHash<QString, QString> &outPresentationNodes,
+ QStringList &outImportedFiles, QSet<QString> &outDataInputs,
+ QSet<QString> &outDataOutputs,int &outOverrideChoice) const;
+
+ void modelRowsInserted(const QModelIndex &parent, int start, int end);
+ void modelRowsRemoved(const QModelIndex &parent, int start, int end);
+ void modelRowsMoved(const QModelIndex &parent, int start, int end);
+ void modelLayoutChanged();
+ void importQmlAssets(const QObject *qmlNode, const QDir &srcDir, const QDir &targetDir,
+ QStringList &outImportedFiles, int &outOverrideChoice) const;
+ void updateDefaultDirMap();
+ void addPathsToReferences(QSet<QString> &references, const QString &projectPath,
+ const QString &origPath);
+ void handlePresentationIdChange(const QString &path, const QString &id);
+ void asyncExpandPresentations();
+ void updateReferences();
+ bool addUniqueImportFile(const QString &importFile, QStringList &outImportedFiles) const;
+ void overridableCopyFile(const QString &srcFile, const QString &targetFile,
+ QStringList &outImportedFiles, int &outOverrideChoice) const;
+ void updateProjectReferences();
+ void getQmlAssets(const QObject *qmlNode, QSet<QString> &outAssetPaths) const;
+ QObject *getQmlStreamRootNode(QQmlApplicationEngine &qmlEngine, const QString &filePath,
+ bool &outIsQmlStream) const;
+
+ struct TreeItem {
+ QPersistentModelIndex index;
+ int depth;
+ bool expanded;
+ TreeItem *parent;
+ int childCount;
+ };
+
+ QFileSystemModel *m_model = nullptr;
+ QPersistentModelIndex m_rootIndex;
+ QList<TreeItem> m_items;
+ QSet<QString> m_references;
+ QHash<QString, QString> m_defaultDirToAbsPathMap;
+
+ // Cache of assets referred by other presentation files and qml streams in the project
+ // Key: Absolute presentation file path
+ // Value: Set of absolute asset file paths referred by the presentation file
+ QHash<QString, QSet<QString>> m_presentationReferences;
+
+ // Compilation of all m_presentationReferences sets and their parent paths
+ QSet<QString> m_projectReferences;
+
+ // Key: uip that needs update
+ // Value: if true, uip was modified or created. If false, it was removed.
+ QHash<QString, bool> m_projectReferencesUpdateMap;
+ QTimer m_projectReferencesUpdateTimer;
+ std::shared_ptr<qt3dsdm::ISignalConnection> m_directoryConnection;
+};
+
+#endif // TREEVIEWADAPTOR_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp
new file mode 100644
index 00000000..dadb5895
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp
@@ -0,0 +1,534 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "ProjectView.h"
+#include "ProjectFileSystemModel.h"
+#include "Core.h"
+#include "Dispatch.h"
+#include "Doc.h"
+#include "Literals.h"
+#include "StudioUtils.h"
+#include "ImportUtils.h"
+#include "StudioApp.h"
+#include "StudioClipboard.h"
+#include "StudioPreferences.h"
+#include "Qt3DSImport.h"
+#include "Dialogs.h"
+#include "IDocumentEditor.h"
+#include "ProjectContextMenu.h"
+#include "EditPresentationIdDlg.h"
+
+#include <QtCore/qprocess.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qdrag.h>
+#include <QtGui/qdesktopservices.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlfile.h>
+#include <QtQuick/qquickitem.h>
+
+ProjectView::ProjectView(const QSize &preferredSize, QWidget *parent) : QQuickWidget(parent)
+ , m_ProjectModel(new ProjectFileSystemModel(this))
+ , m_preferredSize(preferredSize)
+{
+ const QString theApplicationPath = Qt3DSFile::GetApplicationDirectory();
+
+ m_defaultBehaviorDir = theApplicationPath + QStringLiteral("/Content/Behavior Library");
+ m_defaultEffectDir = theApplicationPath + QStringLiteral("/Content/Effect Library");
+ m_defaultFontDir = theApplicationPath + QStringLiteral("/Content/Font Library");
+ m_defaultImageDir = theApplicationPath + QStringLiteral("/Content/Maps Library");
+ m_defaultMaterialDir = theApplicationPath + QStringLiteral("/Content/Material Library");
+ m_defaultModelDir = theApplicationPath + QStringLiteral("/Content/Models Library");
+ m_defaultPresentationDir = theApplicationPath + QStringLiteral("/Content/Presentations");
+ m_defaultQmlStreamDir = theApplicationPath + QStringLiteral("/Content/Qml Streams");
+
+ m_BehaviorDir = m_defaultBehaviorDir;
+ m_EffectDir = m_defaultEffectDir;
+ m_FontDir = m_defaultFontDir;
+ m_ImageDir = m_defaultImageDir;
+ m_MaterialDir = m_defaultMaterialDir;
+ m_ModelDir = m_defaultModelDir;
+ m_presentationDir = m_defaultPresentationDir;
+ m_qmlStreamDir = m_defaultQmlStreamDir;
+
+ m_assetImportDir = theApplicationPath + QStringLiteral("/Content");
+
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &ProjectView::initialize);
+
+ auto dispatch = g_StudioApp.GetCore()->GetDispatch();
+ dispatch->AddPresentationChangeListener(this);
+ dispatch->AddDataModelListener(this);
+ dispatch->AddFileOpenListener(this);
+}
+
+ProjectView::~ProjectView()
+{
+}
+
+QAbstractItemModel *ProjectView::projectModel() const
+{
+ return m_ProjectModel;
+}
+
+QSize ProjectView::sizeHint() const
+{
+ return m_preferredSize;
+}
+
+void ProjectView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_parentView"), this);
+
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Project/ProjectView.qml")));
+}
+
+void ProjectView::effectAction(int row)
+{
+ m_EffectDir = m_defaultEffectDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_EffectDir, Q3DStudio::DocumentEditorFileType::Effect);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::fontAction(int row)
+{
+ m_FontDir = m_defaultFontDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_FontDir, Q3DStudio::DocumentEditorFileType::Font);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::imageAction(int row)
+{
+ m_ImageDir = m_defaultImageDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_ImageDir, Q3DStudio::DocumentEditorFileType::Image);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::materialAction(int row)
+{
+ m_MaterialDir = m_defaultMaterialDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_MaterialDir, Q3DStudio::DocumentEditorFileType::Material);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::modelAction(int row)
+{
+ m_ModelDir = m_defaultModelDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_ModelDir, Q3DStudio::DocumentEditorFileType::DAE);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::presentationAction(int row)
+{
+ m_presentationDir = m_defaultPresentationDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_presentationDir, Q3DStudio::DocumentEditorFileType::Presentation);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::behaviorAction(int row)
+{
+ m_BehaviorDir = m_defaultBehaviorDir;
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_BehaviorDir, Q3DStudio::DocumentEditorFileType::Behavior);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::assetImportAction(int row)
+{
+ QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets(
+ m_assetImportDir, Q3DStudio::DocumentEditorFileType::Unknown);
+ m_ProjectModel->importUrls(urls, row);
+}
+
+void ProjectView::assetImportInContext(int row)
+{
+ // If the context is a default directory, select the correct directory
+ Q3DStudio::DocumentEditorFileType::Enum assetType = m_ProjectModel->assetTypeForRow(row);
+ QString *assetDir = &m_assetImportDir;
+ switch (assetType) {
+ case Q3DStudio::DocumentEditorFileType::Effect:
+ assetDir = &m_EffectDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::Font:
+ assetDir = &m_FontDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::Image:
+ assetDir = &m_ImageDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::Material:
+ assetDir = &m_MaterialDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::DAE:
+ assetDir = &m_ModelDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::Behavior:
+ assetDir = &m_BehaviorDir;
+ break;
+ case Q3DStudio::DocumentEditorFileType::Presentation:
+ assetDir = &m_presentationDir;
+ break;
+ default:
+ break;
+ }
+
+ QList<QUrl> urls;
+ urls = g_StudioApp.GetDialogs()->SelectAssets(*assetDir, assetType);
+ m_ProjectModel->importUrls(urls, row, false);
+}
+
+void ProjectView::OnNewPresentation()
+{
+ rebuild();
+}
+
+void ProjectView::OnOpenDocument(const QString &inFilename, bool inSucceeded)
+{
+ Q_UNUSED(inFilename)
+ Q_UNUSED(inSucceeded)
+}
+
+void ProjectView::OnSaveDocument(const QString &inFilename, bool inSucceeded, bool inSaveCopy)
+{
+ Q_UNUSED(inFilename)
+ Q_UNUSED(inSucceeded)
+ Q_UNUSED(inSaveCopy)
+ m_ProjectModel->asyncUpdateReferences();
+}
+
+void ProjectView::OnDocumentPathChanged(const QString &inNewPath)
+{
+ Q_UNUSED(inNewPath)
+}
+
+void ProjectView::OnBeginDataModelNotifications()
+{
+}
+
+void ProjectView::OnEndDataModelNotifications()
+{
+ m_ProjectModel->asyncUpdateReferences();
+}
+
+void ProjectView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ Q_UNUSED(inInstance);
+}
+
+void ProjectView::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount)
+{
+ Q_UNUSED(inInstance);
+ Q_UNUSED(inInstanceCount);
+}
+
+void ProjectView::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+ QQuickWidget::mousePressEvent(event);
+}
+
+void ProjectView::startDrag(QQuickItem *item, int row)
+{
+ item->grabMouse(); // Grab to make sure we can ungrab after the drag
+ const auto index = m_ProjectModel->index(row);
+
+ QDrag drag(this);
+ drag.setMimeData(m_ProjectModel->mimeData({index}));
+ drag.setPixmap(QPixmap(QQmlFile::urlToLocalFileOrQrc(index.data(Qt::DecorationRole).toUrl())));
+ Qt::DropAction action = Qt::CopyAction;
+ // prevent DnD the currently open presentation and presentations with empty id
+ if (isCurrentPresentation(row) || ((isPresentation(row) || isQmlStream(row))
+ && presentationId(row).isEmpty())) {
+ action = Qt::IgnoreAction;
+ }
+ drag.exec(action);
+
+ // Ungrab to trigger mouse release on the originating item
+ QTimer::singleShot(0, item, &QQuickItem::ungrabMouse);
+}
+
+bool ProjectView::isCurrentPresentation(int row) const
+{
+ return m_ProjectModel->isCurrentPresentation(m_ProjectModel->filePath(row));
+}
+
+void ProjectView::editPresentationId(int row, bool qmlStream)
+{
+ QString relativePresPath = QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath())
+ .relativeFilePath(m_ProjectModel->filePath(row));
+
+ EditPresentationIdDlg dlg(relativePresPath,
+ qmlStream ? EditPresentationIdDlg::EditQmlStreamId
+ : EditPresentationIdDlg::EditPresentationId, this);
+ dlg.exec();
+}
+
+void ProjectView::renamePresentation(int row, bool qmlStream)
+{
+ QString relativePresPath = QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath())
+ .relativeFilePath(m_ProjectModel->filePath(row));
+
+ EditPresentationIdDlg dlg(relativePresPath,
+ qmlStream ? EditPresentationIdDlg::EditQmlStreamName
+ : EditPresentationIdDlg::EditPresentationName, this);
+ dlg.exec();
+}
+
+void ProjectView::showContainingFolder(int row) const
+{
+ if (row == -1)
+ return;
+ const auto path = m_ProjectModel->filePath(row);
+#if defined(Q_OS_WIN)
+ QString param = QStringLiteral("explorer ");
+ if (!QFileInfo(path).isDir())
+ param += QLatin1String("/select,");
+ param += QDir::toNativeSeparators(path).replace(QLatin1String(" "), QLatin1String("\ "));
+ QProcess::startDetached(param);
+#elif defined(Q_OS_MACOS)
+ QProcess::startDetached("/usr/bin/osascript", {"-e",
+ QStringLiteral("tell application \"Finder\" to reveal POSIX file \"%1\"").arg(path)});
+ QProcess::startDetached("/usr/bin/osascript", {"-e",
+ QStringLiteral("tell application \"Finder\" to activate")});
+#else
+ // we cannot select a file here, because no file browser really supports it...
+ QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(path).absolutePath()));
+#endif
+}
+
+void ProjectView::copyPath(int row) const
+{
+ if (row == -1)
+ return;
+
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const QString path = m_ProjectModel->filePath(row);
+ const QString relativePath = doc->GetRelativePathToDoc(path);
+ CStudioClipboard::CopyTextToClipboard(relativePath);
+}
+
+void ProjectView::copyFullPath(int row) const
+{
+ if (row == -1)
+ return;
+ const auto path = m_ProjectModel->filePath(row);
+ CStudioClipboard::CopyTextToClipboard(path);
+}
+
+bool ProjectView::isPresentation(int row) const
+{
+ return m_ProjectModel->filePath(row).endsWith(QLatin1String(".uip"));
+}
+
+bool ProjectView::isQmlStream(int row) const
+{
+ return g_StudioApp.isQmlStream(m_ProjectModel->filePath(row));
+}
+
+bool ProjectView::isMaterialFolder(int row) const
+{
+ return m_ProjectModel->filePath(row).endsWith(QLatin1String("/materials"));
+}
+
+bool ProjectView::isInMaterialFolder(int row) const
+{
+ return g_StudioApp.GetCore()->getProjectFile().getRelativeFilePathTo(
+ m_ProjectModel->filePath(row)).startsWith(QLatin1String("materials/"));
+}
+
+bool ProjectView::isMaterialData(int row) const
+{
+ return m_ProjectModel->filePath(row).endsWith(QLatin1String(".materialdef"));
+}
+
+bool ProjectView::isInitialPresentation(int row) const
+{
+ return m_ProjectModel->isInitialPresentation(m_ProjectModel->filePath(row));
+}
+
+bool ProjectView::isFolder(int row) const
+{
+ return QFileInfo(m_ProjectModel->filePath(row)).isDir();
+}
+
+bool ProjectView::isReferenced(int row) const
+{
+ const auto index = m_ProjectModel->index(row);
+ return index.data(ProjectFileSystemModel::IsReferencedRole).toBool()
+ || index.data(ProjectFileSystemModel::IsProjectReferencedRole).toBool();
+}
+
+QString ProjectView::presentationId(int row) const
+{
+ return m_ProjectModel->presentationId(m_ProjectModel->filePath(row));
+}
+
+void ProjectView::setInitialPresentation(int row)
+{
+ QString setId = presentationId(row);
+
+ // If presentation id is empty, it means .uip is not part of the project. It shouldn't be
+ // possible to set initial presentation in that case.
+ Q_ASSERT(!setId.isEmpty());
+
+ g_StudioApp.GetCore()->getProjectFile().setInitialPresentation(setId);
+ m_ProjectModel->updateRoles({Qt::DecorationRole});
+}
+
+bool ProjectView::isRefreshable(int row) const
+{
+ return m_ProjectModel->isRefreshable(row);
+}
+
+void ProjectView::showContextMenu(int x, int y, int index)
+{
+ ProjectContextMenu contextMenu(this, index);
+ contextMenu.exec(mapToGlobal({x, y}));
+}
+
+bool ProjectView::toolTipsEnabled()
+{
+ return CStudioPreferences::ShouldShowTooltips();
+}
+
+void ProjectView::openFile(int row)
+{
+ if (row == -1)
+ return;
+
+ QFileInfo fi(m_ProjectModel->filePath(row));
+ if (fi.isDir() || isCurrentPresentation(row))
+ return;
+
+ QString filePath = QDir::cleanPath(fi.absoluteFilePath());
+ QTimer::singleShot(0, [filePath, row, this]() {
+ // .uip files should be opened in this studio instance
+ if (filePath.endsWith(QLatin1String(".uip"), Qt::CaseInsensitive)) {
+ if (g_StudioApp.PerformSavePrompt())
+ g_StudioApp.OnLoadDocument(filePath);
+ } else if (filePath.endsWith(QLatin1String(".materialdef"), Qt::CaseInsensitive)) {
+ editMaterial(row);
+ } else {
+ QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
+ }
+ });
+}
+
+void ProjectView::refreshImport(int row) const
+{
+ if (row == -1)
+ return;
+ using namespace Q3DStudio;
+ const auto path = m_ProjectModel->filePath(row);
+ qt3dsimp::ImportPtrOrError importPtr = qt3dsimp::Import::Load(path.toStdWString().c_str());
+ if (importPtr.m_Value) {
+ const auto destDir = importPtr.m_Value->GetDestDir();
+ const auto srcFile = importPtr.m_Value->GetSrcFile();
+ const QString fullSrcPath(QDir(destDir).filePath(srcFile));
+ const QFileInfo oldFile(fullSrcPath);
+ const QFileInfo newFile(g_StudioApp.GetDialogs()->ConfirmRefreshModelFile(fullSrcPath));
+ if (newFile.exists() && newFile.isFile()) {
+ // We don't want to create undo point of "Refresh Import", undoing this sort of
+ // thing is supposed to be done in the DCC tool.
+ g_StudioApp.GetCore()->GetDoc()->getSceneEditor()->RefreshImport(
+ oldFile.canonicalFilePath(), newFile.canonicalFilePath());
+ }
+ }
+}
+
+void ProjectView::addMaterial(int row) const
+{
+ if (row == -1)
+ return;
+
+ QString path = m_ProjectModel->filePath(row);
+ QFileInfo info(path);
+ if (info.isFile())
+ path = info.dir().path();
+ path += QLatin1String("/Material");
+ QString extension = QLatin1String(".materialdef");
+
+ QFile file(path + extension);
+ int i = 1;
+ while (file.exists()) {
+ i++;
+ file.setFileName(path + QString::number(i) + extension);
+ }
+
+ file.open(QIODevice::WriteOnly);
+ file.write("<MaterialData version=\"1.0\">\n</MaterialData>");
+}
+
+void ProjectView::editMaterial(int row) const
+{
+ m_ProjectModel->showInfo(row);
+}
+
+void ProjectView::duplicate(int row) const
+{
+ m_ProjectModel->duplicate(row);
+}
+
+void ProjectView::duplicatePresentation(int row) const
+{
+ g_StudioApp.duplicatePresentation(m_ProjectModel->filePath(row));
+}
+
+void ProjectView::deleteFile(int row) const
+{
+ if (isReferenced(row)) {
+ // Execution should never get here, as menu option is disabled, but since reference cache
+ // updates are asynchronous, it is possible to have situation where menu item is enabled
+ // but deletion is no longer valid when selected.
+ qWarning() << __FUNCTION__ << "Tried to delete referenced file";
+ return;
+ }
+
+ const QString &filePath = m_ProjectModel->filePath(row);
+
+ if (isPresentation(row) || isQmlStream(row)) {
+ // When deleting renderables, project file assets needs to be updated
+ g_StudioApp.GetCore()->getProjectFile().deletePresentationFile(filePath);
+ } else {
+ QFile file(filePath);
+ file.remove();
+ }
+}
+
+void ProjectView::rebuild()
+{
+ m_ProjectModel->setRootPath(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h
new file mode 100644
index 00000000..7533e677
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h
@@ -0,0 +1,149 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef PROJECTVIEW_H
+#define PROJECTVIEW_H
+
+#include "DispatchListeners.h"
+#include "Qt3DSFile.h"
+#include "EditPresentationIdDlg.h"
+
+#include <QQuickWidget>
+#include <QModelIndex>
+
+class ProjectFileSystemModel;
+QT_FORWARD_DECLARE_CLASS(QQuickItem)
+
+class ProjectView : public QQuickWidget,
+ public CPresentationChangeListener,
+ public IDataModelListener,
+ public CFileOpenListener
+
+
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QAbstractItemModel *projectModel READ projectModel NOTIFY projectChanged FINAL)
+
+public:
+ explicit ProjectView(const QSize &preferredSize, QWidget *parent = nullptr);
+ ~ProjectView();
+
+ QSize sizeHint() const override;
+
+ QAbstractItemModel *projectModel() const;
+
+ Q_INVOKABLE void effectAction(int row);
+ Q_INVOKABLE void fontAction(int row);
+ Q_INVOKABLE void imageAction(int row);
+ Q_INVOKABLE void materialAction(int row);
+ Q_INVOKABLE void modelAction(int row);
+ Q_INVOKABLE void presentationAction(int row);
+ Q_INVOKABLE void behaviorAction(int row);
+ Q_INVOKABLE void assetImportAction(int row);
+ void assetImportInContext(int row);
+
+ Q_INVOKABLE void startDrag(QQuickItem *item, int row);
+ Q_INVOKABLE void showContextMenu(int x, int y, int index);
+ Q_INVOKABLE bool toolTipsEnabled();
+ Q_INVOKABLE void openFile(int row);
+
+ void showContainingFolder(int row) const;
+ void copyPath(int row) const;
+ void copyFullPath(int row) const;
+ void refreshImport(int row) const;
+ void addMaterial(int row) const;
+ void editMaterial(int row) const;
+ void duplicate(int row) const;
+ void duplicatePresentation(int row) const;
+ void deleteFile(int row) const;
+
+ bool isRefreshable(int row) const;
+
+ Q_INVOKABLE bool isPresentation(int row) const;
+ Q_INVOKABLE bool isQmlStream(int row) const;
+ bool isCurrentPresentation(int row) const;
+ bool isMaterialFolder(int row) const;
+ bool isInMaterialFolder(int row) const;
+ bool isMaterialData(int row) const;
+ bool isInitialPresentation(int row) const;
+ bool isFolder(int row) const;
+ bool isReferenced(int row) const;
+ QString presentationId(int row) const;
+ void setInitialPresentation(int row);
+ Q_INVOKABLE void editPresentationId(int row, bool qmlStream);
+ void renamePresentation(int row, bool qmlStream);
+
+ // CPresentationChangeListener
+ void OnNewPresentation() override;
+ // CFileOpenListener
+ void OnOpenDocument(const QString &inFilename, bool inSucceeded) override;
+ void OnSaveDocument(const QString &inFilename, bool inSucceeded, bool inSaveCopy) override;
+ void OnDocumentPathChanged(const QString &inNewPath) override;
+ // IDataModelListener
+ void OnBeginDataModelNotifications() override;
+ void OnEndDataModelNotifications() override;
+ // These are used during drag operations or during operations which
+ // require immediate user feedback. So they are unimplemented, effectively,
+ // we ignore them.
+ void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount) override;
+
+Q_SIGNALS:
+ void projectChanged();
+
+protected:
+ void mousePressEvent(QMouseEvent *event) override;
+
+private:
+ void initialize();
+ void rebuild();
+
+ ProjectFileSystemModel *m_ProjectModel = nullptr;
+ QColor m_BaseColor = QColor::fromRgb(75, 75, 75);
+ QString m_defaultBehaviorDir;
+ QString m_defaultEffectDir;
+ QString m_defaultFontDir;
+ QString m_defaultImageDir;
+ QString m_defaultMaterialDir;
+ QString m_defaultModelDir;
+ QString m_defaultPresentationDir;
+ QString m_defaultQmlStreamDir;
+ QString m_BehaviorDir;
+ QString m_EffectDir;
+ QString m_FontDir;
+ QString m_ImageDir;
+ QString m_MaterialDir;
+ QString m_ModelDir;
+ QString m_presentationDir;
+ QString m_qmlStreamDir;
+ QString m_assetImportDir;
+ QSize m_preferredSize;
+};
+
+#endif // PROJECTVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml
new file mode 100644
index 00000000..043d9ba2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml
@@ -0,0 +1,317 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+ id: root
+
+ color: _backgroundColor
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 4
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ onClicked: {
+ _parentView.showContextMenu(mouse.x, mouse.y, projectTree.currentIndex);
+ }
+ }
+
+ ListView {
+ id: projectTree
+
+ anchors.fill: parent
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ model: _parentView.projectModel
+
+ onCurrentIndexChanged: {
+ // Try to keep something selected always
+ if ((currentIndex < 0 || currentIndex >= count) && count > 0)
+ currentIndex = 0;
+ }
+
+ delegate: Rectangle {
+ id: delegateItem
+ property bool dragging: false
+ property bool dragStarted: false
+ property point pressPoint
+ width: parent.width
+ height: 20
+ color: (index == projectTree.currentIndex || dragging) ? _selectionColor
+ : "transparent"
+ function handlePress(mouse, tryDrag) {
+ projectTree.currentIndex = model.index;
+
+ if (mouse.button === Qt.LeftButton && tryDrag && _isDraggable) {
+ pressPoint = Qt.point(mouse.x, mouse.y);
+ dragStarted = false;
+ }
+ }
+
+ function handlePositionChange(mouse, item) {
+ if (_isDraggable && !dragStarted
+ && (Math.abs(mouse.x - pressPoint.x) > 4
+ || Math.abs(mouse.y - pressPoint.y) > 4)) {
+ dragStarted = true;
+ _parentView.startDrag(item, index);
+ }
+ }
+
+ function handleClick(mouse) {
+ if (mouse.button === Qt.RightButton) {
+ var rootPoint = mapToItem(root, mouse.x, mouse.y);
+ _parentView.showContextMenu(rootPoint.x, rootPoint.y,
+ projectTree.currentIndex);
+ }
+ }
+
+ function handleDoubleClick(mouse) {
+ if (mouse.button === Qt.LeftButton) {
+ if (_isExpandable) {
+ if (_expanded)
+ projectTree.model.collapse(index);
+ else
+ projectTree.model.expand(index);
+ } else {
+ _parentView.openFile(index);
+ }
+ }
+ }
+
+ MouseArea {
+ id: delegateMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+
+ onPressed: delegateItem.handlePress(mouse, false)
+ onClicked: delegateItem.handleClick(mouse)
+ onDoubleClicked: delegateItem.handleDoubleClick(mouse)
+
+ Row {
+ x: _depth*28
+ anchors.verticalCenter: parent.verticalCenter
+
+ Image {
+ source: _resDir + (_expanded ? "arrow_down.png" : "arrow.png")
+ opacity: _isExpandable ? 1 : 0
+
+ MouseArea {
+ visible: _isExpandable
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onPressed: delegateItem.handlePress(mouse, false)
+ onClicked: {
+ if (_expanded)
+ projectTree.model.collapse(index)
+ else
+ projectTree.model.expand(index)
+ }
+ }
+ }
+
+ Image {
+ id: fileIconImage
+ source: fileIcon
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ onPressed: delegateItem.handlePress(mouse, true)
+ onPositionChanged: delegateItem.handlePositionChange(
+ mouse, fileIconImage)
+ onClicked: delegateItem.handleClick(mouse)
+ onDoubleClicked: delegateItem.handleDoubleClick(mouse)
+ }
+ }
+
+ StyledLabel {
+ id: fileNameLabel
+ text: _fileId ? fileName + " <" + _fileId + ">" : fileName;
+ color: {
+ _isReferenced ? _textColor
+ : _isProjectReferenced ? _projectReferencedColor
+ : _disabledColor
+ }
+ leftPadding: 2
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ onPressed: delegateItem.handlePress(mouse, true)
+ onPositionChanged: delegateItem.handlePositionChange(
+ mouse, fileNameLabel)
+ onClicked: delegateItem.handleClick(mouse)
+ onDoubleClicked: delegateItem.handleDoubleClick(mouse)
+ }
+ }
+
+ Item {
+ // Spacer item
+ width: 4
+ height: 1
+ }
+
+ Image {
+ source: _extraIcon ? _resDir + _extraIcon : ""
+ visible: _extraIcon ? true : false
+
+ MouseArea {
+ id: warningMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ hoverEnabled: true
+ onPressed: delegateItem.handlePress(mouse, false)
+ onClicked: delegateItem.handleClick(mouse)
+ onDoubleClicked: _parentView.editPresentationId(
+ index, _parentView.isQmlStream(index))
+ }
+ StyledTooltip {
+ text: _parentView.isPresentation(index)
+ ? qsTr("No presentation Id")
+ : qsTr("No Qml stream Id")
+ enabled: warningMouseArea.containsMouse
+ }
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+
+ onEntered: {
+ if (drag.hasUrls
+ && projectTree.model.hasValidUrlsForDropping(drag.urls)) {
+ dragging = true;
+ drag.accept(Qt.CopyAction)
+ } else {
+ drag.accepted = false;
+ }
+ }
+
+ onExited: {
+ dragging = false;
+ }
+
+ onDropped: {
+ if (drop.hasUrls)
+ projectTree.model.importUrls(drop.urls, index, false);
+ dragging = false;
+ }
+ }
+ }
+ DropArea {
+ // Leftover listview area. Dropping here is equivalent to dropping to root
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height - parent.contentHeight
+ onEntered: {
+ if (drag.hasUrls && projectTree.model.hasValidUrlsForDropping(drag.urls))
+ drag.accept(Qt.CopyAction)
+ else
+ drag.accepted = false;
+ }
+ onDropped: {
+ if (drop.hasUrls)
+ projectTree.model.importUrls(drop.urls, 0, false)
+ }
+ }
+ }
+ }
+
+ StyledMenuSeparator {
+ leftPadding: 12
+ rightPadding: 12
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.margins: 4
+ Layout.rightMargin: 12
+ Layout.leftMargin: 12
+
+ StyledToolButton {
+ enabledImage: "Asset-import-Normal.png";
+ onClicked: _parentView.assetImportAction(projectTree.currentIndex);
+ toolTipText: qsTr("Import Assets");
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ StyledToolButton {
+ enabledImage: "Objects-Effect-Normal.png";
+ onClicked: _parentView.effectAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Effect Library")
+ }
+
+ StyledToolButton {
+ enabledImage: "Objects-Text-Normal.png";
+ onClicked: _parentView.fontAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Font Library")
+ }
+
+ StyledToolButton {
+ enabledImage: "Objects-Image-Normal.png";
+ onClicked: _parentView.imageAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Map Library")
+ }
+
+ StyledToolButton {
+ enabledImage: "Objects-Material-Normal.png";
+ onClicked: _parentView.materialAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Material Library")
+ }
+
+ StyledToolButton {
+ enabledImage: "Assets-Model.png";
+ onClicked: _parentView.modelAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Model Library")
+ }
+
+ StyledToolButton {
+ enabledImage: "Objects-Behavior-Normal.png";
+ onClicked: _parentView.behaviorAction(projectTree.currentIndex)
+ toolTipText: qsTr("Open Behavior Library")
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp
new file mode 100644
index 00000000..7bd7efef
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "SlideContextMenu.h"
+#include "SlideView.h"
+
+SlideContextMenu::SlideContextMenu(SlideView *parent, int row, int rowCount, bool master)
+ : QMenu(parent)
+ , m_view(parent)
+ , m_row(row)
+ , m_rowCount(rowCount)
+{
+ QAction *action = new QAction(tr("New Slide"));
+ action->setEnabled(!master);
+ connect(action, &QAction::triggered, this, &SlideContextMenu::handleAddNewSlide);
+ addAction(action);
+
+ action = new QAction(tr("Delete Slide\tDel"));
+ action->setEnabled(!master && m_row != -1 && m_rowCount > 1);
+ connect(action, &QAction::triggered, this, &SlideContextMenu::handleRemoveSlide);
+ addAction(action);
+
+ QString ctrlKey(QStringLiteral("Ctrl+"));
+#ifdef Q_OS_MACOS
+ ctrlKey = "⌘";
+#endif
+ action = new QAction(tr("Duplicate Slide\t%1D").arg(ctrlKey));
+ action->setEnabled(!master && m_row != -1);
+ connect(action, &QAction::triggered, this, &SlideContextMenu::handleDuplicateSlide);
+ addAction(action);
+}
+
+SlideContextMenu::~SlideContextMenu()
+{
+}
+
+void SlideContextMenu::handleAddNewSlide()
+{
+ m_view->addNewSlide(m_row == -1 ? m_rowCount : m_row + 1);
+}
+
+void SlideContextMenu::handleRemoveSlide()
+{
+ m_view->removeSlide(m_row);
+}
+
+void SlideContextMenu::handleDuplicateSlide()
+{
+ m_view->duplicateSlide(m_row);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h
new file mode 100644
index 00000000..bb4bb864
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SLIDE_CONTEXT_MENU_H
+#define SLIDE_CONTEXT_MENU_H
+
+#include <QtWidgets/qmenu.h>
+
+class SlideView;
+
+class SlideContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ explicit SlideContextMenu(SlideView *parent, int row, int rowCount, bool master);
+ virtual ~SlideContextMenu();
+
+private Q_SLOTS:
+ void handleAddNewSlide();
+ void handleRemoveSlide();
+ void handleDuplicateSlide();
+
+private:
+ SlideView *m_view;
+ int m_row;
+ int m_rowCount;
+};
+#endif // SLIDE_CONTEXT_MENU_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp
new file mode 100644
index 00000000..c07377c1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp
@@ -0,0 +1,468 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "SlideModel.h"
+
+#include "CmdActivateSlide.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "SlideSystem.h"
+#include "IDocumentEditor.h"
+
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+
+SlideModel::SlideModel(int slideCount, QObject *parent) : QAbstractListModel(parent)
+ , m_slides(slideCount)
+{
+}
+
+QVariant SlideModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(),index.parent()))
+ return {};
+
+ const auto row = index.row();
+
+ switch (role) {
+ case NameRole:
+ return slideName(m_slides[row]);
+ case SelectedRole:
+ return row == m_selectedRow;
+ case VariantsRole:
+ int slideIdx = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(m_slides[row]);
+ if (slideIdx < m_variantsModel.size()) {
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ const auto keys = m_variantsModelKeys[slideIdx];
+ QString templ = QString::fromWCharArray(L"<font color='%1'>\u25A0</font>");
+ QString slideVariants;
+ for (auto g : keys) // variants groups
+ slideVariants.append(templ.arg(variantsDef[g].m_color));
+
+ return slideVariants;
+ }
+ }
+
+ return {};
+}
+
+bool SlideModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!hasIndex(index.row(), index.column(),index.parent()))
+ return false;
+
+ auto &slideHandle = m_slides[index.row()];
+
+ switch (role) {
+ case NameRole: {
+ setSlideName(slideHandle, value.toString());
+ Q_EMIT dataChanged(index, index, {role});
+ break;
+ }
+ case HandleRole: {
+ slideHandle = value.value<qt3dsdm::Qt3DSDMSlideHandle>();
+ qt3dsdm::Qt3DSDMInstanceHandle instanceHandle
+ = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(slideHandle);
+ m_slideLookupHash.insert(instanceHandle, slideHandle);
+ Q_EMIT dataChanged(index, index, {HandleRole, NameRole});
+ break;
+ }
+ case SelectedRole: {
+ m_selectedRow = value.toBool() ? index.row() : -1;
+
+ if (m_selectedRow != -1) {
+ CCmdActivateSlide *theCmd = new CCmdActivateSlide(GetDoc(), m_slides[m_selectedRow]);
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+ }
+
+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {role});
+ break;
+ }
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+int SlideModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_slides.count();
+}
+
+QHash<int, QByteArray> SlideModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(NameRole, "name");
+ names.insert(VariantsRole, "variants");
+ names.insert(SelectedRole, "selected");
+
+ return names;
+}
+
+bool SlideModel::insertRows(int row, int count, const QModelIndex &parent)
+{
+ if (row > m_slides.count())
+ return false;
+
+ beginInsertRows(parent, row, row + count - 1);
+ for (int i = 0; i < count; ++i)
+ m_slides.insert(row, {});
+ endInsertRows();
+
+ setData(index(row + count - 1), true, SelectedRole);
+ return true;
+}
+
+bool SlideModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row + count > m_slides.count())
+ return false;
+
+ bool selectionRemoved = false;
+ beginRemoveRows(parent, row, row + count - 1);
+ for (int i = 0; i < count; ++i) {
+ if (m_selectedRow == row + i)
+ selectionRemoved = true;
+ m_slides.removeAt(row);
+ }
+ endRemoveRows();
+
+ auto newSelectedRow = -1;
+ if (selectionRemoved) {
+ if (row > 0)
+ newSelectedRow = row - 1;
+ else
+ newSelectedRow = 0;
+ } else if (m_selectedRow > row) {
+ newSelectedRow = m_selectedRow - count;
+ }
+ if (newSelectedRow != -1)
+ setData(index(newSelectedRow), true, SelectedRole);
+
+ return true;
+}
+
+void SlideModel::duplicateRow(int row)
+{
+ const auto handle = m_slides[row];
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Duplicate Slide"))
+ ->DuplicateSlide(handle);
+}
+
+void SlideModel::startRearrange(int row)
+{
+ m_rearrangeStartRow = row;
+ m_rearrangeEndRow = -1;
+}
+
+void SlideModel::move(int fromRow, int toRow)
+{
+ if (fromRow == toRow)
+ return;
+
+ onSlideRearranged({}, fromRow + 1, toRow + 1);
+}
+
+void SlideModel::finishRearrange(bool commit)
+{
+ if (m_rearrangeEndRow != m_rearrangeStartRow
+ && m_rearrangeEndRow >= 0 && m_rearrangeStartRow >= 0) {
+ // Restore state before committing the actual change
+ // +1 added as DocumentEditor uses 1 based indexes for slides
+ int endRow = m_rearrangeEndRow + 1;
+ onSlideRearranged({}, endRow, m_rearrangeStartRow + 1);
+
+ if (commit) {
+ auto handle = m_slides[m_rearrangeStartRow];
+ m_rearrangeStartRow = -1;
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Rearrange Slide"))
+ ->RearrangeSlide(handle, endRow);
+ }
+ }
+ m_rearrangeEndRow = -1;
+ m_rearrangeStartRow = -1;
+}
+
+void SlideModel::clear()
+{
+ beginResetModel();
+ m_slides.clear();
+ m_slideLookupHash.clear();
+ endResetModel();
+}
+
+void SlideModel::addNewSlide(int row)
+{
+ const auto handle = (row < m_slides.size()) ? m_slides[row] : m_slides.last();
+ const auto instanceHandle = GetBridge()->GetOwningComponentInstance(handle);
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = GetBridge()->GetComponentSlide(instanceHandle, 0);
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Create Slide"))
+ ->AddSlide(theMasterSlide, row + 1);
+}
+
+void SlideModel::removeSlide(int row)
+{
+ // Don't allow deleting of the last slide
+ if (m_slides.size() > 1) {
+ const auto handle = m_slides[row];
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Delete Slide"))->DeleteSlide(
+ handle);
+ }
+}
+
+void SlideModel::onNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem());
+
+ // Ignore new slides added to random different components
+ if (m_slides.size() && theSlideSystem.GetMasterSlide(inSlide)
+ != theSlideSystem.GetMasterSlide(m_slides[0])) {
+ return;
+ }
+
+ finishRearrange(false); // Cancel any uncommitted rearrange
+
+ // Find the slide index
+ int row = int(slideIndex(inSlide));
+
+ // Slide index zero indicates master slide. We can't add master slides
+ Q_ASSERT(row > 0);
+
+ --row;
+
+ beginInsertRows({}, row, row);
+ m_slides.insert(row, inSlide);
+ qt3dsdm::Qt3DSDMInstanceHandle instanceHandle
+ = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(inSlide);
+ m_slideLookupHash.insert(instanceHandle, inSlide);
+ endInsertRows();
+
+ setData(index(row), true, SelectedRole);
+}
+
+void SlideModel::onDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ for (int i = 0; i < m_slides.size(); ++i) {
+ if (m_slides[i] == inSlide) {
+ if (m_rearrangeStartRow >= 0) {
+ finishRearrange(false); // Cancel any uncommitted rearrange
+ // We need to re-resolve the index after rearrange cancel
+ for (int j = 0; j < m_slides.size(); ++j) {
+ if (m_slides[j] == inSlide) {
+ i = j;
+ break;
+ }
+ }
+ }
+ QList<qt3dsdm::Qt3DSDMInstanceHandle> keys = m_slideLookupHash.keys(inSlide);
+ for (auto key : keys)
+ m_slideLookupHash.remove(key);
+ removeRows(i, 1);
+ break;
+ }
+ }
+}
+
+void SlideModel::onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int fromRow,
+ int toRow)
+{
+ if (inMaster.Valid()) {
+ // If imMaster is valid, this was triggered by either a rearrange commit or
+ // undo/redo of a rearrange, so we need to cancel any uncommitted rearrange.
+ finishRearrange(false);
+ // Check we are working on correct slide set
+ qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem());
+ if (fromRow - 1 >= m_slides.size()
+ || inMaster != theSlideSystem.GetMasterSlide(m_slides[fromRow - 1]))
+ return;
+ } else {
+ // Do not do uncommitted rearranges if we haven't started a rearrange (or more likely
+ // an uncommitted rearrange was canceled while in progress by undo/redo operation)
+ if (m_rearrangeStartRow < 0)
+ return;
+ }
+
+ // -1 because internal slide model has 1-based indexing for non-master slides
+ if (fromRow > toRow)
+ beginMoveRows({}, fromRow - 1, fromRow - 1, {}, toRow - 1);
+ else
+ beginMoveRows({}, fromRow - 1, fromRow - 1, {}, toRow);
+ m_slides.move(fromRow - 1, toRow - 1);
+ m_rearrangeEndRow = toRow - 1;
+
+ endMoveRows();
+}
+
+bool SlideModel::hasSlideWithName(const QString &name) const
+{
+ for (const auto &slide: m_slides) {
+ if (slideName(slide) == name)
+ return true;
+ }
+ return false;
+}
+
+QString SlideModel::slideName(const qt3dsdm::Qt3DSDMSlideHandle &handle) const
+{
+ auto doc = GetDoc();
+ if (!doc->isValid())
+ return {};
+ const auto instanceHandle = doc->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(handle);
+ return GetBridge()->GetName(instanceHandle).toQString();
+}
+
+void SlideModel::setSlideName(const qt3dsdm::Qt3DSDMSlideHandle &handle, const QString &name)
+{
+ const auto oldName = slideName(handle);
+ if (oldName != name && !name.trimmed().isEmpty()) {
+ using namespace qt3dsdm;
+ CDoc *theDoc = GetDoc();
+ CClientDataModelBridge *theBridge = GetBridge();
+ if (!theBridge)
+ return;
+ const auto instanceHandle = GetDoc()->GetStudioSystem()->
+ GetSlideSystem()->GetSlideInstance(handle);
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*theDoc, QObject::tr("Set Slide Name"))
+ ->SetSlideName(instanceHandle, theBridge->GetNameProperty(),
+ Q3DStudio::CString::fromQString(oldName),
+ Q3DStudio::CString::fromQString(name));
+ }
+}
+
+void SlideModel::refreshVariants(const QVector<QHash<QString, QStringList>> &vModel,
+ const QVector<QStringList> &vModelKeys)
+{
+ m_variantsModel.clear();
+ m_variantsModelKeys.clear();
+
+ if (vModel.isEmpty()) {
+ const auto *slideSystem = GetDoc()->GetStudioSystem()->GetSlideSystem();
+ int slideCount = slideSystem->GetSlideCount(slideSystem->GetMasterSlide(
+ GetDoc()->GetActiveSlide()));
+ m_variantsModel.resize(slideCount);
+ m_variantsModelKeys.resize(slideCount);
+
+ const auto propertySystem = GetDoc()->GetPropertySystem();
+ const QVector<int> instances = GetDoc()->getVariantInstances();
+ for (auto instance : instances) {
+ int slideIdx = slideIndex(slideSystem->GetAssociatedSlide(instance));
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance,
+ GetBridge()->getVariantsProperty(instance),
+ sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ if (!propVal.isEmpty()) {
+ QStringList tagPairs = propVal.split(QLatin1Char(','));
+ for (int i = 0; i < tagPairs.size(); ++i) {
+ QStringList pair = tagPairs[i].split(QLatin1Char(':'));
+ if (!m_variantsModel[slideIdx][pair[0]].contains(pair[1]))
+ m_variantsModel[slideIdx][pair[0]].append(pair[1]);
+
+ if (!m_variantsModelKeys[slideIdx].contains(pair[0]))
+ m_variantsModelKeys[slideIdx].append(pair[0]);
+ }
+ }
+ }
+ }
+
+ // add master slide variants to other slides
+ const auto keys = m_variantsModel[0].keys();
+ for (int i = 1; i < slideCount; ++i) {
+ for (auto g : keys) {
+ for (int j = 0; j < m_variantsModel[0][g].length(); ++j) {
+ if (!m_variantsModel[i][g].contains(m_variantsModel[0][g][j]))
+ m_variantsModel[i][g].append(m_variantsModel[0][g][j]);
+ }
+
+ if (!m_variantsModelKeys[i].contains(g))
+ m_variantsModelKeys[i].append(g);
+ }
+ }
+ } else {
+ m_variantsModel = vModel;
+ m_variantsModelKeys = vModelKeys;
+ }
+
+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {VariantsRole});
+}
+
+CDoc *SlideModel::GetDoc() const
+{
+ return g_StudioApp.GetCore()->GetDoc();
+}
+
+long SlideModel::slideIndex(const qt3dsdm::Qt3DSDMSlideHandle &handle) const
+{
+ return GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(handle);
+}
+
+int SlideModel::rowToSlideIndex(int row) const
+{
+ return GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(m_slides[row]);
+}
+
+CClientDataModelBridge *SlideModel::GetBridge() const
+{
+ auto doc = GetDoc();
+ if (!doc->isValid())
+ return nullptr;
+ return doc->GetStudioSystem()->GetClientDataModelBridge();
+}
+
+void SlideModel::refreshSlideLabel(qt3dsdm::Qt3DSDMInstanceHandle instanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle propertyHandle)
+{
+ if (m_slideLookupHash.contains(instanceHandle)
+ && propertyHandle == GetBridge()->GetNameProperty()) {
+ qt3dsdm::Qt3DSDMSlideHandle slideHandle = m_slideLookupHash.value(instanceHandle);
+ for (int i = 0; i < m_slides.size(); ++i) {
+ if (m_slides[i] == slideHandle) {
+ setData(index(i, 0), GetBridge()->GetName(instanceHandle).toQString(),
+ SlideModel::NameRole);
+ break;
+ }
+ }
+ }
+}
+
+// Set selected slide highlight on UI
+void SlideModel::setSelectedSlideIndex(const QModelIndex &index)
+{
+ if (m_selectedRow == index.row() ||
+ !hasIndex(index.row(), index.column(), index.parent()))
+ return;
+
+ m_selectedRow = index.row();
+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {SelectedRole});
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h
new file mode 100644
index 00000000..0b1deab1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SLIDEMODEL_H
+#define SLIDEMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qhash.h>
+
+#include "Qt3DSDMHandles.h"
+
+class CClientDataModelBridge;
+class CDoc;
+
+class SlideModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum Roles {
+ NameRole = Qt::DisplayRole,
+ HandleRole = Qt::UserRole + 1,
+ SelectedRole,
+ VariantsRole
+ };
+
+ SlideModel(int slideCount, QObject *parent = nullptr);
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::DisplayRole) override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ bool insertRows(int row, int count,
+ const QModelIndex &parent = QModelIndex()) override;
+ bool removeRows(int row, int count,
+ const QModelIndex &parent = QModelIndex()) override;
+ void duplicateRow(int row);
+ void startRearrange(int row);
+ void move(int fromRow, int toRow);
+ void finishRearrange(bool commit);
+
+ void clear();
+ void addNewSlide(int row);
+ void removeSlide(int row);
+
+ void onNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ void onDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ void onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex,
+ int toRow);
+ void refreshSlideLabel(qt3dsdm::Qt3DSDMInstanceHandle instanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle propertyHandle);
+ void setSelectedSlideIndex(const QModelIndex &index);
+ void refreshVariants(const QVector<QHash<QString, QStringList>> &vModel = {},
+ const QVector<QStringList> &vModelKeys = {});
+ int rowToSlideIndex(int row) const;
+ QVector<QHash<QString, QStringList> > variantsModel() const { return m_variantsModel; }
+ QVector<QStringList> variantsModelKeys() const { return m_variantsModelKeys; }
+
+private:
+ bool hasSlideWithName(const QString &name) const;
+ QString slideName(const qt3dsdm::Qt3DSDMSlideHandle &handle) const;
+ void setSlideName(const qt3dsdm::Qt3DSDMSlideHandle &handle, const QString &name);
+ inline CDoc *GetDoc() const;
+ inline long slideIndex(const qt3dsdm::Qt3DSDMSlideHandle &handle) const;
+ inline CClientDataModelBridge *GetBridge() const;
+
+ QVector<qt3dsdm::Qt3DSDMSlideHandle> m_slides;
+ int m_selectedRow = -1;
+ int m_rearrangeStartRow = -1;
+ int m_rearrangeEndRow = -1;
+ QVector<QHash<QString, QStringList> > m_variantsModel;
+ QVector<QStringList> m_variantsModelKeys;
+ QHash<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMSlideHandle> m_slideLookupHash;
+};
+
+
+#endif // SLIDEMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp
new file mode 100644
index 00000000..c49f34d2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp
@@ -0,0 +1,621 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "SlideView.h"
+#include "Core.h"
+#include "Dispatch.h"
+#include "Doc.h"
+#include "StudioPreferences.h"
+#include "SlideModel.h"
+#include "StudioApp.h"
+#include "StudioUtils.h"
+#include "SlideContextMenu.h"
+#include "DataInputSelectView.h"
+#include "DataInputDlg.h"
+#include "IDocumentEditor.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "Dialogs.h"
+
+#include "QtWidgets/qlabel.h"
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+SlideView::SlideView(QWidget *parent) : QQuickWidget(parent)
+ , m_MasterSlideModel(new SlideModel(1, this))
+ , m_SlidesModel(new SlideModel(0, this))
+ , m_CurrentModel(m_SlidesModel)
+ , m_variantsToolTip(new QLabel(this))
+ , m_toolTip(tr("No Controller"))
+{
+ m_variantsToolTip->setObjectName(QStringLiteral("variantsToolTip"));
+ m_variantsToolTip->setWindowModality(Qt::NonModal);
+ m_variantsToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
+ m_variantsToolTip->setContentsMargins(2, 2, 2, 2);
+
+ g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &SlideView::initialize);
+
+ m_variantRefreshTimer.setSingleShot(true);
+ m_variantRefreshTimer.setInterval(0);
+ connect(&m_variantRefreshTimer, &QTimer::timeout, [this]() {
+ m_SlidesModel->refreshVariants();
+ m_MasterSlideModel->refreshVariants(m_SlidesModel->variantsModel(),
+ m_SlidesModel->variantsModelKeys());
+ });
+}
+
+SlideView::~SlideView()
+{
+ clearSlideList();
+ g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this);
+ delete m_dataInputSelector;
+}
+
+bool SlideView::showMasterSlide() const
+{
+ return m_CurrentModel == m_MasterSlideModel;
+}
+
+void SlideView::setShowMasterSlide(bool show)
+{
+ const bool currentIsMaster = m_CurrentModel == m_MasterSlideModel;
+ if (show == currentIsMaster)
+ return;
+
+ m_CurrentModel = show ? m_MasterSlideModel : m_SlidesModel;
+
+ // We need to get the first slide in the correct master mode
+ CDoc *theDoc = GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle theRoot = theDoc->GetActiveRootInstance();
+ CClientDataModelBridge *theBridge = GetBridge();
+ qt3dsdm::Qt3DSDMSlideHandle theNewActiveSlide =
+ theBridge->GetOrCreateGraphRoot(theRoot); // this will return the master slide
+ qt3dsdm::ISlideSystem *theSlideSystem = theDoc->GetStudioSystem()->GetSlideSystem();
+ if (m_CurrentModel != m_MasterSlideModel) {
+ qt3dsdm::Qt3DSDMSlideHandle masterSlide = theNewActiveSlide;
+ theNewActiveSlide = m_MasterSlideReturnPointers.value(masterSlide, 0);
+ if (!theSlideSystem->SlideValid(theNewActiveSlide)) {
+ theNewActiveSlide = theSlideSystem->GetSlideByIndex(
+ masterSlide, 1); // activate the first slide;
+ }
+ }
+
+ // We have forced a mode change, and so we need to set the current active TC
+ // to be in the correct mode so our slide palette will show the correct information
+ if (theNewActiveSlide.Valid())
+ theDoc->NotifyActiveSlideChanged(theNewActiveSlide);
+
+ Q_EMIT showMasterSlideChanged();
+ Q_EMIT currentModelChanged();
+}
+
+void SlideView::showControllerDialog(const QPoint &point)
+{
+ QString currCtr = m_currentController.size() ?
+ m_currentController : m_dataInputSelector->getNoneString();
+ QVector<QPair<QString, int>> dataInputList;
+
+ for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems))
+ dataInputList.append({it->name, it->type});
+
+ m_dataInputSelector->setData(dataInputList, currCtr);
+ CDialogs::showWidgetBrowser(this, m_dataInputSelector, point,
+ CDialogs::WidgetBrowserAlign::ToolButton);
+}
+
+bool SlideView::toolTipsEnabled()
+{
+ return CStudioPreferences::ShouldShowTooltips();
+}
+
+QSize SlideView::sizeHint() const
+{
+ return {150, 500};
+}
+
+QSize SlideView::minimumSizeHint() const
+{
+ // prevent datainput control indicator from overlapping
+ // with slide name too much when panel is minimised
+ return {100, 0};
+}
+
+void SlideView::deselectAll()
+{
+ g_StudioApp.GetCore()->GetDoc()->DeselectAllItems();
+}
+
+void SlideView::addNewSlide(int row)
+{
+ m_SlidesModel->addNewSlide(row);
+}
+
+void SlideView::removeSlide(int row)
+{
+ m_SlidesModel->removeSlide(row);
+}
+
+void SlideView::duplicateSlide(int row)
+{
+ m_SlidesModel->duplicateRow(row);
+}
+
+void SlideView::startSlideRearrange(int row)
+{
+ m_SlidesModel->startRearrange(row);
+}
+
+void SlideView::moveSlide(int from, int to)
+{
+ m_SlidesModel->move(from, to);
+}
+
+void SlideView::finishSlideRearrange(bool commit)
+{
+ m_SlidesModel->finishRearrange(commit);
+}
+
+void SlideView::showContextMenu(int x, int y, int row)
+{
+ SlideContextMenu contextMenu(this, row, m_SlidesModel->rowCount(),
+ m_CurrentModel == m_MasterSlideModel);
+ contextMenu.exec(mapToGlobal({x, y}));
+}
+
+void SlideView::showVariantsTooltip(int row, const QPoint &point)
+{
+ QString templ = QStringLiteral("<font color='%1'>%2</font>");
+ QString tooltipStr("<table>");
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ const auto slideIndex = m_CurrentModel->rowToSlideIndex(row);
+ const auto variantsModel = m_CurrentModel->variantsModel()[slideIndex];
+ const auto variantsModelKeys = m_CurrentModel->variantsModelKeys()[slideIndex];
+ for (auto &g : variantsModelKeys) {
+ tooltipStr.append("<tr><td>");
+ tooltipStr.append(templ.arg(variantsDef[g].m_color).arg(g + ": "));
+ tooltipStr.append("</td><td>");
+ const auto tags = variantsModel[g];
+ for (auto &t : tags)
+ tooltipStr.append(t + ", ");
+ tooltipStr.chop(2);
+ tooltipStr.append("</td></tr>");
+ }
+ tooltipStr.append("</table>");
+
+ m_variantsToolTip->setText(tooltipStr);
+ m_variantsToolTip->adjustSize();
+ m_variantsToolTip->move(point);
+ m_variantsToolTip->raise();
+ m_variantsToolTip->show();
+}
+
+void SlideView::hideVariantsTooltip()
+{
+ m_variantsToolTip->hide();
+}
+
+void SlideView::OnNewPresentation()
+{
+ // Register callbacks
+ qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalProvider();
+ m_MasterSlideReturnPointers.clear();
+
+ m_Connections.push_back(theSignalProvider->ConnectActiveSlide(
+ std::bind(&SlideView::OnActiveSlide, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)));
+
+ // Needed for undo/redo functionality to work properly
+ m_Connections.push_back(theSignalProvider->ConnectSlideCreated(
+ std::bind(&SlideView::OnNewSlide, this, std::placeholders::_1)));
+ m_Connections.push_back(theSignalProvider->ConnectSlideDeleted(
+ std::bind(&SlideView::OnDeleteSlide, this, std::placeholders::_1)));
+ m_Connections.push_back(theSignalProvider->ConnectSlideRearranged(
+ std::bind(&SlideView::OnSlideRearranged, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)));
+
+ // Set up listener for the name changes to slide
+ m_Connections.push_back(theSignalProvider->ConnectInstancePropertyValue(
+ std::bind(&SlideView::onPropertyChanged, this,
+ std::placeholders::_1, std::placeholders::_2)));
+
+ // object created/deleted
+ m_Connections.push_back(theSignalProvider->ConnectInstanceCreated(
+ std::bind(&SlideView::onAssetCreated, this, std::placeholders::_1)));
+ m_Connections.push_back(theSignalProvider->ConnectInstanceDeleted(
+ std::bind(&SlideView::onAssetDeleted, this, std::placeholders::_1)));
+
+ // Set up listener for undo/redo changes in order to update
+ // slide datainput control
+ CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch();
+ theDispatch->AddDataModelListener(this);
+
+ refreshVariants();
+}
+
+void SlideView::OnClosingPresentation()
+{
+ m_Connections.clear();
+ clearSlideList();
+}
+
+void SlideView::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+ QQuickWidget::mousePressEvent(event);
+}
+
+void SlideView::OnActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex,
+ const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ // Don't use inIndex because inIndex might have been changed due to deletion
+ Q_UNUSED(inIndex);
+ Q_UNUSED(inMaster);
+
+ qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem());
+ int currentSlideIndex = theSlideSystem.GetSlideIndex(inSlide);
+ setShowMasterSlide(currentSlideIndex == 0);
+ setActiveSlide(inSlide);
+
+ // Update slide highlight to match active slide
+ // -1 because first slide is masterslide
+ auto index = m_SlidesModel->index(currentSlideIndex - 1, 0);
+ m_SlidesModel->setSelectedSlideIndex(index);
+
+ if (currentSlideIndex != 0)
+ m_MasterSlideReturnPointers[inMaster] = inSlide;
+}
+
+void SlideView::OnNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ m_SlidesModel->onNewSlide(inSlide);
+}
+
+void SlideView::OnDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ m_SlidesModel->onDeleteSlide(inSlide);
+}
+
+void SlideView::OnSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex,
+ int inNewIndex)
+{
+ m_SlidesModel->onSlideRearranged(inMaster, inOldIndex, inNewIndex);
+}
+
+void SlideView::onDataInputChange(int handle, int instance, const QString &dataInputName)
+{
+ Q_UNUSED(handle)
+ Q_UNUSED(instance)
+
+ if (dataInputName == m_currentController)
+ return;
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::Qt3DSDMInstanceHandle slideRoot = doc->GetActiveRootInstance();
+ QString fullSlideControlStr;
+
+ if (dataInputName != m_dataInputSelector->getNoneString()) {
+ fullSlideControlStr = "$" + dataInputName + " @slide";
+ m_controlled = true;
+ m_currentController = dataInputName;
+ m_toolTip = tr("Slide Controller:\n") + m_currentController;
+ } else {
+ m_controlled = false;
+ m_currentController.clear();
+ m_toolTip = tr("No Controller");
+ }
+ qt3dsdm::Qt3DSDMPropertyHandle ctrldProp;
+ if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_SCENE)
+ ctrldProp = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty;
+ else if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_COMPONENT)
+ ctrldProp = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty;
+ else
+ Q_ASSERT(false);
+
+ qt3dsdm::SValue controlledPropertyVal;
+ doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(slideRoot, ctrldProp,
+ controlledPropertyVal);
+
+ // To indicate that slide transitions are controlled by data input,
+ // we set "controlled property" of this scene to contain the name of
+ // controller followed by special indicator "@slide".
+ // If we have existing slide control in this root element, replace it.
+ // Otherwise just append slide control string to controlledproperty
+ // (it might already contain timeline control information)
+ QString existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal);
+ if (existingCtrl.contains(QLatin1String("@slide"))) {
+ int slideStrPos = existingCtrl.indexOf(QLatin1String("@slide"));
+ // find the controlling datainput name and build the string to replace
+ int ctrStrPos = existingCtrl.lastIndexOf(QLatin1Char('$'), slideStrPos - 2);
+ QString prevCtrler = existingCtrl.mid(ctrStrPos, slideStrPos - ctrStrPos - 1);
+ existingCtrl.replace(prevCtrler + QLatin1String(" @slide"), fullSlideControlStr);
+ } else {
+ if (!existingCtrl.isEmpty() && m_controlled)
+ existingCtrl.append(QLatin1Char(' '));
+ existingCtrl.append(fullSlideControlStr);
+ }
+
+ if (existingCtrl.endsWith(QLatin1Char(' ')))
+ existingCtrl.chop(1);
+
+ if (existingCtrl.startsWith(QLatin1Char(' ')))
+ existingCtrl.remove(0, 1);
+
+ qt3dsdm::SValue fullCtrlPropVal
+ = std::make_shared<qt3dsdm::CDataStr>(Q3DStudio::CString::fromQString(existingCtrl));
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Slide control"))
+ ->SetInstancePropertyValue(slideRoot, ctrldProp, fullCtrlPropVal);
+
+ UpdateSlideViewTitleColor();
+ Q_EMIT controlledChanged();
+}
+
+void SlideView::onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ // refresh the variants model if the created asset has a variants property set.
+ if (GetBridge()->GetObjectType(inInstance) & OBJTYPE_IS_VARIANT) {
+ const auto propertySystem = GetDoc()->GetPropertySystem();
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(inInstance,
+ GetBridge()->getVariantsProperty(inInstance),
+ sValue)) {
+ if (qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->GetLength() != 0)
+ refreshVariants();
+ }
+ }
+}
+
+void SlideView::onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ Q_UNUSED(inInstance)
+
+ refreshVariants();
+}
+
+void SlideView::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ // refresh slide name
+ m_SlidesModel->refreshSlideLabel(inInstance, inProperty);
+
+ // refresh variants
+ if (inProperty == GetBridge()->getVariantsProperty(inInstance))
+ refreshVariants();
+}
+
+void SlideView::onDockLocationChange(Qt::DockWidgetArea area)
+{
+ m_dockArea = area;
+ Q_EMIT dockAreaChanged();
+}
+
+// Set the state of slide control based on scene or component
+// controlledproperty
+void SlideView::updateDataInputStatus()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::Qt3DSDMInstanceHandle slideRoot = doc->GetActiveRootInstance();
+
+ qt3dsdm::Qt3DSDMPropertyHandle ctrldProp;
+ if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_SCENE)
+ ctrldProp = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty;
+ else if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_COMPONENT)
+ ctrldProp = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty;
+ else
+ Q_ASSERT(false);
+
+ qt3dsdm::SValue controlledPropertyVal;
+ doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(slideRoot, ctrldProp,
+ controlledPropertyVal);
+ QString existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal);
+
+ QString newController;
+ int slideStrPos = existingCtrl.indexOf(QLatin1String("@slide"));
+ if (slideStrPos != -1) {
+ int ctrStrPos = existingCtrl.lastIndexOf(QLatin1Char('$'), slideStrPos - 2);
+ newController = existingCtrl.mid(ctrStrPos + 1, slideStrPos - ctrStrPos - 2);
+ }
+ if (newController != m_currentController) {
+ m_currentController = newController;
+ if (!m_currentController.isEmpty()) {
+ m_toolTip = tr("Slide Controller:\n") + m_currentController;
+ m_controlled = true;
+ } else {
+ m_currentController.clear();
+ m_toolTip = tr("No Controller");
+ m_controlled = false;
+ }
+ // update UI
+ UpdateSlideViewTitleColor();
+ Q_EMIT controlledChanged();
+ if (m_dataInputSelector && m_dataInputSelector->isVisible())
+ m_dataInputSelector->setCurrentController(m_currentController);
+ }
+}
+void SlideView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_parentView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl());
+
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Slide/SlideView.qml")));
+
+ const QVector<EDataType> acceptedTypes = { EDataType::DataTypeString };
+ m_dataInputSelector = new DataInputSelectView(acceptedTypes, this);
+ connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged,
+ this, &SlideView::onDataInputChange);
+}
+
+void SlideView::clearSlideList()
+{
+ m_ActiveRoot = 0;
+ m_SlidesModel->clear();
+}
+
+void SlideView::setActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle)
+{
+ // Make sure we are in the correct master mode based on the inActiveSlideHandle
+ // If we changed mode, then we need to force a rebuild
+ bool theRebuildFlag = isMaster(inActiveSlideHandle) && (m_CurrentModel != m_MasterSlideModel);
+
+ // Check to see if the incoming slide is a sibling of the current active slide
+ // If it is, then we may be able to update without rebuilding everything
+ if (!theRebuildFlag
+ && m_ActiveRoot == GetBridge()->GetOwningComponentInstance(inActiveSlideHandle)) {
+ // If this is a new active slide, but the same root parent
+ if (m_ActiveSlideHandle != inActiveSlideHandle) {
+ m_ActiveSlideHandle = inActiveSlideHandle;
+ }
+ } else {
+ // We have a new parent or a new slide that makes us rebuild the entire list
+ rebuildSlideList(inActiveSlideHandle);
+ }
+}
+
+void SlideView::rebuildSlideList(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle)
+{
+ // Clear out the existing slides
+ clearSlideList();
+
+ // Add new slide controls as required
+ if (inActiveSlideHandle.Valid()) {
+ m_ActiveSlideHandle = inActiveSlideHandle;
+ m_ActiveRoot = GetBridge()->GetOwningComponentInstance(inActiveSlideHandle);
+
+ // Get the Master Slide handle and the slide count
+ qt3dsdm::ISlideSystem *theSlideSystem = GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlide =
+ theSlideSystem->GetMasterSlide(inActiveSlideHandle);
+
+ // update handle for master slide
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlideHandle =
+ theSlideSystem->GetSlideByIndex(theMasterSlide, 0);
+ m_MasterSlideModel->setData(m_MasterSlideModel->index(0, 0),
+ QVariant::fromValue(theMasterSlideHandle),
+ SlideModel::HandleRole);
+
+ long theSlideCount = (long)theSlideSystem->GetSlideCount(theMasterSlide);
+
+ // Iterate through, creating the new slide controls
+ m_SlidesModel->clear();
+ m_SlidesModel->insertRows(0, theSlideCount - 1, {});
+ int row = 0;
+ for (long theSlideIndex = 1; theSlideIndex < theSlideCount; ++theSlideIndex) {
+ qt3dsdm::Qt3DSDMSlideHandle theSlideHandle =
+ theSlideSystem->GetSlideByIndex(theMasterSlide, theSlideIndex);
+ auto index = m_SlidesModel->index(row, 0);
+ m_SlidesModel->setData(index,
+ QVariant::fromValue(theSlideHandle),
+ SlideModel::HandleRole);
+ const auto instanceHandle =
+ GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(theSlideHandle);
+ m_SlidesModel->setData(index,
+ GetBridge()->GetName(instanceHandle).toQString(),
+ SlideModel::NameRole);
+ // This slide is the active slide
+ if (theSlideHandle == m_ActiveSlideHandle) {
+ m_SlidesModel->setData(index, true, SlideModel::SelectedRole);
+ }
+ row++;
+ }
+ }
+}
+
+CDoc *SlideView::GetDoc() const
+{
+ return g_StudioApp.GetCore()->GetDoc();
+}
+
+CClientDataModelBridge *SlideView::GetBridge() const
+{
+ return GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+}
+
+qt3dsdm::ISlideSystem *SlideView::GetSlideSystem() const
+{
+ return GetDoc()->GetStudioSystem()->GetSlideSystem();
+}
+
+long SlideView::GetSlideIndex(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const
+{
+ return GetSlideSystem()->GetSlideIndex(inSlideHandle);
+}
+
+bool SlideView::isMaster(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const
+{
+ return (0 == GetSlideIndex(inSlideHandle));
+}
+
+void SlideView::refreshVariants()
+{
+ if (!m_variantRefreshTimer.isActive())
+ m_variantRefreshTimer.start();
+}
+
+void SlideView::OnBeginDataModelNotifications()
+{
+}
+
+void SlideView::OnEndDataModelNotifications()
+{
+ updateDataInputStatus();
+}
+
+void SlideView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ Q_UNUSED(inInstance)
+}
+
+void SlideView::OnImmediateRefreshInstanceMultiple(
+ qt3dsdm::Qt3DSDMInstanceHandle *inInstance, long inInstanceCount)
+{
+ Q_UNUSED(inInstance)
+ Q_UNUSED(inInstanceCount)
+}
+
+// Notify the user about control state change also with slide view
+// title color change.
+void SlideView::UpdateSlideViewTitleColor() {
+ QString styleString;
+ if (m_controlled) {
+ styleString = "QDockWidget#slide { color: "
+ + QString(CStudioPreferences::dataInputColor().name()) + "; }";
+ } else {
+ styleString = "QDockWidget#slide { color: "
+ + QString(CStudioPreferences::textColor().name()) + "; }";
+ }
+
+ parentWidget()->setStyleSheet(styleString);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h
new file mode 100644
index 00000000..4a15e3a1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h
@@ -0,0 +1,154 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SLIDEVIEW_H
+#define SLIDEVIEW_H
+
+#include <QtQuickWidgets/qquickwidget.h>
+#include <QtCore/qtimer.h>
+
+#include "SlideModel.h"
+#include "Qt3DSDMSignals.h"
+#include "DispatchListeners.h"
+
+class CClientDataModelBridge;
+class CDoc;
+class DataInputSelectView;
+
+QT_FORWARD_DECLARE_CLASS(QLabel);
+
+namespace qt3dsdm {
+class ISlideSystem;
+}
+
+class SlideView : public QQuickWidget,
+ public CPresentationChangeListener,
+ public IDataModelListener
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QAbstractItemModel *currentModel READ currentModel NOTIFY currentModelChanged FINAL)
+ Q_PROPERTY(bool showMasterSlide READ showMasterSlide WRITE setShowMasterSlide NOTIFY showMasterSlideChanged FINAL)
+ Q_PROPERTY(bool controlled MEMBER m_controlled NOTIFY controlledChanged)
+ Q_PROPERTY(QString currController MEMBER m_currentController NOTIFY controlledChanged)
+ Q_PROPERTY(QString toolTip MEMBER m_toolTip NOTIFY controlledChanged)
+ Q_PROPERTY(Qt::DockWidgetArea dockArea MEMBER m_dockArea NOTIFY dockAreaChanged)
+public:
+ SlideView(QWidget *parent = nullptr);
+ ~SlideView();
+
+ bool showMasterSlide() const;
+ void setShowMasterSlide(bool show);
+ QAbstractItemModel *currentModel() { return m_CurrentModel; }
+ QSize sizeHint() const override;
+ QSize minimumSizeHint() const override;
+ void onDataInputChange(int handle, int instance, const QString &dataInputName);
+ void onDockLocationChange(Qt::DockWidgetArea area);
+ void refreshVariants();
+
+ Q_INVOKABLE void deselectAll();
+ Q_INVOKABLE void addNewSlide(int row);
+ Q_INVOKABLE void removeSlide(int row);
+ Q_INVOKABLE void duplicateSlide(int row);
+ Q_INVOKABLE void startSlideRearrange(int row);
+ Q_INVOKABLE void moveSlide(int from, int to);
+ Q_INVOKABLE void finishSlideRearrange(bool commit);
+ Q_INVOKABLE void showContextMenu(int x, int y, int row);
+ Q_INVOKABLE void showControllerDialog(const QPoint &point);
+ Q_INVOKABLE void showVariantsTooltip(int row, const QPoint &point);
+ Q_INVOKABLE void hideVariantsTooltip();
+ Q_INVOKABLE bool toolTipsEnabled();
+
+ // Presentation Change Listener
+ void OnNewPresentation() override;
+ void OnClosingPresentation() override;
+
+ // IDataModelListener
+ void OnBeginDataModelNotifications() override;
+ void OnEndDataModelNotifications() override;
+ void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount) override;
+
+Q_SIGNALS:
+ void currentModelChanged();
+ void showMasterSlideChanged();
+ void controlledChanged();
+ void dockAreaChanged();
+
+protected:
+ void mousePressEvent(QMouseEvent *event) override;
+
+ // DataModel callbacks
+ virtual void OnActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex,
+ const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ virtual void OnNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ virtual void OnDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ virtual void OnSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex,
+ int inNewIndex);
+
+ void updateDataInputStatus();
+ void UpdateSlideViewTitleColor();
+
+private:
+ void initialize();
+ void clearSlideList();
+ void setActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle);
+ inline CDoc *GetDoc() const;
+ inline CClientDataModelBridge *GetBridge() const;
+ inline qt3dsdm::ISlideSystem *GetSlideSystem() const;
+ long GetSlideIndex(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const;
+ bool isMaster(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const;
+ void rebuildSlideList(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle);
+ void onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+
+ SlideModel *m_MasterSlideModel = nullptr;
+ SlideModel *m_SlidesModel = nullptr;
+ SlideModel *m_CurrentModel = nullptr;
+ DataInputSelectView *m_dataInputSelector = nullptr;
+ QLabel *m_variantsToolTip = nullptr;
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Connections;
+ // We need to remember which slide we were on when we entered the master slide.
+ // Then, when the users leave the master slide we can go back to roughly the same
+ // state.
+ QHash<int, int> m_MasterSlideReturnPointers;
+
+ // the object containing the slides to be inspected.
+ qt3dsdm::Qt3DSDMInstanceHandle m_ActiveRoot = 0;
+ qt3dsdm::Qt3DSDMSlideHandle m_ActiveSlideHandle; // the active slide handle
+ bool m_controlled = false; // Are slides in this slide set controlled by datainput?
+ QString m_currentController;
+ QString m_toolTip;
+ Qt::DockWidgetArea m_dockArea;
+ QTimer m_variantRefreshTimer;
+};
+
+#endif // SLIDEVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml
new file mode 100644
index 00000000..7100ed8a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml
@@ -0,0 +1,416 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../controls"
+
+Rectangle {
+
+ id: root
+
+ readonly property bool masterSlide: _parentView.showMasterSlide
+
+ function handleMouseClicks(mouse, mappedCoords) {
+ if (mouse.button === Qt.RightButton) {
+ _parentView.showContextMenu(mappedCoords.x, mappedCoords.y, -1);
+ } else {
+ root.focus = true;
+ //Unselect All element when we click outside slider item in listView.
+ //It worked as it in old version.
+ _parentView.deselectAll();
+ mouse.accepted = false
+ }
+ }
+
+ Connections {
+ target: _parentView
+ onDockAreaChanged: diIndicator.reAnchor();
+ }
+
+ color: _backgroundColor
+
+ Column {
+ anchors {
+ top: parent.top
+ topMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ spacing: 5
+ width: parent.width
+
+ MouseArea {
+ id: masterMouseArea
+
+ width: parent.width
+ height: childrenRect.height
+
+ propagateComposedEvents: true
+ acceptedButtons: Qt.AllButtons
+ onClicked: {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ root.handleMouseClicks(mouse, coords);
+ }
+
+ Column {
+ id: masterButtonColumn
+ spacing: -4
+ anchors.horizontalCenter: parent.horizontalCenter
+ Button {
+ id: masterEditButton
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ onClicked: _parentView.showMasterSlide = !_parentView.showMasterSlide
+
+ background: Rectangle {
+ color: "transparent"
+ }
+ contentItem: Image {
+ source: _parentView.showMasterSlide ? _resDir + "Slide-Normal.png"
+ : _resDir + "Slide-Master-Active.png"
+ }
+ }
+
+ StyledLabel {
+ id: masterEditLabel
+ text: _parentView.showMasterSlide ? qsTr("Leave Master") : qsTr("Edit Master")
+ font.pixelSize: _fontSize
+ color: _masterColor
+ verticalAlignment: Text.AlignVCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ StyledMenuSeparator {
+ id: separator
+ leftPadding: 12
+ rightPadding: 12
+ }
+
+ ListView {
+ id: slideList
+
+ ScrollBar.vertical: ScrollBar {}
+
+ width: root.width
+ property int listItemHeight: root.height - masterButtonColumn.height
+ - separator.height - separator2.height
+ - parent.spacing * 2 - 14 - slideControlButton.height
+ - slideControlButton.spacing * 2
+ // DockWidgetArea is enum; value 0x2 denotes right edge
+ property int area: _parentView.dockArea
+ height: listItemHeight > 0 ? listItemHeight : 0
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+
+ model: _parentView.currentModel
+ spacing: 10
+
+ Rectangle {
+ id: diIndicator
+ height: slideList.listItemHeight
+ width: dataInputImage2.height
+
+ function reAnchor() {
+ // reset anchors before setting new value
+ anchors.right = undefined
+ anchors.left = undefined
+ // default position for indicator is right edge
+ // except when slide panel is attached to window right side
+ if (parent.area === 2)
+ anchors.left = parent.left
+ else
+ anchors.right = parent.right
+ }
+
+ color: _parentView.controlled ? _dataInputColor : "transparent"
+ Row {
+ rotation: 90
+ anchors.centerIn: parent
+ spacing: 5
+ Image {
+ id: dataInputImage2
+ fillMode: Image.Pad
+ visible: _parentView.controlled
+ source: _resDir + "Objects-DataInput-White.png"
+
+ }
+ StyledLabel {
+ text: _parentView.currController
+ anchors.margins: 16
+ color: "#ffffff"
+ }
+ }
+ }
+
+ MouseArea {
+ // mouse handling for the area not covered by the delegates
+ propagateComposedEvents: true
+ anchors.fill: parent
+ z: -1 // Only reached when clicking outside delegates
+ acceptedButtons: Qt.AllButtons
+ onClicked: {
+ if (slideList.indexAt(mouse.x, mouse.y) === -1) {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ root.handleMouseClicks(mouse, coords);
+ } else {
+ mouse.accepted = false;
+ }
+ }
+ onPressed: {
+ if (slideList.indexAt(mouse.x, mouse.y) !== -1)
+ mouse.accepted = false;
+ }
+ }
+
+ delegate: MouseArea {
+ id: delegateArea
+
+ property int dragIndex
+ property bool held : false
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: delegateItem.height
+ width: parent.width
+
+ acceptedButtons: Qt.RightButton | Qt.LeftButton
+ drag.target: held ? delegateItem : null
+ drag.axis: Drag.YAxis
+
+ onPressed: {
+ dragIndex = model.index;
+ _parentView.startSlideRearrange(model.index);
+ if (mouse.x > delegateItem.x && mouse.x < delegateItem.x + delegateItem.width)
+ held = true;
+ }
+
+ onReleased: {
+ held = false;
+ _parentView.finishSlideRearrange(true);
+ }
+
+ onCanceled: {
+ held = false;
+ _parentView.finishSlideRearrange(false);
+ }
+
+ onClicked: {
+ _parentView.deselectAll();
+ if (mouse.button === Qt.LeftButton) {
+ root.focus = true;
+ model.selected = true;
+ }
+ if (mouse.button === Qt.RightButton) {
+ const coords = mapToItem(root, mouse.x, mouse.y);
+ _parentView.showContextMenu(coords.x, coords.y, model.index);
+ }
+ }
+
+ Item {
+ id: delegateItem
+
+ anchors.centerIn: parent
+ height: column.implicitHeight
+ width: 100
+
+ Drag.keys: "application/x-slide"
+ Drag.active: delegateArea.held
+ Drag.hotSpot.x: width / 2
+ Drag.hotSpot.y: height / 2
+ Drag.source: delegateArea
+
+ Column {
+ id: column
+ spacing: 2
+ anchors.fill: parent
+ Image {
+ id: slideImage
+
+ source: {
+ if (masterSlide)
+ return _resDir + "Slide-Master-Active.png"
+ return model.selected ? _resDir + "Slide-Active.png"
+ : _resDir + "Slide-Normal.png";
+ }
+ }
+
+ Label { // variants
+ width: slideImage.width
+ font.pixelSize: 14
+ font.letterSpacing: 2
+ leftPadding: 3
+ topPadding: -3
+ bottomPadding: 8
+ background: Rectangle { color: _variantsSlideViewBGColor }
+ wrapMode: Text.WrapAnywhere
+ lineHeight: .6
+ visible: model.variants !== undefined && model.variants !== ""
+ text: model.variants ? model.variants : ""
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: {
+ _parentView.showVariantsTooltip(
+ model.index, mapToGlobal(x + width + 2, y));
+ }
+ onExited : {
+ _parentView.hideVariantsTooltip();
+ }
+ }
+ }
+
+ Item {
+ anchors.horizontalCenter: slideImage.horizontalCenter
+
+ height: childrenRect.height
+ width: childrenRect.width
+ Row {
+ StyledLabel {
+ visible: !masterSlide
+ text: model.index + 1 + ": "
+ }
+
+ TextInput {
+ id: slideName
+
+ property bool ignoreHotkeys: true
+
+ readOnly: masterSlide
+ selectByMouse: !readOnly
+ color: _textColor
+ text: model.name
+ font.pixelSize: _fontSize
+
+ onFocusChanged: {
+ if (focus && !readOnly)
+ selectAll();
+ }
+
+ onEditingFinished: {
+ model.name = text;
+ slideName.focus = false;
+ }
+
+ Keys.onEscapePressed: {
+ slideName.undo();
+ slideName.focus = false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+ keys: "application/x-slide"
+ onEntered: {
+ var oldIndex = drag.source.dragIndex
+ var newIndex = model.index
+ _parentView.moveSlide(oldIndex, newIndex)
+ drag.source.dragIndex = newIndex
+ }
+ }
+
+ states: State {
+ when: held
+
+ ParentChange {
+ target: delegateItem
+ parent: slideList
+ }
+
+ PropertyChanges {
+ target: delegateItem
+ anchors.centerIn: null
+ }
+ }
+ }
+ }
+
+ StyledMenuSeparator {
+ id: separator2
+ leftPadding: 12
+ rightPadding: 12
+ }
+ // RowLayout for possible addition and positioning of label
+ // showing the controller name
+ RowLayout {
+ Layout.rightMargin: 12
+ Layout.leftMargin: 12
+ anchors.left: parent.left
+ Button {
+ id: slideControlButton
+ width: dataInputImage.sourceSize.width
+ height: dataInputImage.sourceSize.height
+ Layout.leftMargin: 12
+ property bool controlled: _parentView.controlled
+ property string currentController: _parentView.currController
+ property string toolTip: _parentView.toolTip
+ background: Rectangle {
+ color: controlButtonArea.containsMouse ? _studioColor1 : _backgroundColor
+ }
+ MouseArea {
+ id: controlButtonArea
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton
+ onClicked: {
+ _parentView.showControllerDialog(mapToGlobal(x + width, y + height));
+ }
+ }
+ Image {
+ id: dataInputImage
+ anchors.fill: parent
+ fillMode: Image.Pad
+ property bool controlled: parent.controlled
+ source: {
+ _resDir + (controlled
+ ? "Objects-DataInput-Active.png"
+ : "Objects-DataInput-Inactive.png")
+ }
+ }
+ StyledTooltip {
+ id: tooltip
+ enabled: controlButtonArea.containsMouse
+ text: parent.toolTip
+ }
+ }
+ StyledLabel {
+ id: dataInputName
+ text: _parentView.currController
+ color: _parentView.controlled ? _dataInputColor : "transparent"
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp
new file mode 100644
index 00000000..a1e9ae40
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "BehaviorTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "StudioApp.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Doc.h"
+
+using namespace qt3dsdm;
+
+CBehaviorTimelineItemBinding::CBehaviorTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+}
+
+EStudioObjectType CBehaviorTimelineItemBinding::GetObjectType() const
+{
+ return OBJTYPE_BEHAVIOR;
+}
+
+//=============================================================================
+/**
+ * Open the associated item as though it was double-clicked in explorer
+ */
+bool CBehaviorTimelineItemBinding::OpenAssociatedEditor()
+{
+ return OpenSourcePathFile();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h
new file mode 100644
index 00000000..d8f809d1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H
+#define INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Behavior type
+ */
+class CBehaviorTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public:
+ CBehaviorTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ ~CBehaviorTimelineItemBinding() {}
+
+ // Qt3DSDMTimelineItemBinding
+ EStudioObjectType GetObjectType() const override;
+ bool OpenAssociatedEditor() override;
+};
+
+#endif // INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp
new file mode 100644
index 00000000..b135e2c0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "EmptyTimelineTimebar.h"
+#include "StudioPreferences.h"
+
+CEmptyTimelineTimebar::CEmptyTimelineTimebar()
+{
+}
+
+CEmptyTimelineTimebar::~CEmptyTimelineTimebar()
+{
+}
+
+long CEmptyTimelineTimebar::GetStartTime() const
+{
+ return 0;
+}
+
+long CEmptyTimelineTimebar::GetEndTime() const
+{
+ return 0;
+}
+
+long CEmptyTimelineTimebar::GetDuration() const
+{
+ return 0;
+}
+
+bool CEmptyTimelineTimebar::ShowHandleBars() const
+{ // makes no sense to show handle bars, when this does not have start/end times.
+ return false;
+}
+
+void CEmptyTimelineTimebar::OnBeginDrag()
+{
+}
+
+void CEmptyTimelineTimebar::OffsetTime(long inDiff)
+{
+ Q_UNUSED(inDiff);
+}
+
+void CEmptyTimelineTimebar::ChangeTime(long inTime, bool inSetStart)
+{
+ Q_UNUSED(inTime);
+ Q_UNUSED(inSetStart);
+}
+
+void CEmptyTimelineTimebar::CommitTimeChange()
+{
+}
+
+void CEmptyTimelineTimebar::RollbackTimeChange()
+{
+}
+
+::CColor CEmptyTimelineTimebar::GetTimebarColor()
+{
+ return CStudioPreferences::GetObjectTimebarColor();
+}
+
+QString CEmptyTimelineTimebar::GetTimebarComment() const
+{
+ return {};
+}
+
+void CEmptyTimelineTimebar::SetTimebarComment(const QString &inComment)
+{
+ Q_UNUSED(inComment);
+}
+
+void CEmptyTimelineTimebar::SetTimebarTime(ITimeChangeCallback *inCallback /*= nullptr*/)
+{
+ Q_UNUSED(inCallback);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h
new file mode 100644
index 00000000..09c00582
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#pragma once
+
+#include "ITimelineTimebar.h"
+
+//=============================================================================
+/**
+ * The current timeline UI design is such that even when no timebar shows up ( with the exception of
+ * the top row, ie the time context )
+ * there is a timebar control to store the keyframes for the animated properties.
+ * Hence, instead of return nullptr for GetTimebar for ITimelineItem, this class will ensure the UI
+ * classes still work.
+ */
+class CEmptyTimelineTimebar : public ITimelineTimebar
+{
+public:
+ CEmptyTimelineTimebar();
+ virtual ~CEmptyTimelineTimebar();
+
+ // ITimelineTimebar
+ long GetStartTime() const override;
+ long GetEndTime() const override;
+ long GetDuration() const override;
+ bool ShowHandleBars() const override;
+ void OnBeginDrag() override;
+ void OffsetTime(long inDiff) override;
+ void ChangeTime(long inTime, bool inSetStart) override;
+ void CommitTimeChange() override;
+ void RollbackTimeChange() override;
+ ::CColor GetTimebarColor() override;
+ QString GetTimebarComment() const override;
+ void SetTimebarComment(const QString &inComment) override;
+ void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) override;
+};
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp
new file mode 100644
index 00000000..a860b24c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "GroupTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Dialogs.h"
+
+// Data model specific
+#include "Doc.h"
+#include "CmdGeneric.h"
+
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMSlides.h"
+#include "Qt3DSDMDataCore.h"
+#include "Qt3DSFileTools.h"
+
+using namespace qt3dsdm;
+
+CGroupTimelineItemBinding::CGroupTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+}
+
+//=============================================================================
+/**
+ * Ideally we like to be able to edit the component in a different editor ( we've been hoping for
+ * that feature ) BUT we don't have that,
+ * and it has always been we 'dive' into the component within Studio.
+ */
+bool CGroupTimelineItemBinding::OpenAssociatedEditor()
+{
+ if (GetObjectType() == OBJTYPE_COMPONENT) {
+ ISlideSystem *theSlideSystem = m_StudioSystem->GetSlideSystem();
+
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ Q3DStudio::CId theId = m_StudioSystem->GetClientDataModelBridge()->GetGUID(theInstance);
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlide =
+ theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId));
+
+ if (theMasterSlide.Valid()) {
+ Qt3DSDMSlideHandle theActiveSlide = theSlideSystem->GetActiveSlide(theMasterSlide);
+
+ CCmd *theCmd = new CCmdGeneric<CDoc, Qt3DSDMSlideHandle>(
+ m_TransMgr->GetDoc(), &CDoc::NotifyActiveSlideChanged,
+ &CDoc::NotifyActiveSlideChanged, theActiveSlide, NULL, "");
+ theCmd->SetUndoable(false);
+ theCmd->SetModifiedFlag(false);
+ m_TransMgr->GetDoc()->GetCore()->ExecuteCommand(theCmd, false);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CGroupTimelineItemBinding::IsImported() const
+{
+
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ qt3dsdm::IPropertySystem *thePropertySystem =
+ m_TransMgr->GetDoc()->GetStudioSystem()->GetPropertySystem();
+ qt3dsdm::SValue theValue;
+ if (thePropertySystem->GetInstancePropertyValue(theInstance, m_TransMgr->GetDoc()
+ ->GetStudioSystem()
+ ->GetClientDataModelBridge()
+ ->GetSourcePathProperty(),
+ theValue)) {
+ qt3dsdm::TDataStrPtr theSrcPath(qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue));
+ Q3DStudio::CFilePath theFilePath(theSrcPath->GetData());
+ if (theFilePath.GetExtension() == CDialogs::GetWideImportFileExtension())
+ return true;
+ }
+ // If it is, check to be sure that
+ // we can get to the import file.
+ // If we can, then we are imported.
+ return false;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h
new file mode 100644
index 00000000..cd7f9dad
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_GROUP_TIMELINEITEM_BINDING_H
+#define INCLUDED_GROUP_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class ITimelineItem;
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Group type
+ */
+class CGroupTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public:
+ CGroupTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ ~CGroupTimelineItemBinding() {}
+
+ // Qt3DSDMTimelineItemBinding
+ bool OpenAssociatedEditor() override;
+ bool IsImported() const override;
+};
+
+#endif // INCLUDED_GROUP_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h
new file mode 100644
index 00000000..1a8beb0a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_IBREADCRUMBPROVIDER_H
+#define INCLUDED_IBREADCRUMBPROVIDER_H 1
+
+#pragma once
+
+#include <QColor>
+#include <QString>
+#include <QObject>
+
+QT_FORWARD_DECLARE_CLASS(QPixmap)
+
+struct SBreadCrumb
+{
+ QColor m_Color; /// Color for text of the bread crumb
+ QString m_String; /// Text to be displayed for the bread crumb
+};
+
+//=============================================================================
+/**
+ * A interface class for the breadcrumb control, to walk down the breadcrumb trail, without having
+ * to know any underlying implementations.
+ */
+class IBreadCrumbProvider : public QObject
+{
+ Q_OBJECT
+public:
+ typedef std::vector<SBreadCrumb> TTrailList;
+
+public:
+ virtual ~IBreadCrumbProvider() {}
+
+ virtual TTrailList GetTrail(bool inRefresh = true) = 0;
+ virtual void OnBreadCrumbClicked(long inTrailIndex) = 0;
+
+ virtual QPixmap GetRootImage() const = 0;
+ virtual QPixmap GetBreadCrumbImage() const = 0;
+ virtual QPixmap GetSeparatorImage() const = 0;
+ virtual QPixmap GetActiveBreadCrumbImage() const = 0;
+Q_SIGNALS:
+ void SigBreadCrumbUpdate();
+};
+
+#endif // INCLUDED_IBREADCRUMBPROVIDER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h
new file mode 100644
index 00000000..4308fb2b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_ITIMELINE_ITEM_H
+#define INCLUDED_ITIMELINE_ITEM_H 1
+
+#pragma once
+
+#include "INamable.h"
+#include "StudioObjectTypes.h"
+
+class ITimelineTimebar;
+
+//=============================================================================
+/**
+ * Abstraction of a data model item in the Scene. This might end up deriving from a more generic
+ * interface, so that common
+ * functions can be generalized for items in the different palettes.
+ */
+//=============================================================================
+class ITimelineItem : public INamable
+{
+public:
+ virtual ~ITimelineItem() {}
+
+ virtual EStudioObjectType GetObjectType() const = 0;
+ virtual bool IsMaster() const = 0;
+
+ virtual bool IsShy() const = 0;
+ virtual void SetShy(bool) = 0;
+ virtual bool IsLocked() const = 0;
+ virtual void SetLocked(bool) = 0;
+ virtual bool IsVisible() const = 0;
+ virtual void SetVisible(bool) = 0;
+ virtual bool IsImported() const { return false; }
+ virtual bool IsVisibilityControlled() const = 0;
+
+ // Actions
+ virtual bool HasAction(bool inMaster) = 0;
+ virtual bool ChildrenHasAction(bool inMaster) = 0;
+ virtual bool ComponentHasAction(bool inMaster) = 0;
+
+ // subpresentations
+ virtual bool hasSubpresentation() const = 0;
+
+ virtual ITimelineTimebar *GetTimebar() = 0;
+};
+
+#endif // INCLUDED_ITIMELINE_ITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h
new file mode 100644
index 00000000..cebc46d1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef INCLUDED_ITIMELINE_ITEM_BINDINGS_H
+#define INCLUDED_ITIMELINE_ITEM_BINDINGS_H 1
+
+#pragma once
+
+#include "ITimelineItem.h"
+#include "ITimelineItemProperty.h"
+#include "SIterator.h"
+
+class RowTree;
+class CControlWindowListener;
+
+// Data model specific ??
+class CDropTarget;
+
+class ITimelineItemKeyframesHolder
+{
+public:
+ virtual ~ITimelineItemKeyframesHolder() {}
+
+ virtual void InsertKeyframe() = 0;
+ virtual void DeleteAllChannelKeyframes() = 0;
+ virtual IKeyframe *GetKeyframeByTime(long inTime) const = 0;
+};
+
+//=============================================================================
+/**
+ * Interface to encapsulate data model specific functions, that Timeline UI objects can talk to.
+ */
+//=============================================================================
+class ITimelineItemBinding : public ITimelineItemKeyframesHolder
+{
+public:
+ // List of possible transactions that requires querying the data model if they are valid
+ enum EUserTransaction {
+ EUserTransaction_None,
+ EUserTransaction_Rename,
+ EUserTransaction_Duplicate,
+ EUserTransaction_Cut,
+ EUserTransaction_Copy,
+ EUserTransaction_Paste,
+ EUserTransaction_Delete,
+ EUserTransaction_MakeComponent,
+ EUserTransaction_EditComponent,
+ EUserTransaction_MakeAnimatable,
+ EUserTransaction_Group,
+ EUserTransaction_Ungroup,
+ EUserTransaction_AddLayer,
+ };
+
+public:
+ virtual ~ITimelineItemBinding() {}
+
+ virtual ITimelineItem *GetTimelineItem() = 0;
+ virtual RowTree *getRowTree() const = 0; // UI
+ virtual void setRowTree(RowTree *row) = 0;
+
+ // Events
+ virtual void SetSelected(bool multiSelect) = 0;
+ virtual void OnCollapsed() = 0;
+ virtual bool OpenAssociatedEditor() = 0;
+ virtual void SetDropTarget(CDropTarget *inTarget) = 0;
+
+ // Hierarchy
+ virtual long GetChildrenCount() = 0;
+ virtual ITimelineItemBinding *GetChild(long inIndex) = 0;
+ virtual QList<ITimelineItemBinding *> GetChildren() = 0;
+ virtual ITimelineItemBinding *GetParent() = 0;
+ virtual void SetParent(ITimelineItemBinding *parent) = 0;
+ // Properties
+ virtual long GetPropertyCount() = 0;
+ virtual ITimelineItemProperty *GetProperty(long inIndex) = 0;
+
+ // Eye/Lock toggles
+ virtual bool ShowToggleControls() const = 0;
+ virtual bool IsLockedEnabled() const = 0;
+ virtual bool IsVisibleEnabled() const = 0;
+
+ // ContextMenu
+ virtual bool IsValidTransaction(EUserTransaction inTransaction) = 0;
+ virtual void PerformTransaction(EUserTransaction inTransaction) = 0;
+ virtual Q3DStudio::CString GetObjectPath() = 0;
+
+ virtual bool IsExternalizeable() { return false; }
+ virtual void Externalize() {}
+ virtual bool IsInternalizeable() { return false; }
+ virtual void Internalize() {}
+
+ void setCreateUIRow(bool create) { m_createUIRow = create; }
+
+protected:
+ bool m_createUIRow = true; // control creation of UI row for old style timeline UI
+};
+
+//=============================================================================
+/**
+ * Helper iterator class that iterates over a ITimeline's children in a ordered (priority) list.
+ */
+//=============================================================================
+class CTimelineItemOrderedIterator : public CSIterator<ITimelineItemBinding *>
+{
+public:
+ CTimelineItemOrderedIterator(ITimelineItemBinding *inRootTimelineItem)
+ {
+ m_RootTimelineItem = inRootTimelineItem;
+ Reset();
+ }
+ bool IsDone() override { return (m_Index >= m_Total); }
+ void operator++() override { m_Index++; }
+ void operator+=(const long inNumToInc) override { m_Index += inNumToInc; }
+ ITimelineItemBinding *GetCurrent() override { return m_RootTimelineItem->GetChild(m_Index); }
+ virtual void Reset()
+ {
+ m_Index = 0;
+ m_Total = m_RootTimelineItem->GetChildrenCount();
+ }
+
+protected:
+ ITimelineItemBinding *m_RootTimelineItem;
+ long m_Index;
+ long m_Total;
+};
+
+//=============================================================================
+/**
+ * Helper iterator class that iterates over a ITimeline's properties
+ */
+//=============================================================================
+class CTimelineItemPropertyIterator : public CSIterator<ITimelineItemProperty *>
+{
+public:
+ CTimelineItemPropertyIterator(ITimelineItemBinding *inTimelineItem)
+ {
+ m_TimelineItem = inTimelineItem;
+ Reset();
+ }
+ bool IsDone() override { return (m_Index >= m_Total); }
+ void operator++() override { m_Index++; }
+ void operator+=(const long inNumToInc) override { m_Index += inNumToInc; }
+ ITimelineItemProperty *GetCurrent() override { return m_TimelineItem->GetProperty(m_Index); }
+ virtual void Reset()
+ {
+ m_Index = 0;
+ m_Total = m_TimelineItem->GetPropertyCount();
+ }
+
+protected:
+ ITimelineItemBinding *m_TimelineItem;
+ long m_Index;
+ long m_Total;
+};
+
+#endif // INCLUDED_ITIMELINE_ITEM_BINDINGS_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h
new file mode 100644
index 00000000..72488480
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_ITIMELINE_ITEM_PROPERTY_H
+#define INCLUDED_ITIMELINE_ITEM_PROPERTY_H 1
+
+#pragma once
+
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSString.h"
+
+class RowTree;
+class IKeyframe;
+
+//=============================================================================
+/**
+ * Abstraction of a data model item's property that is displayed in the Timeline.
+ */
+//=============================================================================
+class ITimelineItemProperty
+{
+public:
+ virtual ~ITimelineItemProperty() {}
+
+ virtual Q3DStudio::CString GetName() const = 0;
+ virtual bool IsMaster() const = 0;
+ virtual qt3dsdm::TDataTypePair GetType() const = 0;
+ virtual float GetMaximumValue() const = 0;
+ virtual float GetMinimumValue() const = 0;
+
+ virtual void SetSelected() = 0;
+ virtual void DeleteAllKeys() = 0;
+
+ virtual void setRowTree(RowTree *row) = 0;
+ virtual RowTree *getRowTree() const = 0;
+
+ // Keyframes
+ virtual IKeyframe *GetKeyframeByTime(long inTime) const = 0;
+ virtual IKeyframe *GetKeyframeByIndex(long inIndex) const = 0;
+ virtual long GetKeyframeCount() const = 0;
+ virtual long GetChannelCount() const = 0;
+ virtual float GetChannelValueAtTime(long inChannelIndex, long inTime) = 0;
+ virtual void SetChannelValueAtTime(long inChannelIndex, long inTime, float inValue) = 0;
+ virtual bool IsDynamicAnimation() = 0;
+};
+
+#endif // INCLUDED_ITIMELINE_ITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h
new file mode 100644
index 00000000..2a5ef827
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef INCLUDED_ITIMELINE_TIMEBAR_H
+#define INCLUDED_ITIMELINE_TIMEBAR_H 1
+
+#pragma once
+
+#include "Qt3DSString.h"
+
+class ITimeChangeCallback;
+
+class CColor;
+
+//=============================================================================
+/**
+ * Interface for a Timebar
+ */
+//=============================================================================
+class ITimelineTimebar
+{
+public:
+ virtual ~ITimelineTimebar() {}
+
+ virtual long GetStartTime() const = 0;
+ virtual long GetEndTime() const = 0;
+ virtual long GetDuration() const = 0;
+ virtual bool ShowHandleBars() const = 0;
+ //=============================================================================
+ /**
+ * TODO: consider refactor. drag&drop specfics should not be in the Data Model.
+ */
+ virtual void OnBeginDrag() = 0;
+ //
+ virtual void OffsetTime(long inDiff) = 0;
+ // Change the start time or the end time of the timebar. inTime: time to change to, inSetStart:
+ // true to set start time, false to set end time.
+ virtual void ChangeTime(long inTime, bool inSetStart) = 0;
+ virtual void CommitTimeChange() = 0;
+ virtual void RollbackTimeChange() = 0;
+ //
+ virtual CColor GetTimebarColor() = 0;
+ virtual QString GetTimebarComment() const = 0;
+ virtual void SetTimebarComment(const QString &inComment) = 0;
+ virtual void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) = 0;
+};
+
+#endif // INCLUDED_ITIMELINE_TIMEBAR_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp
new file mode 100644
index 00000000..ac0fa169
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "ImageTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "EmptyTimelineTimebar.h"
+
+using namespace qt3dsdm;
+
+CImageTimelineItemBinding::CImageTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+}
+
+CImageTimelineItemBinding::~CImageTimelineItemBinding()
+{
+}
+
+ITimelineTimebar *CImageTimelineItemBinding::GetTimebar()
+{ // No timebars on images
+ return new CEmptyTimelineTimebar();
+}
+
+Q3DStudio::CString CImageTimelineItemBinding::GetName() const
+{
+ return m_Name;
+}
+
+void CImageTimelineItemBinding::SetName(const Q3DStudio::CString &inName)
+{
+ m_Name = inName;
+}
+
+EStudioObjectType CImageTimelineItemBinding::GetObjectType() const
+{
+ return OBJTYPE_IMAGE;
+}
+
+bool CImageTimelineItemBinding::ShowToggleControls() const
+{
+ // no toggle controls, by design
+ return false;
+}
+
+//=============================================================================
+/**
+ * Open the associated item as though it was double-clicked in explorer
+ */
+bool CImageTimelineItemBinding::OpenAssociatedEditor()
+{
+ return OpenSourcePathFile();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h
new file mode 100644
index 00000000..b9343872
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_IMAGE_TIMELINEITEM_BINDING_H
+#define INCLUDED_IMAGE_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+class ITimelineTimebar;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Image type
+ */
+class CImageTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public:
+ CImageTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ virtual ~CImageTimelineItemBinding();
+
+ // Qt3DSDMTimelineItemBinding
+ ITimelineTimebar *GetTimebar() override;
+ Q3DStudio::CString GetName() const override;
+ void SetName(const Q3DStudio::CString &inName) override;
+ EStudioObjectType GetObjectType() const override;
+ bool ShowToggleControls() const override;
+ bool OpenAssociatedEditor() override;
+
+ void SetPropertyHandle(qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+ {
+ m_PropertyHandle = inProperty;
+ }
+ qt3dsdm::Qt3DSDMPropertyHandle GetPropertyHandle() const { return m_PropertyHandle; }
+
+protected:
+ Q3DStudio::CString m_Name;
+ qt3dsdm::Qt3DSDMPropertyHandle m_PropertyHandle;
+};
+
+#endif // INCLUDED_IMAGE_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp
new file mode 100644
index 00000000..79d5da54
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp
@@ -0,0 +1,254 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "LayerTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "ImageTimelineItemBinding.h"
+#include "EmptyTimelineTimebar.h"
+
+// Data model specific
+#include "IDoc.h"
+#include "ClientDataModelBridge.h"
+#include "DropSource.h"
+#include "Doc.h"
+
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMStudioSystem.h"
+
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSDMDataCore.h"
+#include "StudioFullSystem.h"
+#include "StudioCoreSystem.h"
+#include "Qt3DSDMSlides.h"
+
+using namespace qt3dsdm;
+
+namespace {
+
+bool ImageSlotIsFilled(qt3dsdm::IPropertySystem *inPropertySystem, Qt3DSDMInstanceHandle inInstance,
+ const TCharStr &inStr)
+{
+ Qt3DSDMPropertyHandle theProperty =
+ inPropertySystem->GetAggregateInstancePropertyByName(inInstance, inStr);
+ SValue theValue;
+ inPropertySystem->GetInstancePropertyValue(inInstance, theProperty, theValue);
+
+ SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue);
+ bool theReturn = theLong4.m_Longs[0] != 0 || theLong4.m_Longs[1] != 0
+ || theLong4.m_Longs[2] != 0 || theLong4.m_Longs[3] != 0;
+
+ return theReturn;
+}
+}
+
+CLayerTimelineItemBinding::CLayerTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ TPropertyHandleList theProperties;
+ thePropertySystem->GetAggregateInstanceProperties(inDataHandle, theProperties);
+
+ size_t thePropertyCount = theProperties.size();
+ for (size_t thePropertyIndex = 0; thePropertyIndex < thePropertyCount; ++thePropertyIndex) {
+ Qt3DSDMPropertyHandle theProperty = theProperties[thePropertyIndex];
+
+ AdditionalMetaDataType::Value theAdditionalMetaDataType =
+ thePropertySystem->GetAdditionalMetaDataType(inDataHandle, theProperty);
+
+ if (theAdditionalMetaDataType == AdditionalMetaDataType::Image) {
+ TCharStr theName(thePropertySystem->GetName(theProperty));
+ TCharStr theFormalName(thePropertySystem->GetFormalName(inDataHandle, theProperty));
+ TNameFormalNamePair thePair =
+ std::make_tuple(theName, theFormalName, theProperty);
+ m_ImageNameFormalNamePairs.push_back(thePair);
+ }
+ }
+}
+
+CLayerTimelineItemBinding::~CLayerTimelineItemBinding()
+{
+}
+
+EStudioObjectType CLayerTimelineItemBinding::GetObjectType() const
+{
+ return OBJTYPE_LAYER;
+}
+
+ITimelineItemBinding *CLayerTimelineItemBinding::GetChild(long inIndex)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ Q3DStudio::CGraphIterator theChildren;
+ Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(),
+ theChildren, theActiveSlide);
+ theChildren += inIndex;
+ return GetOrCreateBinding(theChildren.GetCurrent());
+ }
+ return nullptr;
+}
+
+QList<ITimelineItemBinding *> CLayerTimelineItemBinding::GetChildren()
+{
+ QList<ITimelineItemBinding *> retlist;
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ Q3DStudio::CGraphIterator theChildren;
+ Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(),
+ theChildren, theActiveSlide);
+ int childCount = int(theChildren.GetCount());
+ retlist.reserve(childCount);
+ for (int i = 0; i < childCount; ++i) {
+ retlist.append(GetOrCreateBinding(theChildren.GetCurrent()));
+ ++theChildren;
+ }
+ }
+
+ return retlist;
+}
+
+void CLayerTimelineItemBinding::OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ using namespace qt3dsdm;
+ CClientDataModelBridge *theBridge = m_TransMgr->GetStudioSystem()->GetClientDataModelBridge();
+ // This is handled via the OnPropertyChanged call below
+ if (theBridge->IsImageInstance(inInstance))
+ return;
+ else
+ Qt3DSDMTimelineItemBinding::OnAddChild(inInstance);
+}
+
+void CLayerTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ Qt3DSDMTimelineItemBinding::OnPropertyChanged(inPropertyHandle);
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle
+CLayerTimelineItemBinding::GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ SValue theImageValue;
+ thePropertySystem->GetInstancePropertyValue(m_DataHandle, inPropertyHandle, theImageValue);
+ SLong4 theImageLong4 = qt3dsdm::get<SLong4>(theImageValue);
+ return m_TransMgr->GetStudioSystem()->GetClientDataModelBridge()->GetImageInstanceByGUID(
+ theImageLong4);
+}
+
+ITimelineItemBinding *
+CLayerTimelineItemBinding::GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ const wchar_t *inName)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theImageInstance = GetImage(inPropertyHandle);
+ if (!theImageInstance.Valid())
+ return nullptr;
+ ITimelineItemBinding *theImageTimelineRow = m_TransMgr->GetBinding(theImageInstance);
+ if (!theImageTimelineRow) // create
+ {
+ theImageTimelineRow = m_TransMgr->GetOrCreate(theImageInstance);
+ // Set the name, by spec: the nice name.
+ theImageTimelineRow->GetTimelineItem()->SetName(inName);
+ CImageTimelineItemBinding *theImageBinding =
+ dynamic_cast<CImageTimelineItemBinding *>(theImageTimelineRow);
+ if (theImageBinding)
+ theImageBinding->SetPropertyHandle(inPropertyHandle);
+ }
+ return theImageTimelineRow;
+}
+
+ITimelineItemBinding *CLayerTimelineItemBinding::GetOrCreateBinding(Qt3DSDMInstanceHandle instance)
+{
+ if (instance.Valid()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ qt3dsdm::IPropertySystem *thePropertySystem =
+ m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ std::shared_ptr<IDataCore> theDataCore =
+ m_TransMgr->GetStudioSystem()->GetFullSystem()->GetCoreSystem()->GetDataCore();
+ ISlideSystem *theSlideSystem = m_TransMgr->GetStudioSystem()->GetSlideSystem();
+ ISlideCore *theSlideCore = m_TransMgr->GetStudioSystem()->GetSlideCore();
+
+ size_t theSlotCursor = (size_t)-1;
+ {
+ qt3dsdm::IPropertySystem *thePropertySystem =
+ m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ qt3dsdm::SLong4 theGuid;
+ {
+ Qt3DSDMPropertyHandle theTypeProperty =
+ thePropertySystem->GetAggregateInstancePropertyByName(instance, L"id");
+ SValue theIdValue;
+ thePropertySystem->GetInstancePropertyValue(instance, theTypeProperty, theIdValue);
+ theGuid = qt3dsdm::get<qt3dsdm::SLong4>(theIdValue);
+ }
+ for (size_t theSlotIndex = 0, theSlotCount = m_ImageNameFormalNamePairs.size();
+ theSlotIndex < theSlotCount; ++theSlotIndex) {
+ bool theIsMatch = false;
+ qt3dsdm::Qt3DSDMPropertyHandle theProperty =
+ std::get<2>(m_ImageNameFormalNamePairs[theSlotIndex]);
+ SValue theValue;
+ const Qt3DSDMPropertyDefinition &theDefinition(
+ theDataCore->GetProperty(theProperty));
+ if (theDefinition.m_Type == DataModelDataType::Long4) {
+ SValue theDCValue;
+ if (theDataCore->GetInstancePropertyValue(theInstance, theProperty,
+ theDCValue)) {
+ SLong4 thePropGuid = get<SLong4>(theDCValue);
+ if (thePropGuid == theGuid)
+ theIsMatch = true;
+ }
+ Qt3DSDMSlideHandle theSlide =
+ theSlideSystem->GetAssociatedSlide(instance);
+ Qt3DSDMSlideHandle theMasterSlide = theSlideSystem->GetMasterSlide(theSlide);
+ if (theIsMatch == false && theSlide.Valid()
+ && theSlideCore->GetSpecificInstancePropertyValue(
+ theSlide, theInstance, theProperty, theValue)) {
+ SLong4 thePropGuid = get<SLong4>(theValue);
+ if (thePropGuid == theGuid)
+ theIsMatch = true;
+ }
+ }
+ if (theIsMatch) {
+ theSlotCursor = theSlotIndex;
+ break;
+ }
+ }
+ }
+ if (theSlotCursor != (size_t)-1) {
+ Qt3DSDMPropertyHandle theImageProperty =
+ thePropertySystem->GetAggregateInstancePropertyByName(
+ m_DataHandle, std::get<0>(m_ImageNameFormalNamePairs[theSlotCursor]));
+ return GetOrCreateImageBinding(
+ theImageProperty,
+ std::get<1>(m_ImageNameFormalNamePairs[theSlotCursor]).wide_str());
+ } else
+ return m_TransMgr->GetOrCreate(instance);
+ }
+ return nullptr;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h
new file mode 100644
index 00000000..74630a64
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_LAYER_TIMELINEITEM_BINDING_H
+#define INCLUDED_LAYER_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+
+namespace qt3dsdm {
+class CStudioSystem;
+}
+
+//=============================================================================
+/**
+ * Binding to generic DataModel object
+ */
+class CLayerTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public: // Types
+ typedef std::tuple<qt3dsdm::TCharStr, qt3dsdm::TCharStr, qt3dsdm::Qt3DSDMPropertyHandle>
+ TNameFormalNamePair;
+ typedef std::vector<TNameFormalNamePair> TNameFormalNamePairList;
+
+protected: // Members
+ TNameFormalNamePairList m_ImageNameFormalNamePairs;
+
+public: // Construction
+ CLayerTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ virtual ~CLayerTimelineItemBinding();
+
+public: // Qt3DSDMTimelineItemBinding
+ EStudioObjectType GetObjectType() const override;
+ // Hierarchy
+ ITimelineItemBinding *GetChild(long inIndex) override;
+ QList<ITimelineItemBinding *> GetChildren() override;
+ void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ // Event callback
+ void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override;
+
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+ ITimelineItemBinding *GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ const wchar_t *inName);
+ ITimelineItemBinding *GetOrCreateBinding(qt3dsdm::Qt3DSDMInstanceHandle instance);
+};
+
+#endif // INCLUDED_LAYER_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp
new file mode 100644
index 00000000..e3483969
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp
@@ -0,0 +1,210 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "MaterialTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "ImageTimelineItemBinding.h"
+#include "EmptyTimelineTimebar.h"
+
+// Data model specific
+#include "IDoc.h"
+#include "ClientDataModelBridge.h"
+#include "DropSource.h"
+
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMStudioSystem.h"
+
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSDMDataCore.h"
+#include "StudioFullSystem.h"
+#include "StudioCoreSystem.h"
+
+using namespace qt3dsdm;
+
+CMaterialTimelineItemBinding::CMaterialTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ TPropertyHandleList theProperties;
+ thePropertySystem->GetAggregateInstanceProperties(inDataHandle, theProperties);
+
+ size_t thePropertyCount = theProperties.size();
+ for (size_t thePropertyIndex = 0; thePropertyIndex < thePropertyCount; ++thePropertyIndex) {
+ Qt3DSDMPropertyHandle theProperty = theProperties[thePropertyIndex];
+
+ AdditionalMetaDataType::Value theAdditionalMetaDataType =
+ thePropertySystem->GetAdditionalMetaDataType(inDataHandle, theProperty);
+
+ if (theAdditionalMetaDataType == AdditionalMetaDataType::Image) {
+ TCharStr theName(thePropertySystem->GetName(theProperty));
+ TCharStr theFormalName(thePropertySystem->GetFormalName(inDataHandle, theProperty));
+ TNameFormalNamePair thePair = std::make_tuple(theName, theFormalName);
+ m_ImageNameFormalNamePairs.push_back(thePair);
+ }
+ }
+}
+
+CMaterialTimelineItemBinding::~CMaterialTimelineItemBinding()
+{
+}
+
+ITimelineTimebar *CMaterialTimelineItemBinding::GetTimebar()
+{ // No timebars on materials
+ return new CEmptyTimelineTimebar();
+}
+
+bool CMaterialTimelineItemBinding::ShowToggleControls() const
+{
+ // Materials have no toggle controls, by design
+ return false;
+}
+
+bool ImageSlotIsFilled(qt3dsdm::IPropertySystem *inPropertySystem, Qt3DSDMInstanceHandle inInstance,
+ const TCharStr &inStr)
+{
+ Qt3DSDMPropertyHandle theProperty =
+ inPropertySystem->GetAggregateInstancePropertyByName(inInstance, inStr);
+ SValue theValue;
+ inPropertySystem->GetInstancePropertyValue(inInstance, theProperty, theValue);
+
+ // Prevent assertion down the path when changing from edited standard material to reference material
+ if (qt3dsdm::GetValueType(theValue) == DataModelDataType::None)
+ return false;
+
+ SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue);
+ bool theReturn = theLong4.m_Longs[0] != 0 || theLong4.m_Longs[1] != 0
+ || theLong4.m_Longs[2] != 0 || theLong4.m_Longs[3] != 0;
+
+ return theReturn;
+}
+
+long CMaterialTimelineItemBinding::GetChildrenCount()
+{
+ long theReturnCount = 0;
+ if (m_TransMgr->GetStudioSystem()->IsInstance(m_DataHandle)) {
+ qt3dsdm::IPropertySystem *thePropertySystem =
+ m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ size_t theSlotCount = m_ImageNameFormalNamePairs.size();
+ for (size_t theSlotIndex = 0; theSlotIndex < theSlotCount; ++theSlotIndex) {
+ if (ImageSlotIsFilled(thePropertySystem, m_DataHandle,
+ std::get<0>(m_ImageNameFormalNamePairs[theSlotIndex]))) {
+ ++theReturnCount;
+ }
+ }
+ }
+
+ return theReturnCount;
+}
+
+ITimelineItemBinding *CMaterialTimelineItemBinding::GetChild(long inIndex)
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+
+ size_t theSlotCursor = 0;
+ size_t theSlotCount = m_ImageNameFormalNamePairs.size();
+ for (size_t theSlotIndex = 0; theSlotIndex < theSlotCount; ++theSlotIndex) {
+ if (ImageSlotIsFilled(thePropertySystem, m_DataHandle,
+ std::get<0>(m_ImageNameFormalNamePairs[theSlotIndex]))) {
+ inIndex--;
+
+ if (inIndex < 0) {
+ theSlotCursor = theSlotIndex;
+ break;
+ }
+ }
+ }
+ Qt3DSDMPropertyHandle theImageProperty = thePropertySystem->GetAggregateInstancePropertyByName(
+ m_DataHandle, std::get<0>(m_ImageNameFormalNamePairs[theSlotCursor]));
+ return GetOrCreateImageBinding(
+ theImageProperty, std::get<1>(m_ImageNameFormalNamePairs[theSlotCursor]).wide_str());
+}
+
+QList<ITimelineItemBinding *> CMaterialTimelineItemBinding::GetChildren()
+{
+ int childCount = GetChildrenCount();
+ QList<ITimelineItemBinding *> retlist;
+ retlist.reserve(childCount);
+ for (int i = 0; i < childCount; ++i)
+ retlist.append(GetChild(i));
+
+ return retlist;
+}
+
+void CMaterialTimelineItemBinding::OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ using namespace qt3dsdm;
+ CClientDataModelBridge *theBridge = m_TransMgr->GetStudioSystem()->GetClientDataModelBridge();
+ // This is handled via the OnPropertyChanged call below
+ if (theBridge->IsImageInstance(inInstance))
+ return;
+ else
+ Qt3DSDMTimelineItemBinding::OnAddChild(inInstance);
+}
+
+void CMaterialTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ Qt3DSDMTimelineItemBinding::OnPropertyChanged(inPropertyHandle);
+}
+
+void CMaterialTimelineItemBinding::OnPropertyLinked(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ Qt3DSDMTimelineItemBinding::OnPropertyLinked(inPropertyHandle);
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle
+CMaterialTimelineItemBinding::GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ SValue theImageValue;
+ thePropertySystem->GetInstancePropertyValue(m_DataHandle, inPropertyHandle, theImageValue);
+ SLong4 theImageLong4 = qt3dsdm::get<SLong4>(theImageValue);
+ return m_TransMgr->GetStudioSystem()->GetClientDataModelBridge()->GetImageInstanceByGUID(
+ theImageLong4);
+}
+
+ITimelineItemBinding *
+CMaterialTimelineItemBinding::GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ const wchar_t *inName)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theImageInstance = GetImage(inPropertyHandle);
+ ITimelineItemBinding *theImageTimelineRow = m_TransMgr->GetBinding(theImageInstance);
+ if (!theImageTimelineRow) // create
+ {
+ theImageTimelineRow = m_TransMgr->GetOrCreate(theImageInstance);
+ // Set the name, by spec: the nice name.
+ theImageTimelineRow->GetTimelineItem()->SetName(inName);
+ CImageTimelineItemBinding *theImageBinding =
+ dynamic_cast<CImageTimelineItemBinding *>(theImageTimelineRow);
+ if (theImageBinding)
+ theImageBinding->SetPropertyHandle(inPropertyHandle);
+ }
+ return theImageTimelineRow;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h
new file mode 100644
index 00000000..8e188a52
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H
+#define INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+#include "Qt3DSDMDataTypes.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Material type
+ */
+class CMaterialTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public: // Types
+ typedef std::tuple<qt3dsdm::TCharStr, qt3dsdm::TCharStr> TNameFormalNamePair;
+ typedef std::vector<TNameFormalNamePair> TNameFormalNamePairList;
+
+protected: // Members
+ TNameFormalNamePairList m_ImageNameFormalNamePairs;
+
+public: // Construction
+ CMaterialTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ virtual ~CMaterialTimelineItemBinding();
+
+public: // Qt3DSDMTimelineItemBinding
+ ITimelineTimebar *GetTimebar() override;
+ bool ShowToggleControls() const override;
+ // Hierarchy
+ long GetChildrenCount() override;
+ ITimelineItemBinding *GetChild(long inIndex) override;
+ QList<ITimelineItemBinding *> GetChildren() override;
+ void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ // Event callback
+ void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override;
+ void OnPropertyLinked(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override;
+
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+ ITimelineItemBinding *GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ const wchar_t *inName);
+};
+
+#endif // INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp
new file mode 100644
index 00000000..a0c4ee99
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "OffsetKeyframesCommandHelper.h"
+#include "Core.h"
+
+// Data model specific
+#include "IDoc.h"
+#include "CmdDataModelChangeKeyframe.h"
+#include "IDocumentEditor.h"
+
+#include "Qt3DSDMTimelineKeyframe.h" //TODO: remove once we resolve the precision issue
+
+using namespace qt3dsdm;
+
+COffsetKeyframesCommandHelper::COffsetKeyframesCommandHelper(CDoc &inDoc)
+ : Q3DStudio::CUpdateableDocumentEditor(inDoc)
+ , m_Doc(inDoc)
+{
+}
+
+COffsetKeyframesCommandHelper::~COffsetKeyframesCommandHelper()
+{
+ Finalize();
+}
+
+//@param inTime time in millisecs
+void COffsetKeyframesCommandHelper::SetCommandTime(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ long inTime)
+{
+ // The DataModel system will take care of merging these under the hood.
+ ENSURE_EDITOR(QObject::tr("Set Keyframe Time")).SetKeyframeTime(inKeyframe, inTime);
+}
+
+// equivalent to commit (onmouseup)
+void COffsetKeyframesCommandHelper::Finalize()
+{
+ CommitEditor();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h
new file mode 100644
index 00000000..0e57773e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_OFFSET_KEYFRAMES_COMMAND_HELPER_H
+#define INCLUDED_OFFSET_KEYFRAMES_COMMAND_HELPER_H 1
+
+#pragma once
+
+// Data model
+#include "Qt3DSDMHandles.h"
+#include "IDocumentEditor.h"
+
+namespace Q3DStudio {
+class IDocumentEditor;
+}
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CCmdDataModelSetKeyframeTime;
+
+class COffsetKeyframesCommandHelper : public Q3DStudio::CUpdateableDocumentEditor
+{
+protected:
+ CDoc &m_Doc;
+
+public:
+ COffsetKeyframesCommandHelper(CDoc &inDoc);
+ ~COffsetKeyframesCommandHelper();
+
+ void SetCommandTime(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, long inTime);
+ void Finalize();
+ void Rollback() { RollbackEditor(); }
+};
+
+#endif \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h
new file mode 100644
index 00000000..ead15d2e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_PASTE_KEYFRAME_COMMAND_HELPER_H
+#define INCLUDED_PASTE_KEYFRAME_COMMAND_HELPER_H 1
+
+#pragma once
+
+//==============================================================================
+// Include
+//==============================================================================
+#include "CmdDataModelInsertKeyframe.h"
+#include "Qt3DSDMPropertyDefinition.h"
+#include "Qt3DSDMDataCore.h"
+
+// This caches all copied keyframes' time and data, for a paste action.
+// This has to deal with the actual data and not keyframe handles, because a prior Cut can
+// invalidate those handles.
+class CPasteKeyframeCommandHelper
+{
+protected:
+ typedef std::vector<CCmdDataModelInsertKeyframe::STimeKeyframeData> TCopiedKeyframeList;
+ TCopiedKeyframeList m_CopiedKeyframeList;
+
+public: // Construction
+ CPasteKeyframeCommandHelper() {}
+ ~CPasteKeyframeCommandHelper() {}
+
+ // inTime should be relative to the earliest keyframe time in this list
+ void AddKeyframeData(qt3dsdm::Qt3DSDMPropertyHandle inProperty, float inKeyframeTime,
+ qt3dsdm::SGetOrSetKeyframeInfo *inInfos, size_t inInfoCount)
+ {
+ m_CopiedKeyframeList.push_back(CCmdDataModelInsertKeyframe::STimeKeyframeData(
+ inProperty, inKeyframeTime, inInfos, inInfoCount));
+ }
+
+ bool HasCopiedKeyframes() const { return !m_CopiedKeyframeList.empty(); }
+
+ // Triggered by a "Paste Keyframe" action
+ // Note: The logic is based on what the Animation Manager in the old system used to do.
+ // 1. The condition for paste to occur is that the property name matches.
+ // The old data model has a limitation that if the destination property is a linked property,
+ // the source has to come from the same instance, most likely a easy way out than to deal with
+ // with having to 'sync' all linked animation tracks.
+ // but that is not an issue in the new data model.
+ //
+ // 2. The first pasted keyframe is at current view time and the rest are offset accordingly.
+ CCmdDataModelInsertKeyframe *GetCommand(CDoc *inDoc, long inTimeOffsetInMilliseconds,
+ qt3dsdm::Qt3DSDMInstanceHandle inTargetInstance)
+ {
+ using namespace qt3dsdm;
+
+ CCmdDataModelInsertKeyframe *theInsertKeyframesCommand = nullptr;
+ TCopiedKeyframeList::iterator theIter = m_CopiedKeyframeList.begin();
+ qt3dsdm::IPropertySystem *thePropertySystem = inDoc->GetStudioSystem()->GetPropertySystem();
+ CClientDataModelBridge *theBridge = inDoc->GetStudioSystem()->GetClientDataModelBridge();
+
+ for (; theIter != m_CopiedKeyframeList.end(); ++theIter) {
+ TCharStr thePropertyName = thePropertySystem->GetName(theIter->m_Property);
+ DataModelDataType::Value thePropertyType =
+ thePropertySystem->GetDataType(theIter->m_Property);
+ Qt3DSDMPropertyHandle theTargetPropertyHandle =
+ theBridge->GetAggregateInstancePropertyByName(inTargetInstance, thePropertyName);
+ if (theTargetPropertyHandle.Valid()) // property exists on target
+ {
+ // sanity check for type match
+ DataModelDataType::Value theTargetPropertyType =
+ thePropertySystem->GetDataType(theTargetPropertyHandle);
+ if (theTargetPropertyType == thePropertyType) {
+ // 2. Offset keyframe time by current view time
+ double milliseconds = theIter->m_KeyframeTime * 1000.0;
+ double theTimeInMilliseconds = milliseconds + inTimeOffsetInMilliseconds;
+ float theTimeInSeconds = static_cast<float>(theTimeInMilliseconds / 1000.0);
+
+ if (!theInsertKeyframesCommand)
+ theInsertKeyframesCommand = new CCmdDataModelInsertKeyframe(
+ inDoc, inTargetInstance, theTargetPropertyHandle, theTimeInSeconds,
+ theIter->m_Infos, theIter->m_ValidInfoCount);
+ else
+ theInsertKeyframesCommand->AddKeyframeData(
+ theTargetPropertyHandle, theTimeInSeconds, theIter->m_Infos,
+ theIter->m_ValidInfoCount);
+ }
+ }
+ }
+ return theInsertKeyframesCommand;
+ }
+
+ void Clear() { m_CopiedKeyframeList.clear(); }
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp
new file mode 100644
index 00000000..192c4414
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "Qt3DSCommonPrecompile.h"
+#include "PathAnchorPointTimelineItemBinding.h"
+#include "EmptyTimelineTimebar.h"
+
+using namespace qt3dsdm;
+
+CPathAnchorPointTimelineItemBinding::CPathAnchorPointTimelineItemBinding(
+ CTimelineTranslationManager *inMgr, Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+{
+}
+
+ITimelineTimebar *CPathAnchorPointTimelineItemBinding::GetTimebar()
+{
+ return new CEmptyTimelineTimebar();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h
new file mode 100644
index 00000000..b806093d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef PATH_ANCHOR_POINT_TIMELINE_ITEM_BINDING
+#define PATH_ANCHOR_POINT_TIMELINE_ITEM_BINDING
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+#include "Qt3DSDMDataTypes.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Material type
+ */
+class CPathAnchorPointTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public: // Construction
+ CPathAnchorPointTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+
+ bool ShowToggleControls() const override { return false; }
+ bool IsVisible() const override { return true; }
+ void SetVisible(bool) override {}
+ ITimelineTimebar *GetTimebar() override;
+ bool IsLocked() const override { return false; }
+ void SetLocked(bool) override {}
+ bool IsShy() const override { return false; }
+ void SetShy(bool) override {}
+ bool IsVisibilityControlled() const override { return false; }
+ Q3DStudio::CString GetName() const override { return L"Anchor Point"; }
+ void SetName(const Q3DStudio::CString &) override {}
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp
new file mode 100644
index 00000000..f646415d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "Qt3DSCommonPrecompile.h"
+#include "PathTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "Doc.h"
+
+bool CPathTimelineItemBinding::IsExternalizeable()
+{
+ // If this path has subpath children, then it is externalizeable.
+ return m_TransMgr->GetDoc()->GetDocumentReader().IsPathExternalizeable(GetInstance());
+}
+
+void CPathTimelineItemBinding::Externalize()
+{
+ Q3DStudio::ScopedDocumentEditor(*m_TransMgr->GetDoc(), QObject::tr("Externalize Path Buffer"),
+ __FILE__, __LINE__)
+ ->ExternalizePath(GetInstance());
+}
+
+bool CPathTimelineItemBinding::IsInternalizeable()
+{
+ // If this path has a sourcepath, then it might be internalizeable
+ return m_TransMgr->GetDoc()->GetDocumentReader().IsPathInternalizeable(GetInstance());
+}
+
+void CPathTimelineItemBinding::Internalize()
+{
+ Q3DStudio::ScopedDocumentEditor(*m_TransMgr->GetDoc(), QObject::tr("Internalize Path Buffer"),
+ __FILE__, __LINE__)
+ ->InternalizePath(GetInstance());
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h
new file mode 100644
index 00000000..ea006044
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef PATH_TIMELINE_ITEM_BINDING
+#define PATH_TIMELINE_ITEM_BINDING
+#pragma once
+#include "Qt3DSDMTimelineItemBinding.h"
+#include "Qt3DSDMDataTypes.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Material type
+ */
+class CPathTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public: // Construction
+ CPathTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle)
+ {
+ }
+
+ bool IsExternalizeable() override;
+ void Externalize() override;
+ bool IsInternalizeable() override;
+ void Internalize() override;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp
new file mode 100644
index 00000000..65b0d852
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "Qt3DSDMAssetTimelineKeyframe.h"
+#include "Qt3DSDMTimelineItemBinding.h"
+
+using namespace qt3dsdm;
+
+Qt3DSDMAssetTimelineKeyframe::Qt3DSDMAssetTimelineKeyframe(Qt3DSDMTimelineItemBinding *inOwningBinding,
+ long inTime)
+ : m_OwningBinding(inOwningBinding)
+ , m_Time(inTime)
+ , m_Selected(false)
+{
+}
+
+Qt3DSDMAssetTimelineKeyframe::~Qt3DSDMAssetTimelineKeyframe()
+{
+}
+
+Keyframe *Qt3DSDMAssetTimelineKeyframe::getUI()
+{
+ return m_ui;
+}
+
+void Qt3DSDMAssetTimelineKeyframe::setUI(Keyframe *kfUI)
+{
+ m_ui = kfUI;
+}
+
+bool Qt3DSDMAssetTimelineKeyframe::IsSelected() const
+{
+ return m_Selected;
+}
+
+long Qt3DSDMAssetTimelineKeyframe::GetTime() const
+{
+ return m_Time;
+}
+
+void Qt3DSDMAssetTimelineKeyframe::SetTime(const long inNewTime)
+{
+ Q_UNUSED(inNewTime);
+ // note: this is not used. because setting time is currently only done through offsetting by
+ // moving keyframes OR using the edit time dialog.
+ Q_ASSERT(0);
+}
+
+void Qt3DSDMAssetTimelineKeyframe::SetDynamic(bool inIsDynamic)
+{
+ m_OwningBinding->SetDynamicKeyframes(m_Time, inIsDynamic);
+}
+
+bool Qt3DSDMAssetTimelineKeyframe::IsDynamic() const
+{
+ // return true if any of its property keyframes is dynamic
+ return m_OwningBinding->HasDynamicKeyframes(m_Time);
+}
+
+void Qt3DSDMAssetTimelineKeyframe::SetSelected(bool inSelected)
+{
+ m_Selected = inSelected;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h
new file mode 100644
index 00000000..cf99cf8c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QT3DSDM_ASSET_KEYFRAME_H
+#define QT3DSDM_ASSET_KEYFRAME_H 1
+
+#pragma once
+
+#include "IKeyframe.h"
+
+// Data model specific
+#include "Qt3DSDMHandles.h"
+
+class Qt3DSDMTimelineItemBinding;
+
+//==============================================================================
+/**
+ * Represents a keyframe displayed for a Asset( e.g. material ), for all keyframes (of the
+ *animated properties) at time t.
+ */
+//==============================================================================
+class Qt3DSDMAssetTimelineKeyframe : public IKeyframe
+{
+protected:
+ Qt3DSDMTimelineItemBinding *m_OwningBinding;
+ long m_Time;
+ bool m_Selected;
+
+public:
+ Qt3DSDMAssetTimelineKeyframe(Qt3DSDMTimelineItemBinding *inOwningBinding, long inTime);
+ virtual ~Qt3DSDMAssetTimelineKeyframe();
+
+ // IKeyframe
+ bool IsSelected() const override;
+ long GetTime() const override;
+ void SetTime(const long inNewTime) override;
+ void SetDynamic(bool inIsDynamic) override;
+ Keyframe *getUI() override;
+ void setUI(Keyframe *kfUI) override;
+ bool IsDynamic() const override;
+
+ void SetSelected(bool inSelected);
+ void UpdateTime(const long inTime) { m_Time = inTime; }
+
+private:
+ Keyframe *m_ui;
+};
+
+#endif // QT3DSDM_ASSET_KEYFRAME_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h
new file mode 100644
index 00000000..18624897
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT3DSDM_TIMELINE_H
+#define QT3DSDM_TIMELINE_H 1
+
+#pragma once
+
+enum ETimelineKeyframeTransaction {
+ ETimelineKeyframeTransaction_Add,
+ ETimelineKeyframeTransaction_Delete,
+ ETimelineKeyframeTransaction_Update,
+ ETimelineKeyframeTransaction_DynamicChanged,
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp
new file mode 100644
index 00000000..d71ef582
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp
@@ -0,0 +1,1164 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "Qt3DSDMTimelineItemBinding.h"
+#include "TimelineTranslationManager.h"
+#include "TimeEditDlg.h"
+#include "EmptyTimelineTimebar.h"
+#include "Qt3DSDMTimelineTimebar.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Dialogs.h"
+#include "GraphUtils.h"
+#include "Qt3DSDMDataCore.h"
+#include "DropTarget.h"
+
+// Data model specific
+#include "IDoc.h"
+#include "ClientDataModelBridge.h"
+#include "Dispatch.h"
+#include "DropSource.h"
+#include "Qt3DSDMTimelineItemProperty.h"
+#include "Qt3DSDMSlides.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlideGraphCore.h"
+#include "Qt3DSDMActionCore.h"
+#include "Qt3DSDMAnimation.h"
+#include "CmdDataModelChangeKeyframe.h"
+#include "RelativePathTools.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSFileTools.h"
+#include "ImportUtils.h"
+
+#include <QtWidgets/qmessagebox.h>
+
+using namespace qt3dsdm;
+
+Qt3DSDMTimelineItemBinding::Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : m_TransMgr(inMgr)
+ , m_DataHandle(inDataHandle)
+ , m_Parent(nullptr)
+ , m_TimelineTimebar(nullptr)
+
+{
+ m_StudioSystem = m_TransMgr->GetStudioSystem();
+}
+
+Qt3DSDMTimelineItemBinding::Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr)
+ : m_TransMgr(inMgr)
+ , m_DataHandle(0)
+ , m_Parent(nullptr)
+ , m_TimelineTimebar(nullptr)
+{
+ m_StudioSystem = m_TransMgr->GetStudioSystem();
+}
+
+Qt3DSDMTimelineItemBinding::~Qt3DSDMTimelineItemBinding()
+{
+ RemoveAllPropertyBindings();
+ delete m_TimelineTimebar;
+}
+
+// helpers
+bool Qt3DSDMTimelineItemBinding::GetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty) const
+{
+ qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem();
+ SValue theValue;
+ thePropertySystem->GetInstancePropertyValue(m_DataHandle, inProperty, theValue);
+ return qt3dsdm::get<bool>(theValue);
+}
+
+void Qt3DSDMTimelineItemBinding::SetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty,
+ bool inValue, const QString &inNiceText) const
+{
+ CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc());
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*theDoc, inNiceText)
+ ->SetInstancePropertyValue(m_DataHandle, inProperty, inValue);
+}
+
+void Qt3DSDMTimelineItemBinding::SetInstanceHandle(qt3dsdm::Qt3DSDMInstanceHandle inDataHandle)
+{
+ m_DataHandle = inDataHandle;
+}
+
+EStudioObjectType Qt3DSDMTimelineItemBinding::GetObjectType() const
+{
+ return m_StudioSystem->GetClientDataModelBridge()->GetObjectType(m_DataHandle);
+}
+
+bool Qt3DSDMTimelineItemBinding::IsMaster() const
+{
+ CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc());
+ Q3DStudio::IDocumentReader &theReader(theDoc->GetDocumentReader());
+ if (GetObjectType() == OBJTYPE_IMAGE) {
+ CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge();
+ Qt3DSDMInstanceHandle theParent;
+ Qt3DSDMPropertyHandle theProperty;
+ bool isPropertyLinked;
+
+ theBridge->GetMaterialFromImageInstance(GetInstance(), theParent, theProperty);
+ isPropertyLinked = theReader.IsPropertyLinked(theParent, theProperty);
+
+ // Also check light probe
+ if (!isPropertyLinked) {
+ theBridge->GetLayerFromImageProbeInstance(GetInstance(), theParent, theProperty);
+ isPropertyLinked = theReader.IsPropertyLinked(theParent, theProperty);
+ }
+
+ return isPropertyLinked;
+ }
+ Qt3DSDMInstanceHandle theQueryHandle(m_DataHandle);
+ if (GetObjectType() == OBJTYPE_PATHANCHORPOINT)
+ theQueryHandle = theReader.GetParent(m_DataHandle);
+
+ // logic: you can't unlink name, so if name is linked then, this is master.
+ Qt3DSDMPropertyHandle theNamePropHandle =
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName(theQueryHandle,
+ L"name");
+ return theReader.IsPropertyLinked(theQueryHandle, theNamePropHandle);
+}
+
+bool Qt3DSDMTimelineItemBinding::IsShy() const
+{
+ return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Shy);
+}
+void Qt3DSDMTimelineItemBinding::SetShy(bool inShy)
+{
+ SetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Shy, inShy,
+ QObject::tr("Shy Toggle"));
+}
+bool Qt3DSDMTimelineItemBinding::IsLocked() const
+{
+ return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Locked);
+}
+
+bool Qt3DSDMTimelineItemBinding::IsVisibilityControlled() const
+{
+ if (!m_StudioSystem->IsInstance(m_DataHandle))
+ return false;
+
+ Qt3DSDMPropertyHandle theNamePropHandle =
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName(
+ m_DataHandle, L"controlledproperty");
+
+ if (!theNamePropHandle)
+ return false;
+ SValue theNameValue;
+ m_StudioSystem->GetPropertySystem()->GetInstancePropertyValue(m_DataHandle, theNamePropHandle,
+ theNameValue);
+ TDataStrPtr theName = qt3dsdm::get<TDataStrPtr>(theNameValue);
+
+ return (wcsstr(theName->GetData(), L"eyeball"));
+}
+
+void ToggleChildrenLock(Q3DStudio::ScopedDocumentEditor &scopedDocEditor,
+ Qt3DSDMTimelineItemBinding *inTimelineItemBinding,
+ SDataModelSceneAsset inSceneAsset, bool inLocked)
+{
+ scopedDocEditor->SetInstancePropertyValue(inTimelineItemBinding->GetInstanceHandle(),
+ inSceneAsset.m_Locked, inLocked);
+ const QList<ITimelineItemBinding *> children = inTimelineItemBinding->GetChildren();
+ for (auto child : children) {
+ ToggleChildrenLock(scopedDocEditor, static_cast<Qt3DSDMTimelineItemBinding *>(child),
+ inSceneAsset, inLocked);
+ }
+}
+
+void Qt3DSDMTimelineItemBinding::SetLocked(bool inLocked)
+{
+ CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc());
+ Q3DStudio::ScopedDocumentEditor scopedDocEditor(*theDoc, QObject::tr("SetLock"), __FILE__,
+ __LINE__);
+
+ SDataModelSceneAsset sceneAsset = m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset();
+ ToggleChildrenLock(scopedDocEditor, this, sceneAsset, inLocked);
+
+ if (inLocked)
+ g_StudioApp.GetCore()->GetDoc()->NotifySelectionChanged();
+}
+
+bool Qt3DSDMTimelineItemBinding::IsVisible() const
+{
+ return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball);
+}
+
+void Qt3DSDMTimelineItemBinding::SetVisible(bool inVisible)
+{
+ SetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball,
+ inVisible, QObject::tr("Visibility Toggle"));
+}
+
+bool Qt3DSDMTimelineItemBinding::HasAction(bool inMaster)
+{
+ TActionHandleList theActions;
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+
+ Qt3DSDMSlideHandle theSlide = theDoc->GetActiveSlide();
+ qt3dsdm::ISlideCore &theSlideCore(*m_StudioSystem->GetSlideCore());
+ if (theSlideCore.IsSlide(theSlide)) {
+ if (inMaster) {
+ theSlide =
+ m_StudioSystem->GetSlideSystem()->GetMasterSlide(theSlide); // use the master slide
+ }
+
+ m_StudioSystem->GetActionCore()->GetActions(theSlide, m_DataHandle, theActions);
+ }
+ return theActions.size() > 0;
+}
+
+bool Qt3DSDMTimelineItemBinding::ChildrenHasAction(bool inMaster)
+{
+ // Get all the instances in this slidegraph
+ // check whehter it's an action instance and is in the slide of interst
+ // check also it's owner is a descendent of the viewed instances
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ IActionCore *theActionCore(m_StudioSystem->GetActionCore());
+ CClientDataModelBridge *theBridge(m_StudioSystem->GetClientDataModelBridge());
+
+ Qt3DSDMSlideHandle theSlide = theDoc->GetActiveSlide();
+ qt3dsdm::ISlideCore &theSlideCore(*m_StudioSystem->GetSlideCore());
+ if (theSlideCore.IsSlide(theSlide)) {
+ if (inMaster) {
+ theSlide =
+ m_StudioSystem->GetSlideSystem()->GetMasterSlide(theSlide); // use the master slide
+ }
+
+ TSlideInstancePairList theGraphInstances;
+ m_StudioSystem->GetSlideSystem()->GetAssociatedInstances(theSlide, theGraphInstances);
+
+ qt3dsdm::Qt3DSDMInstanceHandle theObservedInstance = GetInstance();
+ if (theObservedInstance.Valid()) {
+ for (TSlideInstancePairList::const_iterator theIter = theGraphInstances.begin();
+ theIter != theGraphInstances.end(); ++theIter) {
+ if (theIter->first == theSlide && theBridge->IsActionInstance(theIter->second)) {
+ Qt3DSDMActionHandle theAction =
+ theActionCore->GetActionByInstance(theIter->second);
+ SActionInfo theActionInfo = theActionCore->GetActionInfo(theAction);
+ Qt3DSDMInstanceHandle theAcionOwner = theActionInfo.m_Owner;
+ if (theAcionOwner.Valid()
+ && IsAscendant(theAcionOwner, theObservedInstance, theDoc->GetAssetGraph()))
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Qt3DSDMTimelineItemBinding::ComponentHasAction(bool inMaster)
+{
+ // Get all the instances in this component slidegraph
+ // check whether the instance is an action instance
+ // if inMaster is true, we only interest with those that are in the master slide, else we want
+ // those that are not in the master slide
+ CClientDataModelBridge *theBridge(m_StudioSystem->GetClientDataModelBridge());
+ if (!theBridge->IsComponentInstance(m_DataHandle))
+ return false;
+
+ Q3DStudio::CId theAssetId = theBridge->GetGUID(m_DataHandle);
+ Qt3DSDMSlideHandle theMasterSlide =
+ m_StudioSystem->GetSlideSystem()->GetMasterSlideByComponentGuid(GuidtoSLong4(theAssetId));
+
+ TSlideInstancePairList theGraphInstances;
+ m_StudioSystem->GetSlideSystem()->GetAssociatedInstances(theMasterSlide, theGraphInstances);
+
+ for (TSlideInstancePairList::const_iterator theIter = theGraphInstances.begin();
+ theIter != theGraphInstances.end(); ++theIter) {
+ if (((inMaster && theIter->first == theMasterSlide)
+ || (!inMaster && theIter->first != theMasterSlide))
+ && theBridge->IsActionInstance(theIter->second)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Qt3DSDMTimelineItemBinding::hasSubpresentation() const
+{
+ CClientDataModelBridge *bridge(m_StudioSystem->GetClientDataModelBridge());
+ IPropertySystem *propSystem = m_StudioSystem->GetPropertySystem();
+ EStudioObjectType objType = GetObjectType();
+
+ if (objType == OBJTYPE_LAYER) {
+ SValue sourcePathValue;
+ propSystem->GetInstancePropertyValue(m_DataHandle, bridge->GetSourcePathProperty(),
+ sourcePathValue);
+ return get<TDataStrPtr>(sourcePathValue)->GetLength() > 0;
+ } else if (objType == OBJTYPE_IMAGE) {
+ SValue subPresValue;
+ propSystem->GetInstancePropertyValue(m_DataHandle,
+ bridge->GetSceneImage().m_SubPresentation,
+ subPresValue);
+ return get<TDataStrPtr>(subPresValue)->GetLength() > 0;
+ }
+
+ return false;
+}
+
+ITimelineTimebar *Qt3DSDMTimelineItemBinding::GetTimebar()
+{
+ if (!m_TimelineTimebar)
+ m_TimelineTimebar = CreateTimelineTimebar();
+ return m_TimelineTimebar;
+}
+
+Q3DStudio::CString Qt3DSDMTimelineItemBinding::GetName() const
+{
+ if (m_StudioSystem->IsInstance(m_DataHandle) == false)
+ return L"";
+ Qt3DSDMPropertyHandle theNamePropHandle =
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName(m_DataHandle,
+ L"name");
+ SValue theNameValue;
+ m_StudioSystem->GetPropertySystem()->GetInstancePropertyValue(m_DataHandle, theNamePropHandle,
+ theNameValue);
+ TDataStrPtr theName = qt3dsdm::get<TDataStrPtr>(theNameValue);
+
+ return (theName) ? Q3DStudio::CString(theName->GetData()) : "";
+}
+
+void Qt3DSDMTimelineItemBinding::SetName(const Q3DStudio::CString &inName)
+{
+ // Ignore if setting the name to what it currently is to avoid duplicate undo points
+ if (inName == GetName())
+ return;
+
+ // Display warning dialog if user tried to enter an empty string
+ if (inName.IsEmpty()) {
+ QString theTitle = QObject::tr("Rename Object Error");
+ QString theString = QObject::tr("Object name cannot be an empty string.");
+ g_StudioApp.GetDialogs()->DisplayMessageBox(theTitle, theString,
+ Qt3DSMessageBox::ICON_ERROR, false);
+
+ return;
+ }
+
+ CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge();
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+
+ // Display warning if the name and path are the same as the material container
+ if (theBridge->GetParentInstance(m_DataHandle) == doc->GetSceneInstance()
+ && inName.toQString() == theBridge->getMaterialContainerName()) {
+ QString theTitle = QObject::tr("Rename Object Error");
+ QString theString = theBridge->getMaterialContainerName()
+ + QObject::tr(" is a reserved name.");
+ g_StudioApp.GetDialogs()->DisplayMessageBox(theTitle, theString,
+ Qt3DSMessageBox::ICON_ERROR, false);
+ // The timeline still shows the new name so refresh the name property
+ m_StudioSystem->GetFullSystemSignalSender()->SendInstancePropertyValue(
+ m_DataHandle, theBridge->GetNameProperty());
+ return;
+ }
+
+ // Display warning if we had to modify the user-given name to make it unique
+ if (!theBridge->CheckNameUnique(theBridge->GetParentInstance(m_DataHandle),
+ m_DataHandle, inName)) {
+ // Find unique name based on the input string
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(
+ *m_TransMgr->GetDoc(), QObject::tr("Set Name"))->SetName(m_DataHandle, inName, true);
+
+ g_StudioApp.GetDialogs()->DisplayObjectRenamed(
+ inName.toQString(), theBridge->GetName(m_DataHandle).toQString());
+ return;
+ }
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(
+ *m_TransMgr->GetDoc(), QObject::tr("Set Name"))->SetName(m_DataHandle, inName, true);
+}
+
+ITimelineItem *Qt3DSDMTimelineItemBinding::GetTimelineItem()
+{
+ return this;
+}
+
+RowTree *Qt3DSDMTimelineItemBinding::getRowTree() const
+{
+ return m_rowTree;
+}
+
+void Qt3DSDMTimelineItemBinding::setRowTree(RowTree *row)
+{
+ m_rowTree = row;
+}
+
+void Qt3DSDMTimelineItemBinding::SetSelected(bool inMultiSelect)
+{
+ if (!inMultiSelect)
+ g_StudioApp.GetCore()->GetDoc()->SelectDataModelObject(m_DataHandle);
+ else
+ g_StudioApp.GetCore()->GetDoc()->ToggleDataModelObjectToSelection(m_DataHandle);
+}
+
+void Qt3DSDMTimelineItemBinding::OnCollapsed()
+{
+ // Preserves legacy behavior where collapsing a tree will select that root, if any of its
+ // descendant was selected
+ // TODO: This won't work for Image (because Image is Material's property, not child)
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ CDoc *theDoc = m_TransMgr->GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = theDoc->GetSelectedInstance();
+ if (theSelectedInstance.Valid()
+ && IsAscendant(theSelectedInstance, theInstance, theDoc->GetAssetGraph()))
+ SetSelected(false);
+ }
+}
+
+bool Qt3DSDMTimelineItemBinding::OpenAssociatedEditor()
+{
+ return false; // nothing to do by default
+}
+
+inline qt3dsdm::Qt3DSDMInstanceHandle Qt3DSDMTimelineItemBinding::GetInstance() const
+{
+ return m_DataHandle;
+}
+
+void Qt3DSDMTimelineItemBinding::SetDropTarget(CDropTarget *inTarget)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ inTarget->SetInstance(theInstance);
+}
+
+long Qt3DSDMTimelineItemBinding::GetChildrenCount()
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ Q3DStudio::CGraphIterator theChildren;
+ Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(),
+ theChildren, theActiveSlide);
+ return (long)theChildren.GetCount();
+ }
+ return 0;
+}
+
+ITimelineItemBinding *Qt3DSDMTimelineItemBinding::GetChild(long inIndex)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ Q3DStudio::CGraphIterator theChildren;
+ Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(),
+ theChildren, theActiveSlide);
+ theChildren += inIndex;
+
+ qt3dsdm::Qt3DSDMInstanceHandle theChildInstance = theChildren.GetCurrent();
+ if (theChildInstance.Valid())
+ return m_TransMgr->GetOrCreate(theChildInstance);
+ }
+ return nullptr;
+}
+
+QList<ITimelineItemBinding *> Qt3DSDMTimelineItemBinding::GetChildren()
+{
+ QList<ITimelineItemBinding *> retlist;
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ Q3DStudio::CGraphIterator theChildren;
+ Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(),
+ theChildren, theActiveSlide);
+ int childCount = int(theChildren.GetCount());
+ retlist.reserve(childCount);
+
+ for (int i = 0; i < childCount; ++i) {
+ qt3dsdm::Qt3DSDMInstanceHandle theChildInstance = theChildren.GetCurrent();
+ if (theChildInstance.Valid())
+ retlist.append(m_TransMgr->GetOrCreate(theChildInstance));
+ ++theChildren;
+ }
+ }
+
+ return retlist;
+}
+
+ITimelineItemBinding *Qt3DSDMTimelineItemBinding::GetParent()
+{
+ return m_Parent;
+}
+void Qt3DSDMTimelineItemBinding::SetParent(ITimelineItemBinding *parent)
+{
+ if (parent != m_Parent) {
+ ASSERT(parent == nullptr || m_Parent == nullptr);
+ m_Parent = parent;
+ }
+}
+
+long Qt3DSDMTimelineItemBinding::GetPropertyCount()
+{
+ long theCount = 0;
+ if (m_StudioSystem->IsInstance(m_DataHandle)) {
+ TPropertyHandleList theProperties;
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle,
+ theProperties);
+ for (size_t thePropertyIndex = 0; thePropertyIndex < theProperties.size();
+ ++thePropertyIndex) {
+ if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated(
+ m_DataHandle, theProperties[thePropertyIndex])) {
+ ++theCount;
+ }
+ }
+ }
+ return theCount;
+}
+
+ITimelineItemProperty *Qt3DSDMTimelineItemBinding::GetProperty(long inIndex)
+{
+ TPropertyHandleList theProperties;
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle,
+ theProperties);
+ long theIndex = -1;
+ size_t thePropertyIndex = 0;
+ for (; thePropertyIndex < theProperties.size(); ++thePropertyIndex) {
+ if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated(
+ m_DataHandle, theProperties[thePropertyIndex])) {
+ ++theIndex;
+ if (theIndex == inIndex)
+ break;
+ }
+ }
+ ASSERT(thePropertyIndex < theProperties.size()); // no reason why this would be out of range!!
+ return GetOrCreatePropertyBinding(theProperties[thePropertyIndex]);
+}
+
+bool Qt3DSDMTimelineItemBinding::ShowToggleControls() const
+{
+ return true;
+}
+
+bool Qt3DSDMTimelineItemBinding::IsLockedEnabled() const
+{
+ return IsLocked();
+}
+
+bool Qt3DSDMTimelineItemBinding::IsVisibleEnabled() const
+{
+ // You can only toggle visible if you aren't on the master slide.
+ return m_StudioSystem->GetSlideSystem()->GetSlideIndex(m_TransMgr->GetDoc()->GetActiveSlide())
+ != 0;
+}
+
+bool Qt3DSDMTimelineItemBinding::IsValidTransaction(EUserTransaction inTransaction)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ const auto bridge = m_StudioSystem->GetClientDataModelBridge();
+ switch (inTransaction) {
+ case EUserTransaction_Rename:
+ return (GetObjectType() != OBJTYPE_SCENE && GetObjectType() != OBJTYPE_IMAGE
+ && (bridge->GetObjectType(theInstance) != OBJTYPE_REFERENCEDMATERIAL
+ || bridge->GetSourcePath(theInstance).isEmpty()));
+
+ case EUserTransaction_Duplicate:
+ if (theInstance.Valid())
+ return m_StudioSystem->GetClientDataModelBridge()->IsDuplicateable(theInstance);
+ break;
+
+ case EUserTransaction_Cut:
+ return g_StudioApp.CanCut();
+
+ case EUserTransaction_Copy:
+ return g_StudioApp.CanCopy();
+
+ case EUserTransaction_Paste:
+ return m_TransMgr->GetDoc()->canPasteObjects();
+
+ case EUserTransaction_Delete:
+ if (theInstance.Valid())
+ return m_StudioSystem->GetClientDataModelBridge()->CanDelete(theInstance);
+ break;
+
+ case EUserTransaction_MakeComponent: {
+ bool theCanMakeFlag = false;
+ if (theInstance.Valid()) {
+ CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge();
+ EStudioObjectType theObjectType = theBridge->GetObjectType(theInstance);
+
+ if (!IsLocked()) {
+ // Any assets that are attached to the Scene directly must not be wrapped in a
+ // component.
+ // This may include behavior assets which may be directly attached to the Scene.
+ // This is because by principal, components cannot exist on the Scene directly.
+ qt3dsdm::Qt3DSDMInstanceHandle theParentInstance =
+ theBridge->GetParentInstance(theInstance);
+ if (theObjectType != OBJTYPE_LAYER && theObjectType != OBJTYPE_SCENE
+ && theObjectType != OBJTYPE_MATERIAL && theObjectType != OBJTYPE_IMAGE
+ && theObjectType != OBJTYPE_EFFECT && theObjectType != OBJTYPE_COMPONENT
+ && (theParentInstance.Valid()
+ && theBridge->GetObjectType(theParentInstance)
+ != OBJTYPE_SCENE)) // This checks if the object is
+ // AttachedToSceneDirectly
+ {
+ theCanMakeFlag = true;
+ }
+ }
+ }
+ return theCanMakeFlag;
+ }
+
+ case EUserTransaction_EditComponent:
+ return (GetObjectType() == OBJTYPE_COMPONENT);
+
+ case EUserTransaction_MakeAnimatable:
+ if (theInstance.Valid()) {
+ CClientDataModelBridge *bridge = m_StudioSystem->GetClientDataModelBridge();
+ EStudioObjectType type = bridge->GetObjectType(theInstance);
+ return !IsLocked() && type == OBJTYPE_REFERENCEDMATERIAL;
+ }
+ return false;
+
+ case EUserTransaction_Group:
+ return g_StudioApp.canGroupSelectedObjects();
+
+ case EUserTransaction_Ungroup:
+ return g_StudioApp.canUngroupSelectedObjects();
+
+ case EUserTransaction_AddLayer:
+ return (GetObjectType() == OBJTYPE_SCENE);
+
+ default: // not handled
+ break;
+ }
+
+ return false;
+}
+
+using namespace Q3DStudio;
+
+inline void DoCut(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ inDoc.DeselectAllKeyframes();
+ inDoc.CutObject(inInstances);
+}
+
+inline void DoDelete(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ inDoc.DeselectAllKeyframes();
+ inDoc.DeleteObject(inInstances);
+}
+
+inline void DoMakeComponent(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ SCOPED_DOCUMENT_EDITOR(inDoc, QObject::tr("Make Component"))->MakeComponent(inInstances);
+}
+
+inline void doMakeAnimatable(CDoc &doc, const qt3dsdm::TInstanceHandleList &instances)
+{
+ SCOPED_DOCUMENT_EDITOR(doc, QObject::tr("Make Animatable"))->makeAnimatable(instances);
+}
+
+inline void DoGroupObjects(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ g_StudioApp.groupSelectedObjects();
+}
+
+inline void DoUngroupObjects(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ g_StudioApp.ungroupSelectedObjects();
+}
+
+inline void doAddLayer(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances)
+{
+ qt3dsdm::Qt3DSDMSlideHandle slide = inDoc.GetActiveSlide();
+ qt3dsdm::Qt3DSDMInstanceHandle parent = inDoc.GetActiveLayer();
+
+ SCOPED_DOCUMENT_EDITOR(inDoc, QObject::tr("Add Layer"))
+ ->CreateSceneGraphInstance(qt3dsdm::ComposerObjectTypes::Layer, parent, slide,
+ DocumentEditorInsertType::PreviousSibling,
+ CPt(), PRIMITIVETYPE_UNKNOWN, -1);
+}
+
+void Qt3DSDMTimelineItemBinding::PerformTransaction(EUserTransaction inTransaction)
+{
+ CDoc *theDoc = m_TransMgr->GetDoc();
+ qt3dsdm::TInstanceHandleList theInstances = theDoc->GetSelectedValue().GetSelectedInstances();
+ if (theInstances.empty())
+ return;
+ CDispatch &theDispatch(*theDoc->GetCore()->GetDispatch());
+
+ // Transactions that could result in *this* object being deleted need to be executed
+ // via postmessage, not in this context because it could result in the currently
+ // active timeline row being deleted while in its own mouse handler.
+ switch (inTransaction) {
+ case EUserTransaction_Duplicate: {
+ theDoc->DeselectAllKeyframes();
+ SCOPED_DOCUMENT_EDITOR(*theDoc,
+ QObject::tr("Duplicate Object"))->DuplicateInstances(theInstances);
+ } break;
+ case EUserTransaction_Cut: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(DoCut, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_Copy: {
+ theDoc->DeselectAllKeyframes();
+ theDoc->CopyObject(theInstances);
+ } break;
+ case EUserTransaction_Paste: {
+ theDoc->DeselectAllKeyframes();
+ theDoc->PasteObject(theDoc->getPasteTarget(GetInstance()));
+ } break;
+ case EUserTransaction_Delete: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(DoDelete, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_MakeComponent: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(DoMakeComponent, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_MakeAnimatable: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(doMakeAnimatable, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_Group: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(DoGroupObjects, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_Ungroup: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(DoUngroupObjects, std::ref(*theDoc), theInstances));
+ } break;
+ case EUserTransaction_AddLayer: {
+ theDispatch.FireOnAsynchronousCommand(
+ std::bind(doAddLayer, std::ref(*theDoc), theInstances));
+ } break;
+ default: // not handled
+ break;
+ }
+}
+
+Q3DStudio::CString Qt3DSDMTimelineItemBinding::GetObjectPath()
+{
+ CDoc *theDoc = m_TransMgr->GetDoc();
+ // Because we are getting absolute path, the base id doesn't matter.
+ return CRelativePathTools::BuildAbsoluteReferenceString(m_DataHandle, theDoc);
+}
+
+int Qt3DSDMTimelineItemBinding::getAnimatedPropertyIndex(int propertyHandle) const
+{
+ TPropertyHandleList theProperties;
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle,
+ theProperties);
+ int index = -1;
+ for (size_t i = 0; i < theProperties.size(); ++i) {
+ if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated(
+ m_DataHandle, theProperties[i])) {
+ index++;
+ }
+ if (theProperties[i].GetHandleValue() == propertyHandle)
+ return index;
+ }
+
+ return -1;
+}
+
+void Qt3DSDMTimelineItemBinding::getTimeContextIndices(const QSet<int> &children,
+ QMap<int, int> &indexMap)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle instance = GetInstance();
+ if (instance.Valid()) {
+ Q3DStudio::CGraphIterator graphChildren;
+ Qt3DSDMSlideHandle activeSlide = m_TransMgr->GetDoc()->GetActiveSlide();
+ GetAssetChildrenInTimeParent(instance, m_TransMgr->GetDoc(), AmITimeParent(),
+ graphChildren, activeSlide);
+ const size_t count = graphChildren.GetCount();
+ for (size_t current = 0; current < count; ++current) {
+ auto handle = graphChildren.GetResult(current);
+ if (children.contains(handle))
+ indexMap.insert(int(current), int(handle));
+ }
+ }
+}
+
+void Qt3DSDMTimelineItemBinding::InsertKeyframe()
+{
+ if (m_PropertyBindingMap.empty())
+ return;
+
+ TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin();
+ ScopedDocumentEditor editor(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Insert Keyframe"),
+ __FILE__, __LINE__);
+ for (; theIter != m_PropertyBindingMap.end(); ++theIter)
+ editor->KeyframeProperty(m_DataHandle, theIter->first, false);
+}
+
+void Qt3DSDMTimelineItemBinding::DeleteAllChannelKeyframes()
+{
+ if (m_PropertyBindingMap.empty())
+ return;
+
+ CDoc *theDoc = m_TransMgr->GetDoc();
+ Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Delete Channel Keyframes"),
+ __FILE__, __LINE__);
+ for (auto &kv : m_PropertyBindingMap)
+ kv.second->DeleteAllKeys();
+}
+
+IKeyframe *Qt3DSDMTimelineItemBinding::GetKeyframeByTime(long inTime) const
+{
+ TAssetKeyframeList::const_iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ if ((*theIter).GetTime() == inTime)
+ return const_cast<Qt3DSDMAssetTimelineKeyframe *>(&(*theIter));
+ }
+ return nullptr;
+}
+
+Qt3DSDMInstanceHandle Qt3DSDMTimelineItemBinding::GetInstanceHandle() const
+{
+ return m_DataHandle;
+}
+
+long Qt3DSDMTimelineItemBinding::GetFlavor() const
+{
+ return QT3DS_FLAVOR_ASSET_TL;
+}
+
+ITimelineTimebar *Qt3DSDMTimelineItemBinding::CreateTimelineTimebar()
+{
+ return new Qt3DSDMTimelineTimebar(m_TransMgr, m_DataHandle);
+}
+
+ITimelineItemProperty *
+Qt3DSDMTimelineItemBinding::GetPropertyBinding(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle);
+ // check if it already exists
+ if (theIter != m_PropertyBindingMap.end())
+ return theIter->second;
+ return nullptr;
+}
+
+bool Qt3DSDMTimelineItemBinding::isRootComponent() const
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ return bridge->IsActiveComponent(m_DataHandle);
+}
+
+bool Qt3DSDMTimelineItemBinding::isDefaultMaterial() const
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ return bridge->isDefaultMaterial(m_DataHandle);
+}
+
+ITimelineItemProperty *
+Qt3DSDMTimelineItemBinding::GetOrCreatePropertyBinding(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ ITimelineItemProperty *theProperty = GetPropertyBinding(inPropertyHandle);
+ // check if it already exists
+ if (theProperty)
+ return theProperty;
+
+ // Create
+ Qt3DSDMTimelineItemProperty *theTimelineProperty =
+ new Qt3DSDMTimelineItemProperty(m_TransMgr, inPropertyHandle, m_DataHandle);
+ m_PropertyBindingMap.insert(std::make_pair(inPropertyHandle, theTimelineProperty));
+
+ return theTimelineProperty;
+}
+
+//=============================================================================
+/**
+ * Add a new property row for this property.
+ * @param inAppend true to skip the check to find where to insert. ( true if this is a
+ * loading/initializing step, where the call is already done in order )
+ */
+void Qt3DSDMTimelineItemBinding::AddPropertyRow(Qt3DSDMPropertyHandle inPropertyHandle,
+ bool inAppend /*= false */)
+{
+ ITimelineItemProperty *theTimelineProperty = GetPropertyBinding(inPropertyHandle);
+ if (theTimelineProperty) // if created, bail
+ return;
+
+ if (!theTimelineProperty)
+ theTimelineProperty = GetOrCreatePropertyBinding(inPropertyHandle);
+
+ // Find the row to insert this new property, if any, this preserves the order the property rows
+ // is displayed in the timeline.
+ ITimelineItemProperty *theNextProperty = nullptr;
+ if (!inAppend) {
+ TPropertyHandleList theProperties;
+ m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle,
+ theProperties);
+ size_t thePropertyIndex = 0;
+ size_t thePropertyCount = theProperties.size();
+ for (; thePropertyIndex < thePropertyCount; ++thePropertyIndex) {
+ if (theProperties[thePropertyIndex] == inPropertyHandle) {
+ ++thePropertyIndex;
+ break;
+ }
+ }
+ // Not all properties are displayed, so another loop to search for the first one that maps
+ // to a existing propertyrow
+ for (; thePropertyIndex < thePropertyCount; ++thePropertyIndex) {
+ TPropertyBindingMap::iterator theNextPropIter =
+ m_PropertyBindingMap.find(theProperties[thePropertyIndex]);
+ if (theNextPropIter != m_PropertyBindingMap.end()) {
+ theNextProperty = theNextPropIter->second;
+ break;
+ }
+ }
+ }
+
+ // Update keyframes
+ AddKeyframes(theTimelineProperty);
+}
+
+void Qt3DSDMTimelineItemBinding::RemovePropertyRow(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle);
+ if (theIter != m_PropertyBindingMap.end()) {
+ ITimelineItemProperty *thePropertyBinding = theIter->second;
+
+ DeleteAssetKeyframesWhereApplicable(thePropertyBinding);
+ m_PropertyBindingMap.erase(theIter);
+ }
+}
+
+// called when a keyframe is inserted, deleted or updated in the data model
+void Qt3DSDMTimelineItemBinding::RefreshPropertyKeyframe(
+ qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ ETimelineKeyframeTransaction inTransaction)
+{
+ TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle);
+ if (theIter != m_PropertyBindingMap.end()) {
+ Qt3DSDMTimelineItemProperty *theProperty = theIter->second;
+ if (theProperty) {
+ if (theProperty->RefreshKeyframe(inKeyframe, inTransaction)) {
+ // Update asset keyframes
+ UpdateKeyframe(theProperty->GetKeyframeByHandle(inKeyframe), inTransaction);
+ }
+ }
+ }
+}
+
+void Qt3DSDMTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+}
+
+void Qt3DSDMTimelineItemBinding::OnPropertyLinked(Qt3DSDMPropertyHandle inPropertyHandle)
+{
+ if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated(m_DataHandle, inPropertyHandle)) {
+ // Refresh property row by delete and recreate
+ RemovePropertyRow(inPropertyHandle);
+ AddPropertyRow(inPropertyHandle);
+ }
+}
+
+bool Qt3DSDMTimelineItemBinding::HasDynamicKeyframes(long inTime)
+{
+ if (inTime == -1) {
+ if (GetPropertyCount() == 0)
+ return false;
+
+ for (long i = 0; i < GetPropertyCount(); ++i) {
+ ITimelineItemProperty *theTimelineItemProperty = GetProperty(i);
+ if (!theTimelineItemProperty->IsDynamicAnimation())
+ return false;
+ }
+ return true;
+ } else {
+ TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin();
+ for (; theIter != m_PropertyBindingMap.end(); ++theIter) {
+ IKeyframe *theKeyframe = theIter->second->GetKeyframeByTime(inTime);
+ if (theKeyframe && theKeyframe->IsDynamic())
+ return true;
+ }
+ }
+ return false;
+}
+
+void Qt3DSDMTimelineItemBinding::SetDynamicKeyframes(long inTime, bool inDynamic)
+{
+ TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin();
+ for (; theIter != m_PropertyBindingMap.end(); ++theIter) {
+ IKeyframe *theKeyframe = theIter->second->GetKeyframeByTime(inTime);
+ if (theKeyframe)
+ theKeyframe->SetDynamic(inDynamic); // TODO: we want this in 1 batch command
+ }
+}
+
+Q3DStudio::CId Qt3DSDMTimelineItemBinding::GetGuid() const
+{
+ CClientDataModelBridge *theClientBridge = m_StudioSystem->GetClientDataModelBridge();
+ qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem();
+ SValue theValue;
+ if (thePropertySystem->GetInstancePropertyValue(m_DataHandle, theClientBridge->GetIdProperty(),
+ theValue)) {
+ SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue);
+ return Q3DStudio::CId(theLong4.m_Longs[0], theLong4.m_Longs[1], theLong4.m_Longs[2],
+ theLong4.m_Longs[3]);
+ }
+ return Q3DStudio::CId();
+}
+
+// Delete asset keyframes at time t if no property keyframes exist at time t
+//@param inSkipPropertyBinding property that to skip, e.g. in cases where property is deleted
+//@return true if there are asset keyframes deleted.
+bool Qt3DSDMTimelineItemBinding::DeleteAssetKeyframesWhereApplicable(
+ ITimelineItemProperty *inSkipPropertyBinding /*= nullptr */)
+{
+ // iterate through m_Keyframes because we cannot obtain time information from the Animation
+ // keyframes anymore, since they are deleted.
+ std::vector<long> theDeleteIndicesList;
+ for (size_t theIndex = 0; theIndex < m_Keyframes.size(); ++theIndex) {
+ TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.begin();
+ for (; theIter != m_PropertyBindingMap.end(); ++theIter) {
+ if ((!inSkipPropertyBinding || theIter->second != inSkipPropertyBinding)
+ && theIter->second->GetKeyframeByTime(m_Keyframes[theIndex].GetTime())) {
+ // done!
+ break;
+ }
+ }
+ if (theIter == m_PropertyBindingMap.end())
+ theDeleteIndicesList.push_back((long)theIndex);
+ }
+ // start with the last item, so that the indices remain valid.
+ for (long i = (long)theDeleteIndicesList.size() - 1; i >= 0; --i) {
+ TAssetKeyframeList::iterator theKeyIter = m_Keyframes.begin();
+ std::advance(theKeyIter, theDeleteIndicesList[i]);
+ m_Keyframes.erase(theKeyIter);
+ }
+
+ return !theDeleteIndicesList.empty();
+}
+
+void Qt3DSDMTimelineItemBinding::RemoveAllPropertyBindings()
+{
+ TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.begin();
+ for (; theIter != m_PropertyBindingMap.end(); ++theIter)
+ delete theIter->second;
+ m_PropertyBindingMap.clear();
+}
+
+void Qt3DSDMTimelineItemBinding::AddKeyframes(ITimelineItemProperty *inPropertyBinding)
+{
+ for (long i = 0; i < inPropertyBinding->GetKeyframeCount(); ++i)
+ UpdateKeyframe(inPropertyBinding->GetKeyframeByIndex(i), ETimelineKeyframeTransaction_Add);
+}
+
+// Update the asset keyframes based on the properties' keyframes.
+void Qt3DSDMTimelineItemBinding::UpdateKeyframe(IKeyframe *inKeyframe,
+ ETimelineKeyframeTransaction inTransaction)
+{
+ bool theDoAddFlag = (inTransaction == ETimelineKeyframeTransaction_Add);
+ bool theDoDeleteFlag = (inTransaction == ETimelineKeyframeTransaction_Delete);
+
+ // For update, if there isn't already a asset keyframe at the associated time, create one
+ if (inTransaction == ETimelineKeyframeTransaction_Update) {
+ theDoAddFlag = inKeyframe && !GetKeyframeByTime(inKeyframe->GetTime());
+ theDoDeleteFlag = true; // plus, since we don't keep track of indiviual property keyframes
+ // here, iterate and make sure list is correct.
+ }
+
+ if (theDoDeleteFlag)
+ DeleteAssetKeyframesWhereApplicable();
+
+ // Add when a new keyframe is added or MAYBE when a keyframe is moved
+ if (theDoAddFlag && inKeyframe) {
+ long theKeyframeTime = inKeyframe->GetTime();
+ if (theKeyframeTime >= 0) {
+ bool theAppend = true;
+ // insert this in the order that it should be. and we trust the
+ TAssetKeyframeList::iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ long theTime = (*theIter).GetTime();
+ if (theTime == theKeyframeTime) {
+ theAppend = false;
+ break; // already exists, we are done. Because we only need 1 to represent ALL
+ // properties
+ }
+ }
+ if (theAppend)
+ m_Keyframes.push_back(Qt3DSDMAssetTimelineKeyframe(this, theKeyframeTime));
+ }
+ }
+}
+
+void Qt3DSDMTimelineItemBinding::OnAddChild(Qt3DSDMInstanceHandle inInstance)
+{
+ CDoc *theDoc = m_TransMgr->GetDoc();
+ CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge();
+ ISlideSystem *theSlideSystem = m_StudioSystem->GetSlideSystem();
+
+ qt3dsdm::Qt3DSDMSlideHandle theSlide = theSlideSystem->GetAssociatedSlide(inInstance);
+ if (theBridge->IsInActiveComponent(inInstance)
+ && (theSlideSystem->IsMasterSlide(theSlide) || theSlide == theDoc->GetActiveSlide())) {
+ // Only add if the asset is in the current active component, and it's a master asset or in
+ // the current slide
+ ITimelineItemBinding *theNextItem = nullptr;
+ qt3dsdm::Qt3DSDMInstanceHandle theParentInstance = GetInstance();
+ // Figure out where to insert this row, if applicable.
+ // CAsset has a list of children, and not necessarily all are active in this slide (e.g.
+ // non-master children)
+ Q3DStudio::TIdentifier theNextChild = 0;
+ if (theParentInstance.Valid()) {
+ // Get the next prioritized child in the same slide
+ Q3DStudio::CGraphIterator theChildren;
+ GetAssetChildrenInSlide(theDoc, theParentInstance, theDoc->GetActiveSlide(),
+ theChildren);
+ theNextChild = GetSibling(inInstance, true, theChildren);
+ }
+
+ if (theNextChild != 0)
+ theNextItem = m_TransMgr->GetOrCreate(theNextChild);
+ }
+}
+
+void Qt3DSDMTimelineItemBinding::OnDeleteChild(Qt3DSDMInstanceHandle inInstance)
+{
+}
+
+void Qt3DSDMTimelineItemBinding::UpdateActionStatus()
+{
+}
+
+//=============================================================================
+/**
+ * Open the associated item as though it was double-clicked in explorer
+ * Respective subclasses (for example Image and Behavior) can call this function
+ */
+bool Qt3DSDMTimelineItemBinding::OpenSourcePathFile()
+{
+ // Get source path property value
+ CClientDataModelBridge *theClientBridge = m_StudioSystem->GetClientDataModelBridge();
+ qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem();
+ SValue theValue;
+ if (thePropertySystem->GetInstancePropertyValue(
+ m_DataHandle, theClientBridge->GetSourcePathProperty(), theValue)) {
+ // Open the respective file
+ Q3DStudio::CFilePath theSourcePath(qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->GetData());
+ Qt3DSFile theFile(m_TransMgr->GetDoc()->GetResolvedPathToDoc(theSourcePath));
+ theFile.Execute();
+ return true;
+ }
+ return false;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h
new file mode 100644
index 00000000..39c81c6d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h
@@ -0,0 +1,205 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H
+#define INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "ITimelineItemBinding.h"
+#include "ITimelineItem.h"
+
+// Data model
+#include "Qt3DSDMHandles.h"
+#include "IDragable.h"
+#include "Qt3DSDMAssetTimelineKeyframe.h"
+#include "OffsetKeyframesCommandHelper.h"
+#include "Qt3DSDMTimeline.h"
+#include "Qt3DSDMSignals.h"
+#include "DispatchListeners.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class CTimelineTranslationManager;
+class Qt3DSDMTimelineItemProperty;
+class CCmdDataModelSetKeyframeTime;
+class RowTree;
+
+namespace qt3dsdm {
+class CStudioSystem;
+}
+
+//=============================================================================
+/**
+ * Binding to generic DataModel object
+ */
+class Qt3DSDMTimelineItemBinding : public ITimelineItemBinding,
+ public ITimelineItem,
+ public IDragable
+
+{
+protected: // Typedef
+ typedef std::map<qt3dsdm::Qt3DSDMPropertyHandle, Qt3DSDMTimelineItemProperty *> TPropertyBindingMap;
+ typedef std::vector<Qt3DSDMAssetTimelineKeyframe> TAssetKeyframeList;
+
+protected:
+ RowTree *m_rowTree = nullptr;
+ CTimelineTranslationManager *m_TransMgr;
+ qt3dsdm::Qt3DSDMInstanceHandle m_DataHandle;
+ ITimelineItemBinding *m_Parent;
+ ITimelineTimebar *m_TimelineTimebar;
+ TPropertyBindingMap m_PropertyBindingMap;
+ TAssetKeyframeList m_Keyframes; /// Sorted (by time) list of keyframes
+ qt3dsdm::CStudioSystem *m_StudioSystem;
+
+ qt3dsdm::TSignalConnectionPtr m_StartTimeConnection;
+ qt3dsdm::TSignalConnectionPtr m_EndTimeConnection;
+
+public:
+ Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr);
+ virtual ~Qt3DSDMTimelineItemBinding();
+
+protected:
+ bool GetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty) const;
+ void SetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty, bool inValue,
+ const QString &inNiceText) const;
+ void SetInstanceHandle(qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+
+public:
+ // ITimelineItem
+ EStudioObjectType GetObjectType() const override;
+ bool IsMaster() const override;
+ bool IsShy() const override;
+ void SetShy(bool) override;
+ bool IsLocked() const override;
+ void SetLocked(bool) override;
+ bool IsVisible() const override;
+ void SetVisible(bool) override;
+ bool HasAction(bool inMaster) override;
+ bool IsVisibilityControlled() const override;
+ bool ChildrenHasAction(bool inMaster) override;
+ bool ComponentHasAction(bool inMaster) override;
+ bool hasSubpresentation() const override;
+ ITimelineTimebar *GetTimebar() override;
+
+ // INamable
+ Q3DStudio::CString GetName() const override;
+ void SetName(const Q3DStudio::CString &inName) override;
+
+ // ITimelineItemBinding
+ ITimelineItem *GetTimelineItem() override;
+ RowTree *getRowTree() const override;
+ void setRowTree(RowTree *row) override;
+ void SetSelected(bool inMultiSelect) override;
+ void OnCollapsed() override;
+ bool OpenAssociatedEditor() override;
+ void SetDropTarget(CDropTarget *inTarget) override;
+ // Hierarchy
+ long GetChildrenCount() override;
+ ITimelineItemBinding *GetChild(long inIndex) override;
+ QList<ITimelineItemBinding *> GetChildren() override;
+ ITimelineItemBinding *GetParent() override;
+ void SetParent(ITimelineItemBinding *parent) override;
+ // Properties
+ long GetPropertyCount() override;
+ ITimelineItemProperty *GetProperty(long inIndex) override;
+ // Eye/Lock toggles
+ bool ShowToggleControls() const override;
+ bool IsLockedEnabled() const override;
+ bool IsVisibleEnabled() const override;
+ // ContextMenu
+ bool IsValidTransaction(EUserTransaction inTransaction) override;
+ void PerformTransaction(EUserTransaction inTransaction) override;
+ Q3DStudio::CString GetObjectPath() override;
+
+ // ITimelineItemKeyframesHolder
+ void InsertKeyframe() override;
+ void DeleteAllChannelKeyframes() override;
+ IKeyframe *GetKeyframeByTime(long inTime) const override;
+
+ // IUICDMSelectable
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstanceHandle() const;
+
+ // IDragable
+ long GetFlavor() const override;
+
+ virtual void AddPropertyRow(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ bool inAppend = false);
+ virtual void RemovePropertyRow(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+ virtual void RefreshPropertyKeyframe(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ qt3dsdm::Qt3DSDMKeyframeHandle,
+ ETimelineKeyframeTransaction inTransaction);
+ virtual void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+ virtual void OnPropertyLinked(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+
+ // Keyframe manipulation
+ virtual bool HasDynamicKeyframes(long inTime);
+ virtual void SetDynamicKeyframes(long inTime, bool inDynamic);
+
+ virtual void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ virtual void OnDeleteChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+
+ void UpdateActionStatus();
+
+ Q3DStudio::CId GetGuid() const;
+
+ // Bridge between asset & DataModel. Ideally we should be fully DataModel
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstance() const;
+
+ int getAnimatedPropertyIndex(int propertyHandle) const;
+ void getTimeContextIndices(const QSet<int> &children, QMap<int ,int> &indexMap);
+
+ ITimelineItemProperty *GetOrCreatePropertyBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+ ITimelineItemProperty *GetPropertyBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle);
+
+ bool isRootComponent() const;
+ bool isDefaultMaterial() const;
+
+protected:
+ virtual ITimelineTimebar *CreateTimelineTimebar();
+ void RemoveAllPropertyBindings();
+ void AddKeyframes(ITimelineItemProperty *inPropertyBinding);
+ bool
+ DeleteAssetKeyframesWhereApplicable(ITimelineItemProperty *inTriggerPropertyBinding = nullptr);
+ void UpdateKeyframe(IKeyframe *inKeyframe, ETimelineKeyframeTransaction inTransaction);
+
+ // For iterating through children
+ virtual bool AmITimeParent() const { return false; }
+
+ // subclasses can call this method to open referenced files
+ virtual bool OpenSourcePathFile();
+};
+
+#endif // INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp
new file mode 100644
index 00000000..d89fcc98
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp
@@ -0,0 +1,470 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "Qt3DSDMTimelineItemProperty.h"
+#include "TimelineTranslationManager.h"
+#include "ITimelineItemBinding.h"
+#include "Qt3DSDMTimelineItemBinding.h"
+#include "Qt3DSDMTimelineKeyframe.h"
+#include "CmdDataModelChangeKeyframe.h"
+#include "CmdDataModelRemoveKeyframe.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "RowTree.h"
+
+// Link to data model
+#include "TimeEditDlg.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMSlides.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMAnimation.h"
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSDMPropertyDefinition.h"
+#include "Qt3DSDMDataCore.h"
+#include "StudioFullSystem.h"
+using namespace qt3dsdm;
+
+bool SortKeyframeByTime(const Qt3DSDMTimelineKeyframe *inLHS, const Qt3DSDMTimelineKeyframe *inRHS)
+{
+ return inLHS->GetTime() < inRHS->GetTime();
+}
+
+// DataModel stores it from 0..1, UI expects 0..255
+inline float DataModelToColor(float inValue)
+{
+ return inValue * 255;
+}
+
+Qt3DSDMTimelineItemProperty::Qt3DSDMTimelineItemProperty(CTimelineTranslationManager *inTransMgr,
+ Qt3DSDMPropertyHandle inPropertyHandle,
+ Qt3DSDMInstanceHandle inInstance)
+ : m_InstanceHandle(inInstance)
+ , m_PropertyHandle(inPropertyHandle)
+ , m_TransMgr(inTransMgr)
+ , m_SetKeyframeValueCommand(nullptr)
+{
+ // Cache all the animation handles because we need them for any keyframes manipulation.
+ // the assumption is that all associated handles are created all at once (i.e. we do not need to
+ // add or delete from this list )
+ CreateKeyframes();
+ InitializeCachedVariables(inInstance);
+ m_Signals.push_back(
+ m_TransMgr->GetStudioSystem()->GetFullSystem()->GetSignalProvider()->ConnectPropertyLinked(
+ std::bind(&Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+
+ m_Signals.push_back(
+ m_TransMgr->GetStudioSystem()
+ ->GetFullSystem()
+ ->GetSignalProvider()
+ ->ConnectPropertyUnlinked(std::bind(
+ &Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+}
+
+Qt3DSDMTimelineItemProperty::~Qt3DSDMTimelineItemProperty()
+{
+ ReleaseKeyframes();
+}
+
+void Qt3DSDMTimelineItemProperty::CreateKeyframes()
+{
+ // Cache all the animation handles because we need them for any keyframes manipulation.
+ // the assumption is that all associated handles are created all at once (i.e. we do not need to
+ // add or delete from this list )
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ DataModelDataType::Value theDataType = thePropertySystem->GetDataType(m_PropertyHandle);
+ IStudioAnimationSystem *theAnimationSystem =
+ m_TransMgr->GetStudioSystem()->GetAnimationSystem();
+ std::tuple<bool, size_t> theArity = GetDatatypeAnimatableAndArity(theDataType);
+ for (size_t i = 0; i < std::get<1>(theArity); ++i) {
+ Qt3DSDMAnimationHandle theAnimationHandle =
+ theAnimationSystem->GetControllingAnimation(m_InstanceHandle, m_PropertyHandle, i);
+ if (theAnimationHandle.Valid())
+ m_AnimationHandles.push_back(theAnimationHandle);
+ }
+ if (!m_AnimationHandles.empty()) { // update wrappers for keyframes
+ IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore();
+ TKeyframeHandleList theKeyframes;
+ // all channels have keyframes at the same time
+ theAnimationCore->GetKeyframes(m_AnimationHandles[0], theKeyframes);
+ for (size_t i = 0; i < theKeyframes.size(); ++i)
+ CreateKeyframeIfNonExistent(theKeyframes[i], m_AnimationHandles[0]);
+ }
+}
+
+void Qt3DSDMTimelineItemProperty::ReleaseKeyframes()
+{
+ m_Keyframes.clear();
+ m_AnimationHandles.clear();
+}
+
+qt3dsdm::Qt3DSDMPropertyHandle Qt3DSDMTimelineItemProperty::getPropertyHandle() const
+{
+ return m_PropertyHandle;
+}
+
+// Type doesn't change and due to the logic required to figure this out, cache it.
+void Qt3DSDMTimelineItemProperty::InitializeCachedVariables(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ using namespace Q3DStudio;
+ qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem();
+
+ m_Type.first = thePropertySystem->GetDataType(m_PropertyHandle);
+ m_Type.second = thePropertySystem->GetAdditionalMetaDataType(inInstance, m_PropertyHandle);
+
+ // Name doesn't change either.
+ TCharStr theFormalName = thePropertySystem->GetFormalName(inInstance, m_PropertyHandle);
+
+ if (theFormalName.empty()) // fallback on property name
+ theFormalName = thePropertySystem->GetName(m_PropertyHandle);
+ m_Name = theFormalName.c_str();
+}
+
+Q3DStudio::CString Qt3DSDMTimelineItemProperty::GetName() const
+{
+ return m_Name;
+}
+
+// Helper function to retrieve the parent binding class.
+inline ITimelineItemBinding *GetParentBinding(RowTree *inRow)
+{
+ ITimelineItemBinding *theParentBinding = nullptr;
+ if (inRow) {
+ RowTree *theParentRow = inRow->parentRow();
+ if (theParentRow) {
+ theParentBinding = theParentRow->getBinding();
+ Q_ASSERT(theParentBinding);
+ }
+ }
+ return theParentBinding;
+}
+
+bool Qt3DSDMTimelineItemProperty::IsMaster() const
+{
+ if (m_rowTree) {
+ if (Qt3DSDMTimelineItemBinding *theParentBinding =
+ static_cast<Qt3DSDMTimelineItemBinding *>(GetParentBinding(m_rowTree)))
+ return m_TransMgr->GetDoc()->GetDocumentReader().IsPropertyLinked(
+ theParentBinding->GetInstanceHandle(), m_PropertyHandle);
+ }
+ return false;
+}
+
+qt3dsdm::TDataTypePair Qt3DSDMTimelineItemProperty::GetType() const
+{
+ return m_Type;
+}
+
+void CompareAndSet(const Qt3DSDMTimelineKeyframe *inKeyframe, float &outRetValue, bool inGreaterThan)
+{
+ float theValue = (inGreaterThan) ? inKeyframe->GetMaxValue() : inKeyframe->GetMinValue();
+ if ((inGreaterThan && theValue > outRetValue) || (!inGreaterThan && theValue < outRetValue))
+ outRetValue = theValue;
+}
+
+// return the max value of the current set of keyframes
+float Qt3DSDMTimelineItemProperty::GetMaximumValue() const
+{
+ float theRetVal = FLT_MIN;
+ do_all(m_Keyframes, std::bind(CompareAndSet, std::placeholders::_1, std::ref(theRetVal), true));
+ if (m_Type.first == DataModelDataType::Float4 && m_Type.second == AdditionalMetaDataType::Color)
+ theRetVal = DataModelToColor(theRetVal);
+ return theRetVal;
+}
+
+// return the min value of the current set of keyframes
+float Qt3DSDMTimelineItemProperty::GetMinimumValue() const
+{
+ float theRetVal = FLT_MAX;
+ do_all(m_Keyframes, std::bind(CompareAndSet, std::placeholders::_1, std::ref(theRetVal), false));
+ if (m_Type.first == DataModelDataType::Float4 && m_Type.second == AdditionalMetaDataType::Color)
+ theRetVal = DataModelToColor(theRetVal);
+ return theRetVal;
+}
+
+RowTree *Qt3DSDMTimelineItemProperty::getRowTree() const
+{
+ return m_rowTree;
+}
+
+// Ensures the object that owns this property is selected.
+void Qt3DSDMTimelineItemProperty::SetSelected()
+{
+ if (m_rowTree) {
+ ITimelineItemBinding *theParentBinding = GetParentBinding(m_rowTree);
+ if (theParentBinding)
+ theParentBinding->SetSelected(false);
+ }
+}
+
+void Qt3DSDMTimelineItemProperty::DeleteAllKeys()
+{
+ if (m_Keyframes.empty())
+ return;
+
+ using namespace Q3DStudio;
+
+ ScopedDocumentEditor editor(*m_TransMgr->GetDoc(), QObject::tr("Delete All Keyframes"),
+ __FILE__, __LINE__);
+ for (size_t idx = 0, end = m_AnimationHandles.size(); idx < end; ++idx)
+ editor->DeleteAllKeyframes(m_AnimationHandles[idx]);
+}
+
+IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByTime(long inTime) const
+{
+ std::vector<long> theTest;
+ TKeyframeList::const_iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ if ((*theIter)->GetTime() == inTime)
+ return (*theIter);
+
+ theTest.push_back((*theIter)->GetTime());
+ }
+ // if key had been deleted, this returns nullptr
+ return nullptr;
+}
+
+IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByIndex(long inIndex) const
+{
+ if (inIndex >= 0 && inIndex < (long)m_Keyframes.size())
+ return m_Keyframes[inIndex];
+
+ Q_ASSERT(0); // should not happen
+ return nullptr;
+}
+
+long Qt3DSDMTimelineItemProperty::GetKeyframeCount() const
+{
+ // this list is updated in constructor and when keyframes are added or deleted.
+ return (long)m_Keyframes.size();
+}
+
+long Qt3DSDMTimelineItemProperty::GetChannelCount() const
+{
+ return (long)m_AnimationHandles.size();
+}
+
+float Qt3DSDMTimelineItemProperty::GetChannelValueAtTime(long inChannelIndex, long inTime)
+{
+ // if no keyframes, get current property value.
+ if (m_Keyframes.empty()) {
+ Qt3DSDMTimelineItemBinding *theParentBinding =
+ static_cast<Qt3DSDMTimelineItemBinding *>(GetParentBinding(m_rowTree));
+ if (theParentBinding) {
+
+ SValue theValue;
+ qt3dsdm::IPropertySystem *thePropertySystem =
+ m_TransMgr->GetStudioSystem()->GetPropertySystem();
+ thePropertySystem->GetInstancePropertyValue(theParentBinding->GetInstanceHandle(),
+ m_PropertyHandle, theValue);
+ switch (m_Type.first) {
+ case DataModelDataType::Float4: {
+ if (m_Type.second == AdditionalMetaDataType::Color) {
+ SFloat4 theFloat4 = qt3dsdm::get<SFloat4>(theValue);
+ if (inChannelIndex >= 0 && inChannelIndex < 4)
+ return DataModelToColor(theFloat4[inChannelIndex]);
+ }
+ break;
+ }
+ case DataModelDataType::Float3: {
+
+ SFloat3 theFloat3 = qt3dsdm::get<SFloat3>(theValue);
+ if (inChannelIndex >= 0 && inChannelIndex < 3)
+ return theFloat3[inChannelIndex];
+ break;
+ }
+ case DataModelDataType::Float2: {
+ SFloat2 theFloat2 = qt3dsdm::get<SFloat2>(theValue);
+ if (inChannelIndex >= 0 && inChannelIndex < 2)
+ return theFloat2[inChannelIndex];
+ break;
+ }
+ case DataModelDataType::Float:
+ return qt3dsdm::get<float>(theValue);
+ break;
+ default: // TODO: handle other types
+ break;
+ }
+ }
+ }
+ IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore();
+ if (!m_AnimationHandles.empty() && inChannelIndex >= 0
+ && inChannelIndex < (long)m_AnimationHandles.size()) {
+ float theValue = theAnimationCore->EvaluateAnimation(
+ m_AnimationHandles[inChannelIndex], Qt3DSDMTimelineKeyframe::GetTimeInSecs(inTime));
+ if (m_Type.first == DataModelDataType::Float4
+ && m_Type.second == AdditionalMetaDataType::Color)
+ theValue = DataModelToColor(theValue);
+
+ return theValue;
+ }
+ return 0.f;
+}
+
+void Qt3DSDMTimelineItemProperty::SetChannelValueAtTime(long inChannelIndex, long inTime,
+ float inValue)
+{
+ Qt3DSDMTimelineKeyframe *theKeyframeWrapper =
+ dynamic_cast<Qt3DSDMTimelineKeyframe *>(GetKeyframeByTime(inTime));
+ if (theKeyframeWrapper) {
+ Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframes;
+ theKeyframeWrapper->GetKeyframeHandles(theKeyframes);
+ if (!theKeyframes.empty() && inChannelIndex < (long)theKeyframes.size()) {
+ inValue /= 255;
+ if (!m_SetKeyframeValueCommand)
+ m_SetKeyframeValueCommand = new CCmdDataModelSetKeyframeValue(
+ g_StudioApp.GetCore()->GetDoc(), theKeyframes[inChannelIndex], inValue);
+ m_SetKeyframeValueCommand->Update(inValue);
+ }
+ }
+}
+
+void Qt3DSDMTimelineItemProperty::setRowTree(RowTree *rowTree)
+{
+ m_rowTree = rowTree;
+}
+
+bool Qt3DSDMTimelineItemProperty::IsDynamicAnimation()
+{
+ return m_Keyframes.size() > 0 && m_Keyframes[0]->IsDynamic();
+}
+
+//=============================================================================
+/**
+ * For updating the UI when keyframes are added/updated/deleted.
+ */
+bool Qt3DSDMTimelineItemProperty::RefreshKeyframe(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ ETimelineKeyframeTransaction inTransaction)
+{
+ bool theHandled = false;
+ switch (inTransaction) {
+ case ETimelineKeyframeTransaction_Delete: {
+ TKeyframeList::iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ Qt3DSDMTimelineKeyframe *theKeyframe = *theIter;
+ if (theKeyframe->HasKeyframeHandle(inKeyframe)) {
+ m_Keyframes.erase(theIter);
+ theHandled = true;
+ break;
+ }
+ }
+ } break;
+ case ETimelineKeyframeTransaction_Add: {
+ Q_ASSERT(!m_AnimationHandles.empty());
+ IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore();
+ Qt3DSDMAnimationHandle theAnimationHandle =
+ theAnimationCore->GetAnimationForKeyframe(inKeyframe);
+ // only create for the first animation handle.
+ if (theAnimationHandle == m_AnimationHandles[0]) { // for undo/redo, the keyframes can be
+ // added in reverse, hence the need to
+ // sort
+ if (CreateKeyframeIfNonExistent(inKeyframe, theAnimationHandle))
+ std::stable_sort(m_Keyframes.begin(), m_Keyframes.end(), SortKeyframeByTime);
+ theHandled = true;
+ }
+ } break;
+ case ETimelineKeyframeTransaction_Update:
+ case ETimelineKeyframeTransaction_DynamicChanged:
+ theHandled = true;
+ break;
+ default:
+ return false;
+ }
+
+ return theHandled;
+}
+
+IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByHandle(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe)
+{
+ TKeyframeList::iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ Qt3DSDMTimelineKeyframe *theKeyframe = *theIter;
+ if (theKeyframe->HasKeyframeHandle(inKeyframe))
+ return *theIter;
+ }
+ return nullptr;
+}
+
+/**
+ * Create a wrapper for this keyframe if doesn't exists.
+ * @return true if created, false if already exists.
+ */
+bool Qt3DSDMTimelineItemProperty::CreateKeyframeIfNonExistent(
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframeHandle, Qt3DSDMAnimationHandle inOwningAnimation)
+{
+ TKeyframeList::iterator theIter = m_Keyframes.begin();
+ for (; theIter != m_Keyframes.end(); ++theIter) {
+ Qt3DSDMTimelineKeyframe *theKeyframe = *theIter;
+ if (theKeyframe->HasKeyframeHandle(inKeyframeHandle))
+ return false;
+ }
+ // check for multiple channels => only create 1 Qt3DSDMTimelineKeyframe
+ Qt3DSDMTimelineKeyframe *theNewKeyframe =
+ new Qt3DSDMTimelineKeyframe(g_StudioApp.GetCore()->GetDoc());
+ theNewKeyframe->AddKeyframeHandle(inKeyframeHandle);
+ if (m_AnimationHandles.size()
+ > 1) { // assert assumption that is only called for the first handle
+ Q_ASSERT(m_AnimationHandles[0] == inOwningAnimation);
+ IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore();
+ float theKeyframeTime = KeyframeTime(theAnimationCore->GetKeyframeData(inKeyframeHandle));
+ for (size_t i = 1; i < m_AnimationHandles.size(); ++i) {
+ TKeyframeHandleList theKeyframes;
+ theAnimationCore->GetKeyframes(m_AnimationHandles[i], theKeyframes);
+ // the data model ensures that there is only 1 keyframe created for a given time
+ for (size_t theKeyIndex = 0; theKeyIndex < theKeyframes.size(); ++theKeyIndex) {
+ float theValue =
+ KeyframeTime(theAnimationCore->GetKeyframeData(theKeyframes[theKeyIndex]));
+ if (theValue == theKeyframeTime) {
+ theNewKeyframe->AddKeyframeHandle(theKeyframes[theKeyIndex]);
+ break;
+ }
+ }
+ }
+ }
+ m_Keyframes.push_back(theNewKeyframe);
+ return true;
+}
+
+void Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged(qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ if (inInstance == m_InstanceHandle && inProperty == m_PropertyHandle) {
+ // Re-bind to keyframes because the ones we should be pointing to will have changed.
+ ReleaseKeyframes();
+ CreateKeyframes();
+ }
+}
+
+void Qt3DSDMTimelineItemProperty::RefreshKeyFrames(void)
+{
+ std::stable_sort(m_Keyframes.begin(), m_Keyframes.end(), SortKeyframeByTime);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h
new file mode 100644
index 00000000..1b1524e4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT3DSDM_TIMELINE_ITEM_PROPERTY_H
+#define QT3DSDM_TIMELINE_ITEM_PROPERTY_H 1
+
+#pragma once
+
+#include "ITimelineItemProperty.h"
+#include "Qt3DSDMTimelineKeyframe.h"
+#include "Qt3DSDMTimeline.h"
+#include "Qt3DSDMPropertyDefinition.h"
+
+class RowTree;
+class CTimelineTranslationManager;
+class CCmdDataModelSetKeyframeValue;
+class Qt3DSDMTimelineItemBinding;
+
+//=============================================================================
+/**
+ * A data model item's property.
+ * Typically only animated properties show up in the Timeline.
+ */
+//=============================================================================
+class Qt3DSDMTimelineItemProperty : public ITimelineItemProperty
+{
+public:
+ Qt3DSDMTimelineItemProperty(CTimelineTranslationManager *inTransMgr,
+ qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ virtual ~Qt3DSDMTimelineItemProperty();
+
+ // ITimelineProperty
+ Q3DStudio::CString GetName() const override;
+ bool IsMaster() const override;
+ qt3dsdm::TDataTypePair GetType() const override;
+ float GetMaximumValue() const override;
+ float GetMinimumValue() const override;
+ void SetSelected() override;
+ void DeleteAllKeys() override;
+ IKeyframe *GetKeyframeByTime(long inTime) const override;
+ IKeyframe *GetKeyframeByIndex(long inIndex) const override;
+ long GetKeyframeCount() const override;
+ long GetChannelCount() const override;
+ float GetChannelValueAtTime(long inChannelIndex, long inTime) override;
+ void SetChannelValueAtTime(long inChannelIndex, long inTime, float inValue) override;
+ bool IsDynamicAnimation() override;
+
+ void setRowTree(RowTree *rowTree) override;
+ RowTree *getRowTree() const override;
+
+ bool RefreshKeyframe(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ ETimelineKeyframeTransaction inTransaction);
+ IKeyframe *GetKeyframeByHandle(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe);
+
+ void RefreshKeyFrames(void);
+
+ qt3dsdm::Qt3DSDMPropertyHandle getPropertyHandle() const;
+
+protected:
+ void InitializeCachedVariables(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ bool CreateKeyframeIfNonExistent(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ qt3dsdm::Qt3DSDMAnimationHandle inOwningAnimation);
+ void OnPropertyLinkStatusChanged(qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void CreateKeyframes();
+ void ReleaseKeyframes();
+
+protected:
+ typedef std::vector<Qt3DSDMTimelineKeyframe *> TKeyframeList;
+
+ qt3dsdm::Qt3DSDMInstanceHandle m_InstanceHandle;
+ qt3dsdm::Qt3DSDMPropertyHandle m_PropertyHandle;
+ CTimelineTranslationManager *m_TransMgr;
+ std::vector<qt3dsdm::Qt3DSDMAnimationHandle> m_AnimationHandles;
+ TKeyframeList m_Keyframes;
+ CCmdDataModelSetKeyframeValue
+ *m_SetKeyframeValueCommand; // for merging modifying keyframe values via graph
+ qt3dsdm::TDataTypePair m_Type;
+ Q3DStudio::CString m_Name;
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Signals;
+
+private:
+ RowTree *m_rowTree = nullptr;
+};
+
+#endif // QT3DSDM_TIMELINE_ITEM_PROPERTY_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp
new file mode 100644
index 00000000..ae7e2035
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "Qt3DSDMTimelineKeyframe.h"
+#include "Qt3DSDMAnimation.h"
+#include "CmdDataModelChangeKeyframe.h"
+#include "CmdBatch.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "OffsetKeyframesCommandHelper.h"
+
+#include "Doc.h"
+#include "StudioApp.h"
+#include "Core.h"
+
+using namespace qt3dsdm;
+
+// TODO: figure out if we can just use IDoc instead of CDoc
+Qt3DSDMTimelineKeyframe::Qt3DSDMTimelineKeyframe(IDoc *inDoc)
+ : m_Doc(dynamic_cast<CDoc *>(inDoc))
+ , m_Selected(false)
+{
+}
+
+Qt3DSDMTimelineKeyframe::~Qt3DSDMTimelineKeyframe()
+{
+}
+
+bool Qt3DSDMTimelineKeyframe::IsSelected() const
+{
+ return m_Selected;
+}
+
+float my_roundf(float r)
+{
+ return (r > 0.0f) ? floorf(r + 0.5f) : ceilf(r - 0.5f);
+}
+
+long Qt3DSDMTimelineKeyframe::GetTime() const
+{
+ if (!m_KeyframeHandles.empty()) {
+ IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore();
+ Qt3DSDMKeyframeHandle theKeyframeHandle = *m_KeyframeHandles.begin();
+ if (theAnimationCore->KeyframeValid(theKeyframeHandle)) {
+ float theTimeinSecs =
+ KeyframeTime(theAnimationCore->GetKeyframeData(theKeyframeHandle));
+ // We always convert back and forth between between long and float.
+ // This causes especially issues when we do comparisons
+ return (long)my_roundf(theTimeinSecs * 1000);
+ }
+ }
+ return -1; // keyframe was deleted, and data cannot be retrieved.
+}
+
+float Qt3DSDMTimelineKeyframe::GetTimeInSecs(long inTime)
+{
+ float theTimeinSecs = static_cast<float>(inTime) / 1000.f;
+ // round off to 4 decimal place to workaround precision issues
+ // TODO: fix this, either all talk float OR long. choose one.
+ theTimeinSecs = (float)(((theTimeinSecs + 0.00005) * 10000.0) / 10000.0f);
+ return theTimeinSecs;
+}
+
+void Qt3DSDMTimelineKeyframe::SetTime(const long inNewTime)
+{
+ float theTimeinSecs = GetTimeInSecs(inNewTime);
+ CCmd *theCmd = nullptr;
+ if (m_KeyframeHandles.size() == 1) {
+ theCmd = new CCmdDataModelSetKeyframeTime(m_Doc, m_KeyframeHandles.front(), theTimeinSecs);
+ } else { // more than 1 channel
+ CCmdBatch *theBatch = new CCmdBatch(m_Doc);
+ TKeyframeHandleList::iterator theIter = m_KeyframeHandles.begin();
+ for (; theIter != m_KeyframeHandles.end(); ++theIter)
+ theBatch->AddCommand(new CCmdDataModelSetKeyframeTime(m_Doc, *theIter, theTimeinSecs));
+ theCmd = theBatch;
+ }
+ if (theCmd)
+ m_Doc->GetCore()->ExecuteCommand(theCmd);
+
+#ifdef _DEBUG
+ // we have a precision issue from converting from long to float..
+ IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore();
+ long theTest = static_cast<long>(
+ KeyframeTime(theAnimationCore->GetKeyframeData(*m_KeyframeHandles.begin())) * 1000);
+ Q_ASSERT(inNewTime == theTest);
+#endif
+}
+
+inline Qt3DSDMAnimationHandle GetAnimationHandle(qt3dsdm::IAnimationCore *inAnimationCore,
+ const TKeyframeHandleList &inKeyframes)
+{
+ if (!inKeyframes.empty())
+ return inAnimationCore->GetAnimationForKeyframe(inKeyframes[0]);
+ return 0;
+}
+
+void Qt3DSDMTimelineKeyframe::SetDynamic(bool inIsDynamic)
+{
+ if (!m_KeyframeHandles.empty()) {
+ Qt3DSDMAnimationHandle theAnimation =
+ GetAnimationHandle(m_Doc->GetStudioSystem()->GetAnimationCore(), m_KeyframeHandles);
+ if (theAnimation.Valid())
+ m_Doc->GetCore()->ExecuteCommand(
+ new CCmdDataModelChangeDynamicKeyframe(m_Doc, theAnimation, inIsDynamic));
+ }
+}
+
+Keyframe *Qt3DSDMTimelineKeyframe::getUI()
+{
+ return m_ui;
+}
+
+void Qt3DSDMTimelineKeyframe::setUI(Keyframe *kfUI)
+{
+ m_ui = kfUI;
+}
+
+// Only the first key of a track can be dynamic.
+bool Qt3DSDMTimelineKeyframe::IsDynamic() const
+{
+ qt3dsdm::IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore();
+ Qt3DSDMAnimationHandle theAnimation = GetAnimationHandle(theAnimationCore, m_KeyframeHandles);
+ if (theAnimation.Valid()) {
+ SAnimationInfo theInfo = theAnimationCore->GetAnimationInfo(theAnimation);
+ if (theInfo.m_DynamicFirstKeyframe) {
+ TKeyframeHandleList theKeyframes;
+ theAnimationCore->GetKeyframes(theAnimation, theKeyframes);
+ if (!theKeyframes.empty()) // only true if track is dynamic and this is the first
+ // keyframe. Might have to optimize because this is so
+ // clunky.
+ return (theKeyframes[0] == m_KeyframeHandles[0]);
+ }
+ }
+ return false;
+}
+
+void Qt3DSDMTimelineKeyframe::AddKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle)
+{
+ m_KeyframeHandles.push_back(inHandle);
+}
+
+bool Qt3DSDMTimelineKeyframe::HasKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle) const
+{
+ TKeyframeHandleList::const_iterator theIter = m_KeyframeHandles.begin();
+ for (; theIter != m_KeyframeHandles.end(); ++theIter) {
+ if (*theIter == inHandle)
+ return true;
+ }
+ return false;
+}
+
+void Qt3DSDMTimelineKeyframe::SetSelected(bool inSelected)
+{
+ m_Selected = inSelected;
+}
+
+// For colors, there would be 3 keyframe handles
+void Qt3DSDMTimelineKeyframe::UpdateKeyframesTime(COffsetKeyframesCommandHelper *inCommandHelper,
+ long inTime)
+{
+ for (size_t i = 0; i < m_KeyframeHandles.size(); ++i)
+ inCommandHelper->SetCommandTime(m_KeyframeHandles[i], inTime);
+}
+
+void Qt3DSDMTimelineKeyframe::GetKeyframeHandles(TKeyframeHandleList &outList) const
+{
+ outList = m_KeyframeHandles;
+}
+
+void CompareAndSet(Qt3DSDMKeyframeHandle inKeyframe, IAnimationCore *inAnimationCore,
+ float &outRetValue, bool inGreaterThan)
+{
+ TKeyframe theKeyframeData = inAnimationCore->GetKeyframeData(inKeyframe);
+ float theValue = KeyframeValueValue(theKeyframeData);
+ if ((inGreaterThan && theValue > outRetValue) || (!inGreaterThan && theValue < outRetValue))
+ outRetValue = theValue;
+}
+
+float Qt3DSDMTimelineKeyframe::GetMaxValue() const
+{
+ IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore();
+ float theRetVal = FLT_MIN;
+ do_all(m_KeyframeHandles,
+ std::bind(CompareAndSet, std::placeholders::_1, theAnimationCore,
+ std::ref(theRetVal), true));
+ return theRetVal;
+}
+
+float Qt3DSDMTimelineKeyframe::GetMinValue() const
+{
+ IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore();
+ float theRetVal = FLT_MAX;
+ do_all(m_KeyframeHandles,
+ std::bind(CompareAndSet, std::placeholders::_1, theAnimationCore,
+ std::ref(theRetVal), false));
+ return theRetVal;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h
new file mode 100644
index 00000000..7799cc0d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QT3DSDM_KEYFRAME_H
+#define QT3DSDM_KEYFRAME_H 1
+
+#pragma once
+
+#include "IKeyframe.h"
+
+// Data model specific
+#include "Qt3DSDMHandles.h"
+
+class IDoc;
+class CDoc;
+class CCmdBatch;
+class COffsetKeyframesCommandHelper;
+struct Keyframe;
+
+//==============================================================================
+/**
+ * Wrapper for a keyframe in DataModel.
+ */
+//==============================================================================
+class Qt3DSDMTimelineKeyframe : public IKeyframe
+{
+public:
+ typedef std::vector<qt3dsdm::Qt3DSDMKeyframeHandle> TKeyframeHandleList;
+
+protected:
+ TKeyframeHandleList
+ m_KeyframeHandles; ///< no. corresponds to the channels the animated property has.
+ CDoc *m_Doc;
+ bool m_Selected;
+ Keyframe *m_ui = nullptr;
+
+public:
+ Qt3DSDMTimelineKeyframe(IDoc *inDoc);
+ virtual ~Qt3DSDMTimelineKeyframe();
+
+ // IKeyframe
+ bool IsSelected() const override;
+ long GetTime() const override;
+ void SetTime(const long inNewTime) override;
+ void SetDynamic(bool inIsDynamic) override;
+ Keyframe *getUI() override;
+ void setUI(Keyframe *kfUI) override;
+ bool IsDynamic() const override;
+
+ void AddKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle);
+ bool HasKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle) const;
+ void SetSelected(bool inSelected);
+ void UpdateKeyframesTime(COffsetKeyframesCommandHelper *inCommandHelper, long inTime);
+ void GetKeyframeHandles(TKeyframeHandleList &outList) const;
+
+ // support drawing graphs
+ float GetMaxValue() const;
+ float GetMinValue() const;
+
+ static float GetTimeInSecs(long inTime);
+};
+
+#endif // QT3DSDM_KEYFRAME_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp
new file mode 100644
index 00000000..0ecefefd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp
@@ -0,0 +1,222 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "Qt3DSDMTimelineTimebar.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMDataCore.h"
+#include "Qt3DSDMDataTypes.h"
+#include "ClientDataModelBridge.h"
+#include "TimelineTranslationManager.h"
+#include "Doc.h"
+#include "Dispatch.h"
+#include "Core.h"
+#include "IDocumentEditor.h"
+#include "StudioFullSystem.h"
+#include "StudioPreferences.h"
+#include "ITimelineItemBinding.h"
+#include "RowTree.h"
+#include "RowTimeline.h"
+#include "StudioApp.h"
+#include "Dialogs.h"
+
+Qt3DSDMTimelineTimebar::Qt3DSDMTimelineTimebar(
+ CTimelineTranslationManager *inTimelineTranslationManager,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle)
+ : Q3DStudio::CUpdateableDocumentEditor(*inTimelineTranslationManager->GetDoc())
+ , m_TimelineTranslationManager(inTimelineTranslationManager)
+ , m_PropertySystem(inTimelineTranslationManager->GetStudioSystem()->GetPropertySystem())
+ , m_DataHandle(inDataHandle)
+{
+ CClientDataModelBridge *theClientDataModelBridge =
+ inTimelineTranslationManager->GetStudioSystem()->GetClientDataModelBridge();
+ m_StartTime = theClientDataModelBridge->GetSceneAsset().m_StartTime;
+ m_EndTime = theClientDataModelBridge->GetSceneAsset().m_EndTime;
+ qt3dsdm::SValue theValue;
+ if (m_PropertySystem->GetInstancePropertyValue(
+ m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor, theValue)) {
+ qt3dsdm::SFloat4 theTimebarColor = qt3dsdm::get<qt3dsdm::SFloat4>(theValue);
+
+ m_Color.SetRGB(static_cast<int>(theTimebarColor.m_Floats[0] * 255.0f),
+ static_cast<int>(theTimebarColor.m_Floats[1] * 255.0f),
+ static_cast<int>(theTimebarColor.m_Floats[2] * 255.0f));
+ }
+ qt3dsdm::IStudioFullSystemSignalProvider *theProvider =
+ inTimelineTranslationManager->GetStudioSystem()->GetFullSystem()->GetSignalProvider();
+ m_PropertyChangedSignal = theProvider->ConnectInstancePropertyValue(
+ std::bind(&Qt3DSDMTimelineTimebar::OnPropertyChanged, this,
+ std::placeholders::_1, std::placeholders::_2));
+
+ OnPropertyChanged(m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor);
+ OnPropertyChanged(m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarText);
+}
+
+void Qt3DSDMTimelineTimebar::OnPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ if (m_DataHandle == inInstance) {
+ bool needsInvalidate = false;
+ qt3dsdm::SValue theValue;
+ CClientDataModelBridge *theClientDataModelBridge =
+ m_TimelineTranslationManager->GetStudioSystem()->GetClientDataModelBridge();
+ if (inProperty == theClientDataModelBridge->GetSceneAsset().m_TimebarColor) {
+
+ if (m_PropertySystem->GetInstancePropertyValue(
+ m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor,
+ theValue)) {
+ qt3dsdm::SFloat4 theTimebarColor = qt3dsdm::get<qt3dsdm::SFloat4>(theValue);
+
+ m_Color.SetRGB(static_cast<int>(theTimebarColor.m_Floats[0] * 255.0f),
+ static_cast<int>(theTimebarColor.m_Floats[1] * 255.0f),
+ static_cast<int>(theTimebarColor.m_Floats[2] * 255.0f));
+ } else {
+ switch (theClientDataModelBridge->GetObjectType(inInstance)) {
+ case OBJTYPE_LAYER:
+ m_Color = CStudioPreferences::GetLayerTimebarColor();
+ break;
+ default:
+ m_Color = CStudioPreferences::GetObjectTimebarColor();
+ break;
+ }
+ }
+ needsInvalidate = true;
+ } else if (inProperty == theClientDataModelBridge->GetSceneAsset().m_TimebarText) {
+ if (m_PropertySystem->GetInstancePropertyValue(
+ m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarText,
+ theValue)) {
+ m_Comment = qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->toQString();
+ } else {
+ m_Comment.clear();
+ }
+ needsInvalidate = true;
+ }
+ if (needsInvalidate) {
+ ITimelineItemBinding *theBinding =
+ m_TimelineTranslationManager->GetOrCreate(inInstance);
+ if (theBinding) {
+ RowTree *rowTree = theBinding->getRowTree();
+ if (rowTree)
+ rowTree->rowTimeline()->setBarColor(m_Color);
+ }
+ }
+ }
+}
+
+Qt3DSDMTimelineTimebar::~Qt3DSDMTimelineTimebar()
+{
+}
+
+// TODO: Can we put this on IInstancePropertyCore?
+template <typename T>
+T GetInstancePropertyValue(qt3dsdm::IPropertySystem *inPropertySystem,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ qt3dsdm::SValue theValue;
+ inPropertySystem->GetInstancePropertyValue(inInstanceHandle, inProperty, theValue);
+ return qt3dsdm::get<T>(theValue);
+}
+
+long Qt3DSDMTimelineTimebar::GetStartTime() const
+{
+ return GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_StartTime);
+}
+
+long Qt3DSDMTimelineTimebar::GetEndTime() const
+{
+ return GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_EndTime);
+}
+
+long Qt3DSDMTimelineTimebar::GetDuration() const
+{
+ auto theStartTime = GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_StartTime);
+ auto theEndTime = GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_EndTime);
+
+ return theEndTime - theStartTime;
+}
+
+bool Qt3DSDMTimelineTimebar::ShowHandleBars() const
+{
+ return true;
+}
+
+void Qt3DSDMTimelineTimebar::OnBeginDrag()
+{ // Really? TODO: Figure out why this is here.
+ // ASSERT(0);
+}
+
+void Qt3DSDMTimelineTimebar::OffsetTime(long inDiff)
+{
+ if (m_DataHandle.Valid()) {
+ ENSURE_EDITOR(QObject::tr("Time Bar Move")).OffsetTimeRange(m_DataHandle, inDiff);
+ m_TimelineTranslationManager->GetDoc()
+ ->GetCore()
+ ->GetDispatch()
+ ->FireImmediateRefreshInstance(m_DataHandle);
+ }
+}
+
+void Qt3DSDMTimelineTimebar::ChangeTime(long inTime, bool inSetStart)
+{
+ if (m_DataHandle.Valid()) {
+ ENSURE_EDITOR(QObject::tr("Time Bar Resize")).ResizeTimeRange(m_DataHandle, inTime,
+ inSetStart);
+ m_TimelineTranslationManager->GetDoc()
+ ->GetCore()
+ ->GetDispatch()
+ ->FireImmediateRefreshInstance(m_DataHandle);
+ }
+}
+
+void Qt3DSDMTimelineTimebar::CommitTimeChange()
+{
+ CommitEditor();
+}
+
+void Qt3DSDMTimelineTimebar::RollbackTimeChange()
+{
+ RollbackEditor();
+}
+
+void Qt3DSDMTimelineTimebar::SetTimebarComment(const QString &inComment)
+{
+ using namespace Q3DStudio;
+ if (inComment != m_Comment) {
+ qt3dsdm::Qt3DSDMInstanceHandle theHandle = m_DataHandle;
+ SCOPED_DOCUMENT_EDITOR(*m_TimelineTranslationManager->GetDoc(),
+ QObject::tr("Set Time Bar Text"))
+ ->SetTimebarText(theHandle, inComment);
+ }
+}
+
+void Qt3DSDMTimelineTimebar::SetTimebarTime(ITimeChangeCallback *inCallback /*= nullptr*/)
+{
+ g_StudioApp.GetDialogs()->asyncDisplayDurationEditDialog(GetStartTime(), GetEndTime(),
+ inCallback);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h
new file mode 100644
index 00000000..33e3f22d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+///////////////////////////////////////////////////////////////////////////////
+// Includes
+#include "ITimelineTimebar.h"
+#include "Qt3DSDMHandles.h"
+#include "IDocumentEditor.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// Forwards
+class CTimelineTranslationManager;
+
+namespace Q3DStudio {
+class IDocumentEditor;
+}
+
+namespace qt3dsdm {
+class IPropertySystem;
+class ISignalConnection;
+}
+
+//=============================================================================
+/**
+ * General timebar implementation for DataModel objects
+ */
+class Qt3DSDMTimelineTimebar : public ITimelineTimebar, public Q3DStudio::CUpdateableDocumentEditor
+{
+public:
+ Qt3DSDMTimelineTimebar(CTimelineTranslationManager *inTimelineTranslationManager,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ virtual ~Qt3DSDMTimelineTimebar();
+
+protected:
+ CTimelineTranslationManager *m_TimelineTranslationManager;
+ qt3dsdm::IPropertySystem *m_PropertySystem;
+ qt3dsdm::Qt3DSDMInstanceHandle m_DataHandle; // The Instance Handle for this Timeline Timeber.
+ qt3dsdm::Qt3DSDMPropertyHandle m_StartTime;
+ qt3dsdm::Qt3DSDMPropertyHandle m_EndTime;
+ ::CColor m_Color; // Timebar color
+
+ QString m_Comment; // Timebar comment text
+ std::shared_ptr<qt3dsdm::ISignalConnection> m_PropertyChangedSignal;
+ void OnPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+
+public:
+ // ITimelineTimebar
+ long GetStartTime() const override;
+ long GetEndTime() const override;
+ long GetDuration() const override;
+ bool ShowHandleBars() const override;
+ void OnBeginDrag() override;
+ void OffsetTime(long inDiff) override;
+ void ChangeTime(long inTime, bool inSetStart) override;
+ void CommitTimeChange() override;
+ void RollbackTimeChange() override;
+ ::CColor GetTimebarColor() override { return m_Color; }
+ QString GetTimebarComment() const override { return m_Comment; }
+ void SetTimebarComment(const QString &inComment) override;
+ void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) override;
+};
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp
new file mode 100644
index 00000000..6707f717
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "SlideTimelineItemBinding.h"
+
+// Data model specific
+#include "Doc.h"
+#include "CmdGeneric.h"
+#include "EmptyTimelineTimebar.h"
+
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "ClientDataModelBridge.h"
+
+using namespace qt3dsdm;
+
+CSlideTimelineItemBinding::CSlideTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ Qt3DSDMInstanceHandle inDataHandle)
+ : Qt3DSDMTimelineItemBinding(inMgr)
+{
+ qt3dsdm::Qt3DSDMSlideHandle theSlideHandle =
+ m_StudioSystem->GetSlideSystem()->GetSlideByInstance(inDataHandle);
+
+ // Get the owning component of m_SlideHandle.
+ // This should return CAsset OBJTYPE_SCENE or OBJTYPE_COMPONENT.
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance =
+ m_StudioSystem->GetClientDataModelBridge()->GetOwningComponentInstance(theSlideHandle);
+ SetInstanceHandle(theInstance);
+
+ // Listen to change on Asset name
+ IStudioFullSystemSignalProvider *theEngine = m_StudioSystem->GetFullSystemSignalProvider();
+ std::function<void(Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)> theSetter(
+ std::bind(&CSlideTimelineItemBinding::OnPropertyChanged, this, std::placeholders::_2));
+ m_Connection = theEngine->ConnectInstancePropertyValue(
+ std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void(
+ Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>,
+ std::placeholders::_1, std::placeholders::_2, theInstance,
+ m_StudioSystem->GetClientDataModelBridge()->GetNameProperty(), theSetter));
+}
+
+ITimelineTimebar *CSlideTimelineItemBinding::GetTimebar()
+{ // No timebars on slides
+ return new CEmptyTimelineTimebar();
+}
+
+void CSlideTimelineItemBinding::SetName(const Q3DStudio::CString & /*inName*/)
+{
+ // Do nothing because name is read only
+}
+
+bool CSlideTimelineItemBinding::IsValidTransaction(EUserTransaction inTransaction)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ switch (inTransaction) {
+ // Disable the following context menus
+ case EUserTransaction_Rename:
+ case EUserTransaction_MakeComponent:
+ case EUserTransaction_EditComponent:
+ return false;
+ default:
+ break;
+ }
+
+ return Qt3DSDMTimelineItemBinding::IsValidTransaction(inTransaction);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h
new file mode 100644
index 00000000..54f01ce5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_SLIDE_TIMELINEITEM_BINDING_H
+#define INCLUDED_SLIDE_TIMELINEITEM_BINDING_H 1
+
+#pragma once
+
+#include "Qt3DSDMTimelineItemBinding.h"
+
+//==============================================================================
+// Classes
+//==============================================================================
+class ITimelineItem;
+class CTimelineTranslationManager;
+
+//=============================================================================
+/**
+ * Binding to a DataModel object of Slide type
+ */
+class CSlideTimelineItemBinding : public Qt3DSDMTimelineItemBinding
+{
+public:
+ CSlideTimelineItemBinding(CTimelineTranslationManager *inMgr,
+ qt3dsdm::Qt3DSDMInstanceHandle inDataHandle);
+ ~CSlideTimelineItemBinding() {}
+
+ // Qt3DSDMTimelineItemBinding
+ ITimelineTimebar *GetTimebar() override;
+ void SetName(const Q3DStudio::CString &inName) override;
+ bool IsValidTransaction(EUserTransaction inTransaction) override;
+
+ // No properties
+ long GetPropertyCount() override { return 0; }
+ ITimelineItemProperty *GetProperty(long) override { return nullptr; }
+
+ // Eye/Lock toggles are not applicable
+ bool ShowToggleControls() const override { return false; }
+ bool IsLockedEnabled() const override { return false; }
+ bool IsVisibleEnabled() const override { return false; }
+
+ // Shy, Locked, Visible are not applicable
+ bool IsShy() const override { return false; }
+ void SetShy(bool) override {}
+ bool IsLocked() const override { return false; }
+ void SetLocked(bool) override {}
+ bool IsVisible() const override { return true; }
+ void SetVisible(bool) override {}
+ bool IsVisibilityControlled() const override { return false; }
+
+ // Keyframes, not applicable to a Slide
+ void InsertKeyframe() override {}
+ void DeleteAllChannelKeyframes() override {}
+ IKeyframe *GetKeyframeByTime(long) const override { return nullptr; }
+
+ // Keyframe manipulation, not applicable
+ bool HasDynamicKeyframes(long inTime) override
+ {
+ Q_UNUSED(inTime);
+ return false;
+ }
+ void SetDynamicKeyframes(long inTime, bool inDynamic) override
+ {
+ Q_UNUSED(inTime);
+ Q_UNUSED(inDynamic);
+ }
+
+protected:
+ std::shared_ptr<qt3dsdm::ISignalConnection>
+ m_Connection; // Callback when the Asset name changes
+
+ bool AmITimeParent() const override { return true; }
+};
+
+#endif // INCLUDED_SLIDE_TIMELINEITEM_BINDING_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp
new file mode 100644
index 00000000..ff7077aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp
@@ -0,0 +1,241 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "TimelineBreadCrumbProvider.h"
+#include "Core.h"
+
+// Link to data model
+#include "Doc.h"
+#include "StudioApp.h"
+#include "Cmd.h"
+#include "ResourceCache.h"
+#include "CColor.h"
+
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "CmdActivateSlide.h"
+
+using namespace qt3dsdm;
+
+//=============================================================================
+/**
+ * Constructor
+ */
+CTimelineBreadCrumbProvider::CTimelineBreadCrumbProvider(CDoc *inDoc)
+ : m_Doc(inDoc)
+{
+}
+
+//=============================================================================
+/**
+ */
+CTimelineBreadCrumbProvider::~CTimelineBreadCrumbProvider()
+{
+}
+
+//=============================================================================
+/**
+ * determine the color and text string for this breadcrumb
+ */
+static inline void FillBreadCrumb(SBreadCrumb &outBreadCrumb,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstance, CDoc *inDoc)
+{
+ // Get the MasterSlide Handle associated with inAsset
+ CClientDataModelBridge *theBridge = inDoc->GetStudioSystem()->GetClientDataModelBridge();
+ ISlideSystem *theSlideSystem = inDoc->GetStudioSystem()->GetSlideSystem();
+ Q3DStudio::CId theId = theBridge->GetGUID(inInstance);
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlide =
+ theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId));
+ Q_ASSERT(theMasterSlide.Valid()); // it should be valid because inAsset should be OBJTYPE_SCENE or
+ // non-library OBJTYPE_COMPONENT
+
+ // Get the active slide index of the master slide. Master Slide always has index 0
+ long theActiveIndex = theSlideSystem->GetActiveSlideIndex(theMasterSlide);
+ bool theIsMaster = (theActiveIndex == 0);
+
+ // Determine the color
+ outBreadCrumb.m_Color =
+ theIsMaster ? CColor(0, 0, 255) : CColor(0, 0, 0); // blue for master, black otherwise
+
+ // Determine the text string
+ outBreadCrumb.m_String = theBridge->GetName(inInstance).toQString();
+ outBreadCrumb.m_String += " (";
+ if (theIsMaster) {
+ outBreadCrumb.m_String += QObject::tr("Master");
+ } else {
+ Qt3DSDMSlideHandle theActiveSlide =
+ theSlideSystem->GetSlideByIndex(theMasterSlide, theActiveIndex);
+ Qt3DSDMInstanceHandle theInstanceHandle = theSlideSystem->GetSlideInstance(theActiveSlide);
+ Q_ASSERT(theInstanceHandle.Valid());
+ outBreadCrumb.m_String += theBridge->GetName(theInstanceHandle).toQString();
+ }
+ outBreadCrumb.m_String += ")";
+}
+
+//=============================================================================
+/**
+ * return the trail of breadcrumb.
+ * This constructs a list of the "time context tree" from Scene down to the current active time
+ * context.
+ * @param inRefresh true to refresh the list, false to get existing.
+ */
+CTimelineBreadCrumbProvider::TTrailList
+CTimelineBreadCrumbProvider::GetTrail(bool inRefresh /*= true */)
+{
+ if (inRefresh)
+ RefreshSlideList();
+
+ TTrailList theList;
+ for (size_t theIndex = 0; theIndex < m_BreadCrumbList.size(); ++theIndex) {
+ SBreadCrumb theBreadCrumb;
+ FillBreadCrumb(theBreadCrumb, m_BreadCrumbList[theIndex], m_Doc);
+ theList.push_back(theBreadCrumb);
+ }
+ return theList;
+}
+
+//=============================================================================
+/**
+ * switch current time context to the one 'represented' by the breadcrumbs.
+ * @param inTrailIndex index into the trail list
+ */
+void CTimelineBreadCrumbProvider::OnBreadCrumbClicked(long inTrailIndex)
+{
+ if (inTrailIndex >= 0 && inTrailIndex < (long)m_BreadCrumbList.size()) {
+ CCmdActivateSlide *theCmd = new CCmdActivateSlide(m_Doc, m_BreadCrumbList[inTrailIndex]);
+ theCmd->SetForceRefresh(false);
+ m_Doc->GetCore()->ExecuteCommand(theCmd, false);
+ }
+}
+
+QPixmap CTimelineBreadCrumbProvider::GetRootImage() const
+{
+ return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_scene.png");
+}
+
+QPixmap CTimelineBreadCrumbProvider::GetBreadCrumbImage() const
+{
+ return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_button.png");
+}
+
+QPixmap CTimelineBreadCrumbProvider::GetSeparatorImage() const
+{
+ return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_colon_button.png");
+}
+
+QPixmap CTimelineBreadCrumbProvider::GetActiveBreadCrumbImage() const
+{
+ return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_grey_button.png");
+}
+
+//=============================================================================
+/**
+ * Called when active time context is changed.
+ */
+void CTimelineBreadCrumbProvider::RefreshSlideList()
+{
+ ClearSlideList();
+
+ qt3dsdm::Qt3DSDMInstanceHandle theActiveRoot = m_Doc->GetActiveRootInstance();
+ if (!theActiveRoot.Valid())
+ return;
+ FillSlideList(theActiveRoot);
+}
+
+//=============================================================================
+/**
+ * Callback that inAsset has its name changed, fire off a signal to the UI control.
+ * All the assets' signals are connected to this object and we'll let the UI control check iterate
+ * through the list for changes and refresh.
+ * Alternative we can set up additional classes that listens to specific assets and only the asset
+ * affected refreshed. the former is easier for now.
+ */
+void CTimelineBreadCrumbProvider::OnNameDirty()
+{
+ Q_EMIT SigBreadCrumbUpdate();
+}
+
+void CTimelineBreadCrumbProvider::ClearSlideList()
+{
+ m_Connections.clear();
+ m_BreadCrumbList.clear();
+}
+
+//=============================================================================
+/**
+ * This will recurse up the time context tree, so that we can fill the list in a top-down (i.e
+ * Scene) first manner
+ */
+void CTimelineBreadCrumbProvider::FillSlideList(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ if (!inInstance.Valid())
+ return;
+
+ CClientDataModelBridge *theBridge = m_Doc->GetStudioSystem()->GetClientDataModelBridge();
+ ISlideSystem *theSlideSystem = m_Doc->GetStudioSystem()->GetSlideSystem();
+ Q3DStudio::CId theId = theBridge->GetGUID(inInstance);
+
+ // Recurse
+ FillSlideList(theBridge->GetParentComponent(inInstance));
+
+ m_BreadCrumbList.push_back(inInstance);
+
+ Qt3DSDMPropertyHandle theNameProp =
+ m_Doc->GetStudioSystem()->GetClientDataModelBridge()->GetNameProperty();
+ IStudioFullSystemSignalProvider *theEngine =
+ m_Doc->GetStudioSystem()->GetFullSystemSignalProvider();
+ std::function<void(Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)> theSetter(
+ std::bind(&CTimelineBreadCrumbProvider::OnNameDirty, this));
+
+ // Listen to name changes on the Asset
+ m_Connections.push_back(
+ theEngine->ConnectInstancePropertyValue(
+ std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void(
+ Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>,
+ std::placeholders::_1, std::placeholders::_2, inInstance,
+ theNameProp, theSetter)));
+
+ // Listen to name changes on the non-master Slides
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlide =
+ theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId));
+ long theSlideCount = (long)theSlideSystem->GetSlideCount(theMasterSlide);
+
+ for (long theIndex = 1; theIndex < theSlideCount; ++theIndex) {
+ Qt3DSDMSlideHandle theSlide = theSlideSystem->GetSlideByIndex(theMasterSlide, theIndex);
+ Qt3DSDMInstanceHandle theSlideInstance = theSlideSystem->GetSlideInstance(theSlide);
+ m_Connections.push_back(
+ theEngine->ConnectInstancePropertyValue(
+ std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void(
+ Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>,
+ std::placeholders::_1, std::placeholders::_2, theSlideInstance,
+ theNameProp, theSetter)));
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h
new file mode 100644
index 00000000..acc7be07
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INCLUDED_BREADCRUMBPROVIDER_H
+#define INCLUDED_BREADCRUMBPROVIDER_H 1
+
+#pragma once
+
+#include "IBreadCrumbProvider.h"
+#include "Qt3DSDMSignals.h"
+
+// Link to data model
+class CDoc;
+class CTimelineBreadCrumbProvider;
+
+//=============================================================================
+/**
+ * Bread crumb provider for displaying a trail of time contexts
+ */
+class CTimelineBreadCrumbProvider : public IBreadCrumbProvider
+{
+public:
+ CTimelineBreadCrumbProvider(CDoc *inDoc);
+ virtual ~CTimelineBreadCrumbProvider();
+
+ TTrailList GetTrail(bool inRefresh = true) override;
+ void OnBreadCrumbClicked(long inTrailIndex) override;
+
+ QPixmap GetRootImage() const override;
+ QPixmap GetBreadCrumbImage() const override;
+ QPixmap GetSeparatorImage() const override;
+ QPixmap GetActiveBreadCrumbImage() const override;
+
+ void RefreshSlideList();
+ void OnNameDirty();
+
+protected:
+ void ClearSlideList();
+ void FillSlideList(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+
+protected:
+ std::vector<qt3dsdm::Qt3DSDMInstanceHandle> m_BreadCrumbList;
+ CDoc *m_Doc;
+ // connections to the DataModel
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Connections;
+};
+
+#endif // INCLUDED_BREADCRUMBPROVIDER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp
new file mode 100644
index 00000000..c03867a7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineTranslationManager.h"
+#include "SlideTimelineItemBinding.h"
+#include "GroupTimelineItemBinding.h"
+#include "BehaviorTimelineItemBinding.h"
+#include "MaterialTimelineItemBinding.h"
+#include "ImageTimelineItemBinding.h"
+#include "PathAnchorPointTimelineItemBinding.h"
+#include "PathTimelineItemBinding.h"
+#include "LayerTimelineItemBinding.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "StudioObjectTypes.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "ClientDataModelBridge.h"
+
+using namespace qt3dsdm;
+
+CTimelineTranslationManager::CTimelineTranslationManager()
+{
+}
+
+CTimelineTranslationManager::~CTimelineTranslationManager()
+{
+ // clean up all bindings
+ Clear();
+}
+
+ITimelineItemBinding *CTimelineTranslationManager::GetOrCreate(Qt3DSDMInstanceHandle inInstance)
+{
+ ITimelineItemBinding *theBinding = GetBinding(inInstance);
+ if (!theBinding) {
+ Qt3DSDMTimelineItemBinding *theReturn = nullptr;
+
+ EStudioObjectType objType = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge()->GetObjectType(inInstance);
+
+ if (objType & OBJTYPE_IS_MATERIAL) {
+ theReturn = new CMaterialTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_IMAGE) {
+ theReturn = new CImageTimelineItemBinding(this, inInstance);
+ } else if (objType & (OBJTYPE_GROUP | OBJTYPE_COMPONENT)) {
+ theReturn = new CGroupTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_BEHAVIOR) {
+ theReturn = new CBehaviorTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_SLIDE) {
+ theReturn = new CSlideTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_PATHANCHORPOINT) {
+ theReturn = new CPathAnchorPointTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_PATH) {
+ theReturn = new CPathTimelineItemBinding(this, inInstance);
+ } else if (objType == OBJTYPE_LAYER) {
+ theReturn = new CLayerTimelineItemBinding(this, inInstance);
+ } else if (objType & (OBJTYPE_MODEL | OBJTYPE_TEXT | OBJTYPE_CAMERA | OBJTYPE_EFFECT
+ | OBJTYPE_LIGHT | OBJTYPE_RENDERPLUGIN | OBJTYPE_ALIAS
+ | OBJTYPE_SUBPATH))
+ theReturn = new Qt3DSDMTimelineItemBinding(this, inInstance);
+ else {
+ // Add support for additional DataModel types here.
+ Q_ASSERT(0);
+ }
+
+ m_InstanceBindingMap.insert({theReturn->GetInstanceHandle(), theReturn});
+ theBinding = theReturn;
+ }
+
+ return theBinding;
+}
+
+/**
+ * Clear all bindings, typically when a presentation is closed.
+ */
+void CTimelineTranslationManager::Clear()
+{
+ // clean up all bindings
+ m_InstanceBindingMap.clear();
+}
+
+/**
+ * @return the Binding object that corresponds to this instance.
+ */
+Qt3DSDMTimelineItemBinding *
+CTimelineTranslationManager::GetBinding(Qt3DSDMInstanceHandle inHandle) const
+{
+ auto it = m_InstanceBindingMap.find(inHandle);
+ if (it != m_InstanceBindingMap.end())
+ return it->second;
+
+ return nullptr;
+}
+
+CDoc *CTimelineTranslationManager::GetDoc() const
+{
+ return dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc());
+}
+
+CStudioSystem *CTimelineTranslationManager::GetStudioSystem() const
+{
+ return GetDoc()->GetStudioSystem();
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h
new file mode 100644
index 00000000..bbb3fa81
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2008 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINE_TRANSLATIONMANAGER_H
+#define TIMELINE_TRANSLATIONMANAGER_H
+
+#include "Qt3DSDMHandles.h"
+
+class ITimelineItemBinding;
+class Qt3DSDMTimelineItemBinding;
+class CDoc;
+
+namespace qt3dsdm {
+class CStudioSystem;
+}
+
+class CTimelineTranslationManager
+{
+public:
+ CTimelineTranslationManager();
+ ~CTimelineTranslationManager();
+
+ ITimelineItemBinding *GetOrCreate(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void Clear();
+
+ Qt3DSDMTimelineItemBinding *GetBinding(qt3dsdm::Qt3DSDMInstanceHandle inHandle) const;
+
+ qt3dsdm::CStudioSystem *GetStudioSystem() const;
+ CDoc *GetDoc() const;
+
+private:
+ std::map<qt3dsdm::Qt3DSDMInstanceHandle, Qt3DSDMTimelineItemBinding *> m_InstanceBindingMap;
+};
+
+#endif // INCLUDED_TIMELINE_TRANSLATIONMANAGER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h
new file mode 100644
index 00000000..7e4ea614
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef KEYFRAME_H
+#define KEYFRAME_H
+
+#include "Bindings/Qt3DSDMTimelineKeyframe.h"
+#include "RowTimeline.h"
+#include "RowTree.h"
+
+struct Keyframe
+{
+ Keyframe(long time, RowTimeline *propRow)
+ : time(time)
+ , rowProperty(propRow)
+ , rowMaster(propRow->parentRow())
+ , propertyType(propRow->rowTree()->propertyType())
+ {}
+
+ bool selected() const
+ {
+ return binding && binding->IsSelected();
+ }
+
+ long time;
+ QString propertyType;
+ RowTimeline *rowProperty = nullptr;
+ RowTimeline *rowMaster = nullptr;
+ Qt3DSDMTimelineKeyframe *binding = nullptr;
+ bool dynamic = false;
+};
+
+#endif // KEYFRAME_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp
new file mode 100644
index 00000000..9a12aab3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp
@@ -0,0 +1,589 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "KeyframeManager.h"
+#include "RowTree.h"
+#include "RowTimeline.h"
+#include "Keyframe.h"
+#include "RowManager.h"
+#include "TimelineGraphicsScene.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioClipboard.h"
+#include "CmdDataModelRemoveKeyframe.h"
+#include "CmdDataModelInsertKeyframe.h"
+#include "CmdDataModelChangeKeyframe.h"
+#include "ClientDataModelBridge.h"
+#include "Bindings/OffsetKeyframesCommandHelper.h"
+#include "Bindings/PasteKeyframesCommandHelper.h"
+#include "StudioPreferences.h"
+#include "Dialogs.h"
+#include "TimeEnums.h"
+
+using namespace qt3dsdm;
+
+KeyframeManager::KeyframeManager(TimelineGraphicsScene *scene) : m_scene(scene)
+{
+}
+
+KeyframeManager::~KeyframeManager()
+{
+ delete m_pasteKeyframeCommandHelper;
+}
+
+QList<Keyframe *> KeyframeManager::insertKeyframe(RowTimeline *row, long time,
+ bool selectInsertedKeyframes)
+{
+ QList<Keyframe *> addedKeyframes;
+ QList<RowTimeline *> propRows;
+ if (!row->rowTree()->isProperty()) {
+ const auto childRows = row->rowTree()->childRows();
+ for (const auto r : childRows) {
+ if (r->isProperty())
+ propRows.append(r->rowTimeline());
+ }
+ } else {
+ propRows.append(row);
+ }
+
+ if (!propRows.empty()) {
+ for (const auto &r : qAsConst(propRows)) {
+ Keyframe *keyframe = new Keyframe(time, r);
+ r->insertKeyframe(keyframe);
+ r->parentRow()->insertKeyframe(keyframe);
+ addedKeyframes.append(keyframe);
+ }
+
+ if (selectInsertedKeyframes && !addedKeyframes.empty()) {
+ deselectAllKeyframes();
+ selectKeyframes(addedKeyframes);
+ }
+ }
+
+ return addedKeyframes;
+}
+
+void KeyframeManager::selectKeyframe(Keyframe *keyframe)
+{
+ if (!m_selectedKeyframes.contains(keyframe)) {
+ m_selectedKeyframes.append(keyframe);
+
+ if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster))
+ m_selectedKeyframesMasterRows.append(keyframe->rowMaster);
+
+ keyframe->binding->SetSelected(true);
+ keyframe->rowMaster->putSelectedKeyframesOnTop();
+ keyframe->rowMaster->updateKeyframes();
+ }
+}
+
+void KeyframeManager::selectConnectedKeyframes(Keyframe *keyframe)
+{
+ // Select all keyframes of same master row at same time
+ const auto keyframes = keyframe->rowMaster->keyframes();
+ for (const auto k : keyframes) {
+ if (k->time == keyframe->time)
+ selectKeyframe(k);
+ }
+}
+
+void KeyframeManager::selectKeyframes(const QList<Keyframe *> &keyframes)
+{
+ for (const auto keyframe : keyframes) {
+ if (!m_selectedKeyframes.contains(keyframe)) {
+ m_selectedKeyframes.append(keyframe);
+
+ if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster))
+ m_selectedKeyframesMasterRows.append(keyframe->rowMaster);
+ }
+ }
+
+ for (auto keyframe : qAsConst(m_selectedKeyframes))
+ keyframe->binding->SetSelected(true);
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows)) {
+ row->putSelectedKeyframesOnTop();
+ row->updateKeyframes();
+ }
+}
+
+QList<Keyframe *> KeyframeManager::selectedKeyframes() const
+{
+ return m_selectedKeyframes;
+}
+
+// update bindings after selected keyframes are moved
+void KeyframeManager::commitMoveSelectedKeyframes()
+{
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ COffsetKeyframesCommandHelper h(*theDoc);
+
+ for (Keyframe *keyframe : qAsConst(m_selectedKeyframes))
+ keyframe->binding->UpdateKeyframesTime(&h, keyframe->time);
+}
+
+void KeyframeManager::selectKeyframesInRect(const QRectF &rect)
+{
+ deselectAllKeyframes();
+
+ RowTree *row = m_scene->rowManager()->getRowAtPos(QPointF(0, rect.top()));
+ while (row && row->y() < rect.bottom()) {
+ if (!row->locked()) {
+ const auto keyframes = row->rowTimeline()->getKeyframesInRange(rect);
+ for (auto keyframe : keyframes) {
+ if (!m_selectedKeyframes.contains(keyframe)) {
+ m_selectedKeyframes.append(keyframe);
+
+ if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster))
+ m_selectedKeyframesMasterRows.append(keyframe->rowMaster);
+ }
+ }
+ }
+ row = m_scene->rowManager()->getRowAtPos(QPointF(0, row->y() + row->size().height()));
+ }
+
+ for (auto keyframe : qAsConst(m_selectedKeyframes))
+ keyframe->binding->SetSelected(true);
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows)) {
+ row->putSelectedKeyframesOnTop();
+ row->updateKeyframes();
+ }
+}
+
+void KeyframeManager::deselectKeyframe(Keyframe *keyframe)
+{
+ if (m_selectedKeyframes.contains(keyframe)) {
+ m_selectedKeyframes.removeAll(keyframe);
+ keyframe->rowMaster->updateKeyframes();
+ m_selectedKeyframesMasterRows.removeAll(keyframe->rowMaster);
+
+ keyframe->binding->SetSelected(false);
+ keyframe->rowMaster->putSelectedKeyframesOnTop();
+ }
+}
+
+void KeyframeManager::deselectConnectedKeyframes(Keyframe *keyframe)
+{
+ // Deselect all keyframes of same master row at same time
+ const auto keyframes = keyframe->rowMaster->keyframes();
+ for (const auto k : keyframes) {
+ if (k->time == keyframe->time)
+ deselectKeyframe(k);
+ }
+}
+
+void KeyframeManager::deselectAllKeyframes()
+{
+ for (auto keyframe : qAsConst(m_selectedKeyframes))
+ keyframe->binding->SetSelected(false);
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows))
+ row->updateKeyframes();
+
+ m_selectedKeyframes.clear();
+ m_selectedKeyframesMasterRows.clear();
+}
+
+void KeyframeManager::deselectRowKeyframes(RowTree *row)
+{
+ const QList<Keyframe *> keyframes = row->rowTimeline()->keyframes();
+ for (const auto keyframe : keyframes) {
+ if (row->isProperty())
+ deselectKeyframe(keyframe);
+ else
+ deselectConnectedKeyframes(keyframe);
+ }
+}
+
+bool KeyframeManager::deleteSelectedKeyframes()
+{
+ if (!m_selectedKeyframes.empty()) {
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ CCmdDataModelRemoveKeyframe *cmd = new CCmdDataModelRemoveKeyframe(theDoc);
+ for (auto keyframe : qAsConst(m_selectedKeyframes)) {
+ cmd->addKeyframeHandles(keyframe->binding);
+
+ keyframe->rowMaster->removeKeyframe(keyframe);
+ keyframe->rowProperty->removeKeyframe(keyframe);
+
+ delete keyframe;
+ }
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows))
+ row->updateKeyframes();
+
+ m_selectedKeyframes.clear();
+ m_selectedKeyframesMasterRows.clear();
+
+ g_StudioApp.GetCore()->ExecuteCommand(cmd);
+ return true;
+ }
+
+ return false;
+}
+
+// delete all keyframes on a row
+void KeyframeManager::deleteKeyframes(RowTimeline *row, bool repaint)
+{
+ const auto keyframes = row->keyframes();
+ for (auto keyframe : keyframes) {
+ keyframe->rowMaster->removeKeyframe(keyframe);
+ keyframe->rowProperty->removeKeyframe(keyframe);
+
+ if (m_selectedKeyframes.contains(keyframe))
+ m_selectedKeyframes.removeAll(keyframe);
+
+ delete keyframe;
+ }
+
+ if (m_selectedKeyframesMasterRows.contains(row))
+ m_selectedKeyframesMasterRows.removeAll(row);
+
+ if (repaint)
+ row->updateKeyframes();
+}
+
+void KeyframeManager::copySelectedKeyframes()
+{
+ if (!m_selectedKeyframes.empty() && m_selectedKeyframesMasterRows.count() == 1) {
+ // Keyframe copying doesn't use clipboard, so clear it so that next time we paste
+ // it will paste the keyframes rather than the last object we copied
+ CStudioClipboard::ClearClipboard();
+
+ if (m_pasteKeyframeCommandHelper)
+ m_pasteKeyframeCommandHelper->Clear(); // clear out previously copied data
+ else
+ m_pasteKeyframeCommandHelper = new CPasteKeyframeCommandHelper();
+
+ // calc min copied frames time
+ long minTime = LONG_MAX;
+ for (auto keyframe : qAsConst(m_selectedKeyframes)) {
+ if (keyframe->time < minTime)
+ minTime = keyframe->time;
+ }
+
+ qt3dsdm::IAnimationCore *animationCore = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetAnimationCore();
+
+ for (auto keyframe : qAsConst(m_selectedKeyframes)) {
+ Qt3DSDMTimelineKeyframe *kf = keyframe->binding;
+ Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles;
+ kf->GetKeyframeHandles(theKeyframeHandles);
+ qt3dsdm::SGetOrSetKeyframeInfo info[3];
+ size_t infoCount = 0;
+ if (!theKeyframeHandles.empty()) {
+ switch (theKeyframeHandles.size()) {
+ case 1:
+ info[0] = setKeyframeInfo(theKeyframeHandles[0], *animationCore);
+ infoCount = 1;
+ break;
+ case 3:
+ info[0] = setKeyframeInfo(theKeyframeHandles[0], *animationCore);
+ info[1] = setKeyframeInfo(theKeyframeHandles[1], *animationCore);
+ info[2] = setKeyframeInfo(theKeyframeHandles[2], *animationCore);
+ infoCount = 3;
+ break;
+ default:
+ break;
+ }
+
+ float dt = Qt3DSDMTimelineKeyframe::GetTimeInSecs(kf->GetTime() - minTime);
+ qt3dsdm::Qt3DSDMAnimationHandle animation
+ = animationCore->GetAnimationForKeyframe(theKeyframeHandles[0]);
+ m_pasteKeyframeCommandHelper->AddKeyframeData(
+ animationCore->GetAnimationInfo(animation).m_Property, dt, info, infoCount);
+ }
+ }
+ }
+}
+
+qt3dsdm::SGetOrSetKeyframeInfo KeyframeManager::setKeyframeInfo(
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, qt3dsdm::IAnimationCore &inCore)
+{
+ qt3dsdm::TKeyframe theKeyframeData = inCore.GetKeyframeData(inKeyframe);
+ qt3dsdm::SEaseInEaseOutKeyframe keyframe =
+ qt3dsdm::get<qt3dsdm::SEaseInEaseOutKeyframe>(theKeyframeData);
+ bool isDynamic = false;
+ if (inCore.IsFirstKeyframe(inKeyframe)) {
+ isDynamic = inCore.GetAnimationInfo(inCore.GetAnimationForKeyframe(inKeyframe))
+ .m_DynamicFirstKeyframe;
+ }
+
+ return qt3dsdm::SGetOrSetKeyframeInfo(keyframe.m_KeyframeValue, keyframe.m_EaseIn,
+ keyframe.m_EaseOut, isDynamic);
+}
+
+void KeyframeManager::pasteKeyframes()
+{
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ if (m_pasteKeyframeCommandHelper && m_pasteKeyframeCommandHelper->HasCopiedKeyframes()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = theDoc->GetSelectedInstance();
+ if (theSelectedInstance.Valid()) {
+ long theCurrentViewTimeInMilliseconds = theDoc->GetCurrentViewTime();
+ CCmdDataModelInsertKeyframe *theInsertKeyframesCommand =
+ m_pasteKeyframeCommandHelper->GetCommand(theDoc, theCurrentViewTimeInMilliseconds,
+ theSelectedInstance);
+ if (theInsertKeyframesCommand)
+ g_StudioApp.GetCore()->ExecuteCommand(theInsertKeyframesCommand);
+ }
+ }
+}
+
+void KeyframeManager::moveSelectedKeyframes(long newTime)
+{
+ Keyframe *pressedKeyframe = m_scene->pressedKeyframe();
+
+ Q_ASSERT(pressedKeyframe);
+
+ // make sure the min-time keyframe doesn't go below zero
+ long minTime = getMinSelectedKeyframesTime();
+ if (pressedKeyframe->time - minTime > newTime)
+ newTime = pressedKeyframe->time - minTime;
+
+ for (auto keyframe : qAsConst(m_selectedKeyframes)) {
+ if (keyframe != pressedKeyframe)
+ keyframe->time = newTime - (pressedKeyframe->time - keyframe->time);
+ }
+ pressedKeyframe->time = newTime;
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows))
+ row->updateKeyframes();
+}
+
+long KeyframeManager::getMinSelectedKeyframesTime() const
+{
+ long minTime = LONG_MAX;
+ for (auto keyframe : qAsConst(m_selectedKeyframes)) {
+ if (keyframe->time < minTime)
+ minTime = keyframe->time;
+ }
+
+ return minTime;
+}
+
+// returns the distance between the pressed keyframe and the min-time keyframe in a multiselection
+long KeyframeManager::getPressedKeyframeOffset() const
+{
+ if (m_scene->pressedKeyframe())
+ return m_scene->pressedKeyframe()->time - getMinSelectedKeyframesTime();
+
+ return 0;
+}
+
+// selected keyframes belong to only one master row
+bool KeyframeManager::oneMasterRowSelected() const
+{
+ return m_selectedKeyframesMasterRows.count() == 1;
+}
+
+bool KeyframeManager::hasSelectedKeyframes() const
+{
+ return !m_selectedKeyframes.empty();
+}
+
+bool KeyframeManager::hasCopiedKeyframes() const
+{
+ return m_pasteKeyframeCommandHelper &&
+ m_pasteKeyframeCommandHelper->HasCopiedKeyframes();
+}
+
+bool KeyframeManager::hasDynamicKeyframes(RowTree *row) const
+{
+ const QList<Keyframe *> keyframes = row->rowTimeline()->keyframes();
+ for (const auto keyframe : keyframes) {
+ if (keyframe->binding->IsDynamic())
+ return true;
+ }
+ return false;
+}
+
+// IKeyframesManager interface
+void KeyframeManager::SetKeyframeTime(long inTime)
+{
+ g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(inTime, g_StudioApp.GetCore()->GetDoc(),
+ ASSETKEYFRAME, this);
+}
+
+void KeyframeManager::SetKeyframesDynamic(bool inDynamic)
+{
+ if (!hasSelectedKeyframes())
+ return;
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ IAnimationCore *animationCore = doc->GetStudioSystem()->GetAnimationCore();
+ CCmdDataModelChangeDynamicKeyframe *cmd = nullptr;
+
+ for (int i = 0; i < m_selectedKeyframes.size(); ++i) {
+ Qt3DSDMTimelineKeyframe *timelineKeyframe = m_selectedKeyframes[i]->binding;
+ Qt3DSDMTimelineKeyframe::TKeyframeHandleList keyframeHandles;
+ timelineKeyframe->GetKeyframeHandles(keyframeHandles);
+
+ for (size_t keyIndex = 0; keyIndex < keyframeHandles.size(); ++keyIndex) {
+ qt3dsdm::Qt3DSDMAnimationHandle animation(
+ animationCore->GetAnimationForKeyframe(keyframeHandles.at(keyIndex)));
+ if (!cmd)
+ cmd = new CCmdDataModelChangeDynamicKeyframe(doc, animation, inDynamic);
+ else
+ cmd->AddHandle(animation);
+ }
+ }
+
+ if (cmd)
+ g_StudioApp.GetCore()->ExecuteCommand(cmd);
+}
+
+void KeyframeManager::CommitChangedKeyframes()
+{
+ m_scene->resetPressedKeyframe();
+ commitMoveSelectedKeyframes();
+}
+
+void KeyframeManager::RollbackChangedKeyframes()
+{
+ m_scene->resetPressedKeyframe();
+
+ for (Keyframe *keyframe : qAsConst(m_selectedKeyframes))
+ keyframe->time = keyframe->binding->GetTime();
+
+ for (auto row : qAsConst(m_selectedKeyframesMasterRows))
+ row->updateKeyframes();
+}
+
+// IKeyframesManager interface
+bool KeyframeManager::HasSelectedKeyframes()
+{
+ return hasSelectedKeyframes();
+}
+
+bool KeyframeManager::HasDynamicKeyframes()
+{
+ return false; // Mahmoud_TODO: implement
+}
+
+bool KeyframeManager::CanPerformKeyframeCopy()
+{
+ return !m_selectedKeyframes.empty() && m_selectedKeyframesMasterRows.count() == 1;
+}
+
+bool KeyframeManager::CanPerformKeyframePaste()
+{
+ if (m_pasteKeyframeCommandHelper && m_pasteKeyframeCommandHelper->HasCopiedKeyframes()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance =
+ g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance();
+ if (theSelectedInstance.Valid())
+ return true;
+ }
+
+ return false;
+}
+
+void KeyframeManager::CopyKeyframes()
+{
+ copySelectedKeyframes();
+}
+
+bool KeyframeManager::RemoveKeyframes(bool inPerformCopy)
+{
+ Q_UNUSED(inPerformCopy)
+
+ return deleteSelectedKeyframes();
+}
+
+void KeyframeManager::PasteKeyframes()
+{
+ pasteKeyframes();
+}
+
+void KeyframeManager::SetKeyframeInterpolation()
+{
+ if (!hasSelectedKeyframes())
+ return;
+
+ float theEaseIn = 0;
+ float theEaseOut = 0;
+ if (CStudioPreferences::GetInterpolation())
+ theEaseIn = theEaseOut = 100;
+
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ IAnimationCore *theAnimationCore = theDoc->GetStudioSystem()->GetAnimationCore();
+
+ if (!m_selectedKeyframes.empty()) {
+ Qt3DSDMTimelineKeyframe *theTimelineKeyframe = m_selectedKeyframes.front()->binding;
+ Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles;
+ theTimelineKeyframe->GetKeyframeHandles(theKeyframeHandles);
+ TKeyframe theKeyframeData = theAnimationCore->GetKeyframeData(theKeyframeHandles[0]);
+ GetEaseInOutValues(theKeyframeData, theEaseIn, theEaseOut);
+ }
+
+ if (g_StudioApp.GetDialogs()->PromptForKeyframeInterpolation(theEaseIn, theEaseOut)) {
+ // Note: Having "editor" variable here is important as its destructor
+ // creates proper transaction
+ Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Set Keyframe Interpolation"),
+ __FILE__, __LINE__);
+ for (Keyframe *keyframe : qAsConst(m_selectedKeyframes)) {
+ Qt3DSDMTimelineKeyframe *theTimelineKeyframe = keyframe->binding;
+ Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles;
+ theTimelineKeyframe->GetKeyframeHandles(theKeyframeHandles);
+ for (size_t i = 0; i < theKeyframeHandles.size(); ++i) {
+ TKeyframe theKeyframeData =
+ theAnimationCore->GetKeyframeData(theKeyframeHandles[i]);
+ SetEaseInOutValues(theKeyframeData, theEaseIn, theEaseOut);
+ theAnimationCore->SetKeyframeData(theKeyframeHandles[i], theKeyframeData);
+ }
+ }
+ }
+}
+
+void KeyframeManager::DeselectAllKeyframes()
+{
+ deselectAllKeyframes();
+}
+
+void KeyframeManager::SetChangedKeyframes()
+{
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle selectedInstance = theDoc->GetSelectedInstance();
+ if (selectedInstance.Valid()) {
+ using namespace Q3DStudio;
+ Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Set Changed Keyframes"),
+ __FILE__, __LINE__);
+ CStudioSystem *theStudioSystem = theDoc->GetStudioSystem();
+ // Get all animated properties.
+ TPropertyHandleList properties;
+ theStudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(selectedInstance,
+ properties);
+ for (size_t i = 0; i < properties.size(); ++i) {
+ if (theStudioSystem->GetAnimationSystem()->IsPropertyAnimated(
+ selectedInstance, properties[i])) {
+ editor->KeyframeProperty(selectedInstance, properties[i], true);
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h
new file mode 100644
index 00000000..9c160687
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef KEYFRAMEMANAGER_H
+#define KEYFRAMEMANAGER_H
+
+#include "IKeyframesManager.h"
+#include "Qt3DSDMAnimation.h"
+#include <QtCore/qlist.h>
+#include <StudioObjectTypes.h>
+
+class RowTimeline;
+class RowTree;
+class TimelineGraphicsScene;
+class CPasteKeyframeCommandHelper;
+struct Keyframe;
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsSceneContextMenuEvent)
+QT_FORWARD_DECLARE_CLASS(QRectF)
+
+class KeyframeManager : public IKeyframesManager
+{
+public:
+ KeyframeManager(TimelineGraphicsScene *m_scene);
+ virtual ~KeyframeManager() override;
+
+ QList<Keyframe *> insertKeyframe(RowTimeline *row, long time,
+ bool selectInsertedKeyframes = true);
+ void selectKeyframe(Keyframe *keyframe);
+ void selectConnectedKeyframes(Keyframe *keyframe);
+ void selectKeyframesInRect(const QRectF &rect);
+ void selectKeyframes(const QList<Keyframe *> &keyframes);
+ QList<Keyframe *> selectedKeyframes() const;
+ void deselectKeyframe(Keyframe *keyframe);
+ void deselectConnectedKeyframes(Keyframe *keyframe);
+ void deselectAllKeyframes();
+ void deselectRowKeyframes(RowTree *row);
+ void deleteKeyframes(RowTimeline *row, bool repaint = true);
+ void copySelectedKeyframes();
+ void pasteKeyframes();
+ void moveSelectedKeyframes(long newTime);
+ void commitMoveSelectedKeyframes();
+ bool deleteSelectedKeyframes();
+ bool oneMasterRowSelected() const;
+ bool hasSelectedKeyframes() const;
+ bool hasCopiedKeyframes() const;
+ bool hasDynamicKeyframes(RowTree *row) const;
+
+ // IKeyframesManager interface
+ void SetKeyframeTime(long inTime) override;
+ void SetKeyframesDynamic(bool inDynamic) override;
+ void CommitChangedKeyframes() override;
+ void RollbackChangedKeyframes() override;
+ bool HasSelectedKeyframes() override;
+ bool HasDynamicKeyframes() override;
+ bool CanPerformKeyframeCopy() override;
+ bool CanPerformKeyframePaste() override;
+ void CopyKeyframes() override;
+ bool RemoveKeyframes(bool inPerformCopy) override;
+ void PasteKeyframes() override;
+ void SetKeyframeInterpolation() override;
+ void DeselectAllKeyframes() override;
+ void SetChangedKeyframes() override;
+ long getPressedKeyframeOffset() const;
+
+private:
+ qt3dsdm::SGetOrSetKeyframeInfo setKeyframeInfo(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ qt3dsdm::IAnimationCore &inCore);
+ long getMinSelectedKeyframesTime() const;
+
+ CPasteKeyframeCommandHelper *m_pasteKeyframeCommandHelper = nullptr;
+ TimelineGraphicsScene *m_scene;
+ QList<Keyframe *> m_selectedKeyframes;
+ QList<RowTimeline *> m_selectedKeyframesMasterRows;
+};
+
+#endif // KEYFRAMEMANAGER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp
new file mode 100644
index 00000000..50eff996
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowManager.h"
+#include "RowTree.h"
+#include "TimelineGraphicsScene.h"
+#include "Ruler.h"
+#include "TreeHeader.h"
+#include "KeyframeManager.h"
+#include "Keyframe.h"
+#include "StudioObjectTypes.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "Bindings/ITimelineTimebar.h"
+#include "Bindings/Qt3DSDMTimelineKeyframe.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioObjectTypes.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+
+#include <QtWidgets/qgraphicslinearlayout.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qtimer.h>
+
+RowManager::RowManager(TimelineGraphicsScene *scene, QGraphicsLinearLayout *layoutLabels,
+ QGraphicsLinearLayout *layoutTimeline)
+ : m_scene(scene)
+ , m_layoutTree(layoutLabels)
+ , m_layoutTimeline(layoutTimeline)
+{
+
+}
+
+RowManager::~RowManager()
+{
+ finalizeRowDeletions();
+}
+
+void RowManager::recreateRowsFromBinding(ITimelineItemBinding *rootBinding)
+{
+ removeAllRows();
+ createRowsFromBindingRecursive(rootBinding);
+}
+
+void RowManager::removeAllRows()
+{
+ m_scene->keyframeManager()->deselectAllKeyframes();
+ clearSelection();
+
+ // delete rows
+ RowTree *row_i;
+ for (int i = m_layoutTree->count() - 1; i >= 1; --i) {
+ row_i = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem());
+ m_layoutTree->removeAt(i);
+ m_layoutTimeline->removeAt(i);
+ delete row_i; // this will also delete the timeline row
+ }
+}
+
+RowTree *RowManager::createRowFromBinding(ITimelineItemBinding *binding, RowTree *parentRow,
+ int index)
+{
+ RowTree *newRow = createRow(binding->GetTimelineItem()->GetObjectType(), parentRow,
+ binding->GetTimelineItem()->GetName().toQString(),
+ QString(), index);
+
+ // connect the new row and its binding
+ binding->setRowTree(newRow);
+ newRow->setBinding(binding);
+
+ // hide if material container
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ if (bridge->isMaterialContainer(newRow->instance())) {
+ newRow->setVisible(false);
+ newRow->rowTimeline()->setVisible(false);
+ }
+
+ // set row start/end time & color
+ ITimelineTimebar *timebar = binding->GetTimelineItem()->GetTimebar();
+ RowTimeline *rowTimeline = newRow->rowTimeline();
+ rowTimeline->clearBoundChildren();
+ rowTimeline->setStartTime(timebar->GetStartTime());
+ rowTimeline->setEndTime(timebar->GetEndTime());
+ rowTimeline->setBarColor(timebar->GetTimebarColor());
+
+ // create property rows
+ for (int i = 0; i < binding->GetPropertyCount(); i++) {
+ ITimelineItemProperty *prop_i = binding->GetProperty(i);
+ RowTree *propRow = getOrCreatePropertyRow(newRow, prop_i->GetName().toQString());
+
+ // connect the property row and its binding
+ prop_i->setRowTree(propRow);
+ propRow->setPropBinding(prop_i);
+
+ // add keyframes
+ for (int j = 0; j < prop_i->GetKeyframeCount(); j++) {
+ Qt3DSDMTimelineKeyframe *kf
+ = static_cast<Qt3DSDMTimelineKeyframe *>(prop_i->GetKeyframeByIndex(j));
+
+ QList<Keyframe *> addedKeyframes
+ = m_scene->keyframeManager()->insertKeyframe(propRow->rowTimeline(),
+ kf->GetTime(), false);
+
+ Keyframe *kfUI = addedKeyframes.at(0);
+ kf->setUI(kfUI);
+ kfUI->binding = kf;
+ kfUI->dynamic = kf->IsDynamic();
+ }
+ }
+
+ updateRulerDuration();
+
+ return newRow;
+}
+
+void RowManager::createRowsFromBindingRecursive(ITimelineItemBinding *binding, RowTree *parentRow)
+{
+ auto instance = static_cast<Qt3DSDMTimelineItemBinding *>(binding)->GetInstance();
+
+ RowTree *newRow = createRowFromBinding(binding, parentRow);
+ // create child rows recursively
+ const QList<ITimelineItemBinding *> children = binding->GetChildren();
+ for (auto child : children)
+ createRowsFromBindingRecursive(child, newRow);
+}
+
+RowTree *RowManager::getOrCreatePropertyRow(RowTree *masterRow, const QString &propType, int index)
+{
+ RowTree *propertyRow = masterRow->getPropertyRow(propType);
+ if (!propertyRow)
+ propertyRow = createRow(OBJTYPE_UNKNOWN, masterRow, {}, propType, index);
+
+ propertyRow->updateLock(masterRow->locked());
+
+ return propertyRow;
+}
+
+RowTree *RowManager::createRow(EStudioObjectType rowType, RowTree *parentRow, const QString &label,
+ const QString &propType, int index)
+{
+ if (parentRow && parentRow->isProperty()) {
+ qWarning() << __FUNCTION__ << "Property row cannot have children. No row added.";
+ } else {
+ // If the row doesnt have a parent, insert it under the scene (first row is the tree header)
+ if (!parentRow && rowType != OBJTYPE_SCENE && m_layoutTree->count() > 1)
+ parentRow = static_cast<RowTree *>(m_layoutTree->itemAt(1));
+
+ RowTree *rowTree = nullptr;
+
+ if (!propType.isEmpty()) // property row
+ rowTree = new RowTree(m_scene, propType);
+ else
+ rowTree = new RowTree(m_scene, rowType, label);
+
+ if (parentRow) {
+ if (index != -1)
+ parentRow->addChildAt(rowTree, index);
+ else
+ parentRow->addChild(rowTree);
+ } else {
+ // root element, no parent
+ m_layoutTree->insertItem(1, rowTree);
+ m_layoutTimeline->insertItem(1, rowTree->rowTimeline());
+ }
+
+ return rowTree;
+ }
+
+ return nullptr;
+}
+
+RowTree *RowManager::getRowAtPos(const QPointF &scenePos) const
+{
+ const QList<QGraphicsItem *> items = m_scene->items(scenePos);
+
+ for (auto item : items) {
+ if (item->type() == TimelineItem::TypeRowTree)
+ return static_cast<RowTree *>(item);
+ }
+
+ return nullptr;
+}
+
+// Call this to select/unselect row, affecting bindings
+void RowManager::selectRow(RowTree *row, bool multiSelect)
+{
+ if (!row) {
+ g_StudioApp.GetCore()->GetDoc()->DeselectAllItems();
+ return;
+ }
+
+ if (row->locked())
+ return;
+
+ if (row->isProperty())
+ row = row->parentRow();
+
+ if (multiSelect && m_selectedRows.size() > 0) {
+ // Do not allow singular object types into multiselection
+ if ((row->objectType() | m_selectedRows[0]->objectType()) & OBJTYPE_IS_SINGULAR)
+ return;
+ }
+
+ Qt3DSDMTimelineItemBinding *binding
+ = static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding());
+ if (binding)
+ binding->SetSelected(multiSelect);
+}
+
+// Call this to update row selection UI status
+void RowManager::setRowSelection(RowTree *row, bool selected)
+{
+ if (!row)
+ return;
+
+ if (selected) {
+ if (!m_selectedRows.contains(row))
+ m_selectedRows.append(row);
+ row->setState(InteractiveTimelineItem::Selected);
+ // Expand parents if not expanded
+ QPointer<RowTree> pRow = row->parentRow();
+ if (!pRow.isNull()) {
+ QTimer::singleShot(0, [this, pRow]() {
+ if (!pRow.isNull())
+ ensureRowExpandedAndVisible(pRow, false);
+ });
+ }
+ } else {
+ m_selectedRows.removeAll(row);
+ row->setState(InteractiveTimelineItem::Normal);
+ }
+}
+
+// Call this to clear all selections UI status
+void RowManager::clearSelection()
+{
+ for (auto row : qAsConst(m_selectedRows))
+ row->setState(InteractiveTimelineItem::Normal);
+ m_selectedRows.clear();
+}
+
+// Updates duration of ruler
+// When you don't want to update max duration (so width of timeline, scrollbar)
+// set updateMaxDuration to false.
+void RowManager::updateRulerDuration(bool updateMaxDuration)
+{
+ long duration = 0;
+ long maxDuration = 0; // for setting correct size for the view so scrollbars appear correctly
+ if (m_layoutTree->count() > 1) {
+ auto rootRow = static_cast<RowTree *>(m_layoutTree->itemAt(1)->graphicsItem());
+ bool isComponent = rootRow->objectType() == OBJTYPE_COMPONENT;
+ for (int i = 1; i < m_layoutTree->count(); ++i) {
+ RowTree *row_i = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem());
+ long dur_i = row_i->rowTimeline()->getEndTime();
+
+ if (((isComponent && i != 1) || row_i->objectType() == OBJTYPE_LAYER)
+ && dur_i > duration) {
+ duration = dur_i;
+ }
+
+ if (dur_i > maxDuration)
+ maxDuration = dur_i;
+ }
+ rootRow->rowTimeline()->setEndTime(duration);
+ }
+
+ m_scene->ruler()->setDuration(duration);
+
+ if (updateMaxDuration)
+ m_scene->ruler()->setMaxDuration(maxDuration);
+}
+
+void RowManager::updateFiltering(RowTree *row)
+{
+ if (!row) // update all rows
+ row = static_cast<RowTree *>(m_layoutTree->itemAt(1));
+ updateRowFilterRecursive(row);
+}
+
+void RowManager::updateRowFilterRecursive(RowTree *row)
+{
+ row->updateFilter();
+
+ if (!row->empty()) {
+ const auto childRows = row->childRows();
+ for (auto child : childRows)
+ updateRowFilterRecursive(child);
+ row->updateArrowVisibility();
+ }
+}
+
+void RowManager::deleteRow(RowTree *row)
+{
+ if (row && row->objectType() != OBJTYPE_SCENE) {
+ if (row->parentRow())
+ row->parentRow()->removeChild(row);
+
+ deleteRowRecursive(row, true);
+ }
+}
+
+void RowManager::finalizeRowDeletions()
+{
+ for (auto row : qAsConst(m_deletedRows)) {
+ // If the row has been reparented, no need to delete it
+ if (!row->parentRow())
+ deleteRowRecursive(row, false);
+ }
+ m_deletedRows.clear();
+}
+
+void RowManager::deleteRowRecursive(RowTree *row, bool deferChildRows)
+{
+ if (!row->childProps().empty()) {
+ const auto childProps = row->childProps();
+ for (auto child : childProps)
+ deleteRowRecursive(child, false);
+ }
+
+ if (!row->childRows().empty()) {
+ const auto childRows = row->childRows();
+ for (auto child : childRows) {
+ if (deferChildRows) {
+ // Let's not delete child rows just yet, there may be a pending move for them.
+ // This happens when the same transaction contains parent deletion and child row
+ // move, such as ungrouping items.
+ child->setParentRow(nullptr);
+ m_deletedRows.append(child);
+ } else {
+ deleteRowRecursive(child, false);
+ }
+ }
+ }
+
+ m_selectedRows.removeAll(row);
+ m_deletedRows.removeAll(row); // Row actually deleted, remove it from pending deletes
+
+ m_scene->keyframeManager()->deleteKeyframes(row->rowTimeline(), false);
+
+ if (row->getBinding())
+ static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding())->setRowTree(nullptr);
+
+ delete row;
+}
+
+RowTree *RowManager::selectedRow() const
+{
+ if (m_selectedRows.size() == 1)
+ return m_selectedRows.first();
+ return nullptr;
+}
+
+bool RowManager::isComponentRoot() const
+{
+ if (m_layoutTree->count() > 1) {
+ RowTree *root = static_cast<RowTree *>(m_layoutTree->itemAt(1)->graphicsItem());
+ return root->objectType() == OBJTYPE_COMPONENT;
+ }
+ return false;
+}
+
+bool RowManager::isRowSelected(RowTree *row) const
+{
+ return m_selectedRows.contains(row);
+}
+
+QVector<RowTree *> RowManager::selectedRows() const
+{
+ return m_selectedRows;
+}
+
+void RowManager::ensureRowExpandedAndVisible(RowTree *row, bool forceChildUpdate) const
+{
+ RowTree *parentRow = row;
+ while (parentRow) {
+ parentRow->updateExpandStatus(parentRow->expandHidden()
+ ? RowTree::ExpandState::HiddenExpanded
+ : RowTree::ExpandState::Expanded, false,
+ forceChildUpdate);
+ parentRow = parentRow->parentRow();
+ }
+}
+
+bool RowManager::isSingleSelected() const
+{
+ return m_selectedRows.size() == 1;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h
new file mode 100644
index 00000000..3a018577
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWMANAGER_H
+#define ROWMANAGER_H
+
+#include "RowTypes.h"
+#include "StudioObjectTypes.h"
+#include <QtCore/qstring.h>
+
+class TimelineGraphicsScene;
+class RowTree;
+class RowTimeline;
+class ITimelineItemBinding;
+class Qt3DSDMTimelineItemBinding;
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+
+class RowManager
+{
+public:
+ RowManager(TimelineGraphicsScene *scene, QGraphicsLinearLayout *layoutLabels,
+ QGraphicsLinearLayout *layoutTimeline);
+ ~RowManager();
+
+ void selectRow(RowTree *row, bool multiSelect = false);
+ void setRowSelection(RowTree *row, bool selected);
+ void deleteRow(RowTree *row);
+ void finalizeRowDeletions();
+ void clearSelection();
+ void updateFiltering(RowTree *rowTree = nullptr);
+ void recreateRowsFromBinding(ITimelineItemBinding *rootBinding);
+ void updateRulerDuration(bool updateMaxDuration = true);
+ bool isSingleSelected() const;
+ RowTree *createRowFromBinding(ITimelineItemBinding *binding, RowTree *parentRow = nullptr,
+ int index = -1);
+ RowTree *getOrCreatePropertyRow(RowTree *masterRow, const QString &propType, int index = -1);
+ RowTree *createRow(EStudioObjectType rowType, RowTree *parentRow = nullptr,
+ const QString &label = QString(), const QString &propType = QString(),
+ int index = -1);
+ RowTree *getRowAtPos(const QPointF &scenePos) const;
+ RowTree *selectedRow() const;
+ bool isComponentRoot() const;
+ bool isRowSelected(RowTree *row) const;
+ QVector<RowTree *> selectedRows() const;
+ void ensureRowExpandedAndVisible(RowTree *row, bool forceChildUpdate) const;
+
+private:
+ void deleteRowRecursive(RowTree *row, bool deferChildRows);
+ void updateRowFilterRecursive(RowTree *row);
+ void createRowsFromBindingRecursive(ITimelineItemBinding *binding,
+ RowTree *parentRow = nullptr);
+ void removeAllRows();
+
+ QVector<RowTree *> m_selectedRows;
+ TimelineGraphicsScene *m_scene;
+ QGraphicsLinearLayout *m_layoutTree;
+ QGraphicsLinearLayout *m_layoutTimeline;
+ QVector<RowTree *> m_deletedRows;
+};
+
+#endif // ROWMANAGER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp
new file mode 100644
index 00000000..3e96f2dc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp
@@ -0,0 +1,451 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowMover.h"
+#include "RowTree.h"
+#include "RowManager.h"
+#include "TimelineGraphicsScene.h"
+#include "TimelineConstants.h"
+#include "StudioPreferences.h"
+
+#include <QtGui/qpainter.h>
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qgraphicsitem.h>
+#include <QtWidgets/qgraphicslinearlayout.h>
+
+RowMover::RowMover(TimelineGraphicsScene *scene)
+ : TimelineItem()
+ , m_scene(scene)
+{
+ setZValue(99);
+ setGeometry(0, 0, TimelineConstants::TREE_MAX_W, 10);
+
+ m_autoExpandTimer.setSingleShot(true);
+ connect(&m_autoExpandTimer, &QTimer::timeout, [this]() {
+ if (m_rowAutoExpand) {
+ m_rowAutoExpand->updateExpandStatus(RowTree::ExpandState::Expanded, true);
+ // Update RowMover after the expansion. The +50 below is just a small margin to ensure
+ // correct row heights before updateTargetRowLater is called.
+ QTimer::singleShot(TimelineConstants::EXPAND_ANIMATION_DURATION + 50, [this]() {
+ if (updateTargetRowLater)
+ updateTargetRowLater();
+ });
+ }
+ });
+}
+
+void RowMover::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ static const QPolygon polygon({QPoint(0, 0), QPoint(0, 3), QPoint(7, 3), QPoint(7, 1),
+ QPoint(int(TimelineConstants::TREE_BOUND_W), 1),
+ QPoint(int(TimelineConstants::TREE_BOUND_W), 0)});
+ painter->setPen(QPen(CStudioPreferences::timelineRowMoverColor(), 1));
+ painter->setBrush(CStudioPreferences::timelineRowMoverColor());
+ painter->drawConvexPolygon(polygon);
+}
+
+RowTree *RowMover::insertionTarget() const
+{
+ return m_insertionTarget.data();
+}
+
+RowTree *RowMover::insertionParent() const
+{
+ return m_insertionParent;
+}
+
+QVector<RowTree *> RowMover::sourceRows() const
+{
+ return m_sourceRows;
+}
+
+void RowMover::removeSourceRow(RowTree *row)
+{
+ m_sourceRows.remove(m_sourceRows.indexOf(row));
+}
+
+bool RowMover::shouldDeleteAfterMove() const
+{
+ return m_deleteAfterMove;
+}
+
+void RowMover::resetInsertionParent(RowTree *newParent)
+{
+ if (m_insertionParent) {
+ m_insertionParent->setDnDState(RowTree::DnDState::None, RowTree::DnDState::Parent);
+ m_insertionParent = nullptr;
+ }
+
+ if (newParent) {
+ m_insertionParent = newParent;
+ m_insertionParent->setDnDState(RowTree::DnDState::Parent, RowTree::DnDState::None);
+ } else {
+ m_insertionTarget = nullptr;
+ }
+}
+
+bool RowMover::isActive() const
+{
+ return m_active;
+}
+
+void RowMover::start(const QVector<RowTree *> &rows)
+{
+ m_deleteAfterMove = false;
+ m_sourceRows.clear();
+ if (!rows.isEmpty()) {
+ // Remove rows that have an ancestor included in the selection or ones that are of
+ // invalid type for moving
+ for (auto candidateRow : rows) {
+ bool omit = !candidateRow->draggable();
+ if (!omit) {
+ for (auto checkRow : rows) {
+ if (candidateRow->isDecendentOf(checkRow))
+ omit = true;
+ }
+ if (!omit)
+ m_sourceRows.append(candidateRow);
+ }
+ }
+ if (!m_sourceRows.isEmpty()) {
+ m_active = true;
+ for (auto row : qAsConst(m_sourceRows))
+ row->setDnDState(RowTree::DnDState::Source, RowTree::DnDState::None, true);
+ qApp->setOverrideCursor(Qt::ClosedHandCursor);
+ }
+ }
+}
+
+void RowMover::end(bool force)
+{
+ if (m_active || force) {
+ m_active = false;
+ for (auto row : qAsConst(m_sourceRows))
+ row->setDnDState(RowTree::DnDState::None, RowTree::DnDState::Any, true);
+
+ m_sourceRows.clear();
+
+ if (!m_insertionTarget.isNull())
+ m_insertionTarget->setDnDState(RowTree::DnDState::None);
+
+ setVisible(false);
+ resetInsertionParent();
+ updateTargetRowLater = {};
+ qApp->changeOverrideCursor(Qt::ArrowCursor);
+ qApp->restoreOverrideCursor();
+
+ m_autoExpandTimer.stop();
+ }
+}
+
+void RowMover::updateState(int depth, double y)
+{
+ setPos(24 + depth * TimelineConstants::ROW_DEPTH_STEP, y);
+ setVisible(true);
+}
+
+bool RowMover::isNextSiblingRow(RowTree *rowMain, RowTree *rowSibling) const
+{
+ // order matters, rowSibling is below rowMain
+ return rowMain->parentRow() == rowSibling->parentRow()
+ && rowSibling->index() - rowMain->index() == 1;
+}
+
+bool RowMover::sourceRowsHasMaster() const
+{
+ for (auto sourceRow : qAsConst(m_sourceRows)) {
+ if (sourceRow->isMaster() && sourceRow->objectType() != OBJTYPE_LAYER)
+ return true;
+ }
+
+ return false;
+}
+
+bool RowMover::isSourceRowsDescendant(RowTree *row) const
+{
+ if (row) {
+ for (auto sourceRow : qAsConst(m_sourceRows)) {
+ if (row == sourceRow || row->isDecendentOf(sourceRow))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// rowType parameter is used to highlight the target row when RowMover is not active,
+// i.e. when dragging from project or basic objects palettes
+void RowMover::updateTargetRow(const QPointF &scenePos, EStudioObjectType rowType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType,
+ bool firstTry)
+{
+ // DnD a presentation / Qml stream from the project panel (to set it as a subpresentation)
+ if (rowType & (OBJTYPE_PRESENTATION | OBJTYPE_QML_STREAM | OBJTYPE_MATERIALDATA)) {
+ if (!m_insertionTarget.isNull())
+ m_insertionTarget->setDnDState(RowTree::DnDState::None, RowTree::DnDState::SP_TARGET);
+
+ RowTree *rowAtMouse = m_scene->rowManager()->getRowAtPos(scenePos);
+ if (rowAtMouse) {
+ // m_insertionTarget will go through CFileDropSource::ValidateTarget() which will
+ // filter out invalid drop rows
+ m_insertionTarget = rowAtMouse;
+ m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild;
+
+ if (rowType == OBJTYPE_MATERIALDATA) {
+ if (rowAtMouse->objectType() & OBJTYPE_IS_MATERIAL)
+ m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET);
+ } else {
+ if (rowAtMouse->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)
+ && !rowAtMouse->isDefaultMaterial()) {
+ m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET);
+ }
+ }
+ m_rowAutoExpand = rowAtMouse;
+ m_autoExpandTimer.start(TimelineConstants::AUTO_EXPAND_TIME);
+ } else {
+ m_rowAutoExpand = nullptr;
+ m_autoExpandTimer.stop();
+ }
+ return;
+ } else if (fileType == Q3DStudio::DocumentEditorFileType::Image) { // DnD an image
+ if (!m_insertionTarget.isNull())
+ m_insertionTarget->setDnDState(RowTree::DnDState::None, RowTree::DnDState::SP_TARGET);
+ // if draggin in the middle of a layer, mat, or image row, set the image as a texture.
+ RowTree *rowAtMouse = m_scene->rowManager()->getRowAtPos(scenePos);
+ if (rowAtMouse) {
+ double y = rowAtMouse->mapFromScene(scenePos).y();
+ if (y > TimelineConstants::ROW_H * .25 && y < TimelineConstants::ROW_H * .75) {
+ if (rowAtMouse->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)
+ && !rowAtMouse->isDefaultMaterial()) {
+ m_rowAutoExpand = nullptr;
+ m_autoExpandTimer.stop();
+ setVisible(false);
+ resetInsertionParent();
+
+ m_insertionTarget = rowAtMouse;
+ m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET);
+ m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild;
+ return;
+ }
+ }
+ }
+ }
+
+ EStudioObjectType theRowType = rowType;
+ if (theRowType == OBJTYPE_UNKNOWN && m_sourceRows.size() == 1)
+ theRowType = m_sourceRows[0]->objectType();
+
+ // row will be inserted just below rowInsert1 and just above rowInsert2 (if it exists)
+ RowTree *rowInsert1 = m_scene->rowManager()
+ ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H * -.5));
+ RowTree *rowInsert2 = m_scene->rowManager()
+ ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H * .5));
+
+ // If on top half of the top row, adjust half row down, as we can never insert anything
+ // above the top row
+ if (!rowInsert1 && rowInsert2 && rowInsert2->index() == 0) {
+ rowInsert1 = m_scene->rowManager()->getRowAtPos(scenePos);
+ rowInsert2 = m_scene->rowManager()
+ ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H));
+ }
+
+ bool valid = rowInsert1 != nullptr;
+
+ if (valid) {
+ // if dragging over a property or a parent of a property, move to the first row
+ // after the property
+ if (rowInsert1->isProperty()) {
+ rowInsert1 = rowInsert1->parentRow()->childProps().last();
+ rowInsert2 = m_scene->rowManager()
+ ->getRowAtPos(QPointF(0, rowInsert1->y() + TimelineConstants::ROW_H));
+ } else if (rowInsert1->hasPropertyChildren() && rowInsert1->expanded()) {
+ rowInsert1 = rowInsert1->childProps().last();
+ rowInsert2 = m_scene->rowManager()
+ ->getRowAtPos(QPointF(0, rowInsert1->y() + TimelineConstants::ROW_H));
+ }
+
+ // calc insertion depth
+ bool inAComponent = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(1))->isComponent();
+ int depth;
+ int depthMin = 2;
+ if (rowInsert2)
+ depthMin = rowInsert2->depth();
+ else if (theRowType != OBJTYPE_LAYER && !inAComponent)
+ depthMin = 3;
+
+ int depthMax = rowInsert1->depth();
+ bool srcHasMaster = sourceRowsHasMaster();
+ if (!rowInsert1->locked() && rowInsert1->isContainer() && !m_sourceRows.contains(rowInsert1)
+ // prevent insertion a master row under a non-master unless under a component root
+ && (!(srcHasMaster && !rowInsert1->isMaster()) || rowInsert1->isComponentRoot())) {
+ depthMax++; // Container: allow insertion as a child
+ } else {
+ RowTree *row = rowInsert1->parentRow();
+ if (rowInsert1->isPropertyOrMaterial() && !rowInsert1->parentRow()->isContainer()) {
+ depthMax--; // non-container with properties and/or a material
+ if (row)
+ row = row->parentRow();
+ }
+ if (srcHasMaster) {
+ while (row && !row->isMaster() && !row->isComponent()) {
+ depthMax--;
+ row = row->parentRow();
+ }
+ }
+ }
+
+ if (theRowType == OBJTYPE_LAYER) {
+ depth = 2; // layers can only be moved on depth 2
+ } else if (theRowType == OBJTYPE_EFFECT) {
+ depth = 3; // effects can only be moved on depth 3 (layer direct child)
+ } else {
+ static const int LEFT_MARGIN = 20;
+ depth = (int(scenePos.x()) - LEFT_MARGIN) / TimelineConstants::ROW_DEPTH_STEP;
+ depth = qBound(depthMin, depth, depthMax);
+ }
+ // calc insertion parent
+ RowTree *insertParent = rowInsert1;
+ // If we are dragging objects to a component,
+ // let insertParent be the component itself, not
+ // the parent for the component and set the source rows
+ // to be deleted soon (their duplicates will be inserted
+ // in the component). Do this only if m_active is true
+ // i.e. user is dragging a row within timeline (not from object/project panel)
+ // AND drop depth is larger than for the component (user is dropping items _in_
+ // the component, not at the same depth as the component itself)
+ if (insertParent->isComponent() && !insertParent->isComponentRoot()
+ && depth > insertParent->depth() && m_active) {
+ m_deleteAfterMove = true;
+ } else {
+ m_deleteAfterMove = false;
+ for (int i = rowInsert1->depth(); i >= depth; --i)
+ insertParent = insertParent->parentRow();
+ }
+
+ resetInsertionParent(insertParent);
+
+ if (depth < depthMin || depth > depthMax
+ || (theRowType != OBJTYPE_UNKNOWN
+ && !CStudioObjectTypes::AcceptableParent(theRowType,
+ m_insertionParent->objectType()))
+ || m_insertionParent->locked()) {
+ valid = false;
+ }
+
+ for (auto sourceRow : qAsConst(m_sourceRows)) {
+ if (m_insertionParent == sourceRow
+ || m_insertionParent->isDecendentOf(sourceRow)
+ || !CStudioObjectTypes::AcceptableParent(sourceRow->objectType(),
+ m_insertionParent->objectType())) {
+ // prevent insertion under itself, or under unacceptable parent
+ valid = false;
+ break;
+ }
+ }
+
+ // calc insertion target and type
+ if (rowInsert1 == m_insertionParent) {
+ if (m_insertionParent->expanded() && !m_insertionParent->childRows().empty()) {
+ m_insertionTarget = m_insertionParent->childRows().at(0);
+ m_insertType = Q3DStudio::DocumentEditorInsertType::PreviousSibling;
+ } else {
+ m_insertionTarget = m_insertionParent;
+ m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild;
+ }
+ } else if (rowInsert1->isProperty() && depth == rowInsert1->depth()) {
+ if (m_insertionParent->childRows().isEmpty()) {
+ m_insertionTarget = m_insertionParent;
+ m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild;
+ } else {
+ m_insertionTarget = m_insertionParent->childRows().at(0);
+ m_insertType = Q3DStudio::DocumentEditorInsertType::PreviousSibling;
+ }
+ } else {
+ m_insertionTarget = rowInsert1;
+ m_insertType = Q3DStudio::DocumentEditorInsertType::NextSibling;
+ if (depth < rowInsert1->depth()) {
+ for (int i = depth; i < rowInsert1->depth(); ++i)
+ m_insertionTarget = m_insertionTarget->parentRow();
+ }
+ }
+ // Don't allow single move right next to moving row at same depth
+ if (m_sourceRows.size() == 1
+ && (m_insertionTarget == m_sourceRows[0]
+ || ((m_insertType == Q3DStudio::DocumentEditorInsertType::NextSibling
+ && isNextSiblingRow(m_insertionTarget, m_sourceRows[0]))
+ || (m_insertType == Q3DStudio::DocumentEditorInsertType::PreviousSibling
+ && isNextSiblingRow(m_sourceRows[0], m_insertionTarget))))) {
+ valid = false;
+ }
+ if (valid) {
+ updateState(depth, rowInsert1->y() + rowInsert1->size().height());
+
+ // auto expand
+ if (!rowInsert1->locked() && !rowInsert1->expanded() && rowInsert1->isContainer()
+ && !rowInsert1->empty() && !isSourceRowsDescendant(rowInsert1)
+ && depth == rowInsert1->depth() + 1) {
+ updateTargetRowLater = std::bind(&RowMover::updateTargetRow, this,
+ scenePos, rowType, fileType, true);
+ m_rowAutoExpand = rowInsert1;
+ m_autoExpandTimer.start(TimelineConstants::AUTO_EXPAND_TIME);
+ } else {
+ m_rowAutoExpand = nullptr;
+ m_autoExpandTimer.stop();
+ }
+ } else if (firstTry && rowInsert2
+ && m_scene->rowManager()->getRowAtPos(scenePos) == rowInsert2) {
+ // If the current drop location turns out to be invalid and we are on the upper half of
+ // a row, we try to resolve target row as if we were half row below. This allows drags
+ // to be accepted over the entire row if inserting above the row is not valid.
+ updateTargetRow(scenePos + QPointF(0, TimelineConstants::ROW_H * .5),
+ rowType, fileType, false);
+ valid = !m_insertionTarget.isNull();
+ }
+ }
+
+ if (!valid) {
+ m_rowAutoExpand = nullptr;
+ m_autoExpandTimer.stop();
+ setVisible(false);
+ resetInsertionParent();
+ }
+}
+
+int RowMover::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeRowMover;
+}
+
+Q3DStudio::DocumentEditorInsertType::Enum RowMover::insertionType() const
+{
+ return m_insertType;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h
new file mode 100644
index 00000000..a8fc99e5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWMOVER_H
+#define ROWMOVER_H
+
+#include "TimelineConstants.h"
+#include "TimelineItem.h"
+#include "DocumentEditorEnumerations.h"
+#include "StudioObjectTypes.h"
+#include <QtCore/qtimer.h>
+#include <QtCore/qpointer.h>
+
+class RowTree;
+class TimelineGraphicsScene;
+
+class RowMover : public TimelineItem
+{
+public:
+ RowMover(TimelineGraphicsScene *m_scene);
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+
+ void start(const QVector<RowTree *> &rows);
+ void end(bool force = false);
+ void updateTargetRow(const QPointF &scenePos, EStudioObjectType rowType = OBJTYPE_UNKNOWN,
+ Q3DStudio::DocumentEditorFileType::Enum fileType
+ = Q3DStudio::DocumentEditorFileType::Unknown,
+ bool firstTry = true);
+ bool isActive() const;
+ RowTree *insertionTarget() const;
+ RowTree *insertionParent() const;
+ QVector<RowTree *> sourceRows() const;
+ void removeSourceRow(RowTree *row);
+ bool shouldDeleteAfterMove() const;
+ int type() const;
+ Q3DStudio::DocumentEditorInsertType::Enum insertionType() const;
+
+private:
+ void updateState(int depth, double y);
+ void resetInsertionParent(RowTree *newParent = nullptr);
+ bool isSourceRowsDescendant(RowTree *row) const;
+ bool sourceRowsHasMaster() const;
+ bool isNextSiblingRow(RowTree *r1, RowTree *r2) const;
+
+ TimelineGraphicsScene *m_scene = nullptr;
+ QPointer<RowTree> m_insertionTarget; // insertion target
+ RowTree *m_insertionParent = nullptr; // insertion parent
+ RowTree *m_rowAutoExpand = nullptr;
+ QVector<RowTree *> m_sourceRows; // dragged rows
+ bool m_active = false;
+ bool m_deleteAfterMove = false;
+ Q3DStudio::DocumentEditorInsertType::Enum m_insertType =
+ Q3DStudio::DocumentEditorInsertType::Unknown;
+ QTimer m_autoExpandTimer;
+ std::function<void()> updateTargetRowLater = {};
+};
+
+#endif // ROWMOVER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h
new file mode 100644
index 00000000..58a93115
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTYPES_H
+#define ROWTYPES_H
+
+#include <qglobal.h>
+
+enum class TimelineControlType {
+ None,
+ KeyFrame,
+ Duration,
+ StartHandle,
+ EndHandle
+};
+
+enum class TreeControlType {
+ None,
+ Arrow,
+ Shy,
+ Hide,
+ Lock
+};
+
+#endif // ROWTYPES_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp
new file mode 100644
index 00000000..429b0170
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "SelectionRect.h"
+#include "TimelineConstants.h"
+#include "Ruler.h"
+
+#include <QtGui/qpainter.h>
+
+SelectionRect::SelectionRect()
+{
+ setZValue(100);
+ setActive(false);
+}
+
+void SelectionRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ if (m_active)
+ painter->drawRect(m_rect);
+}
+
+void SelectionRect::start(const QPointF &origin)
+{
+ m_rect.setTopLeft(origin);
+ m_rect.setWidth(0);
+ m_rect.setHeight(0);
+ m_active = true;
+}
+
+void SelectionRect::updateSize(const QPointF &pos, const QRectF &visibleScene)
+{
+ QPointF newPos = pos;
+ if (newPos.x() < visibleScene.left())
+ newPos.setX(visibleScene.left());
+ else if (newPos.x() > visibleScene.right())
+ newPos.setX(visibleScene.right());
+ if (newPos.y() < visibleScene.top())
+ newPos.setY(visibleScene.top());
+ else if (newPos.y() > visibleScene.bottom())
+ newPos.setY(visibleScene.bottom());
+
+ m_rect.setBottomRight(newPos);
+ setRect(m_rect.normalized());
+}
+
+void SelectionRect::end()
+{
+ setRect(QRectF());
+ m_active = false;
+}
+
+bool SelectionRect::isActive() const
+{
+ return m_active;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h
new file mode 100644
index 00000000..224284c0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SELECTIONRECT_H
+#define SELECTIONRECT_H
+
+#include <QtWidgets/qgraphicsitem.h>
+
+class SelectionRect : public QGraphicsRectItem
+{
+public:
+ explicit SelectionRect();
+
+ void start(const QPointF &origin);
+ void updateSize(const QPointF &pos, const QRectF &visibleScene);
+ void end();
+ bool isActive() const;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+
+private:
+ bool m_active = false;
+ QRectF m_rect;
+};
+
+#endif // SELECTIONRECT_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h
new file mode 100644
index 00000000..61525718
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINECONSTANTS_H
+#define TIMELINECONSTANTS_H
+
+namespace TimelineConstants
+{
+ // Dimensions
+ const int ROW_H = 20;
+ const int ROW_H_EXPANDED = 120; // property rows height when graph is shown
+ const int ROW_SPACING = 2;
+ const int ROW_DEPTH_STEP = 15; // x-distance between 2 consecutive depths
+ const double RULER_SEC_W = 30; // width of 1 second section (at scale 1)
+ const double RULER_MILLI_W = RULER_SEC_W / 1000.0; // width of 1 millisecond section at scale 1
+ const int RULER_SEC_DIV = 10; // second divisions
+ const int RULER_DIV_H1 = 5; // height of main divisions
+ const int RULER_DIV_H2 = 3; // height of secondary divisions
+ const int RULER_DIV_H3 = 1; // height of minor divisions
+ const int RULER_BASE_Y = 18; // baseline Y
+ const int RULER_LABEL_W = 60;
+ const int RULER_LABEL_H = 10;
+ const int RULER_TICK_SCALE1 = 2;
+ const int RULER_TICK_SCALE2 = 3;
+ const int RULER_TICK_SCALE3 = 6;
+ const int RULER_TICK_SCALE4 = 21;
+ const int TOOLBAR_MARGIN = 10; // margin between the timeline and the toolbar
+ const int ROW_TEXT_OFFSET_Y = 3; // offset Y of comment text on row
+
+ const double RULER_EDGE_OFFSET = 15;
+ const double TREE_MIN_W = 160;
+ const double TREE_MAX_W = 600;
+ const double TREE_DEFAULT_W = 250;
+ const double TREE_BOUND_W = 10000; // real width of the row (> max possible visible tree area)
+ const double TREE_ICONS_W = 53;
+ const int SPLITTER_W = 4;
+ const int PLAYHEAD_W = 14;
+ const int DURATION_HANDLE_W = 14; // width of duration end handles in a timeline row
+ const int NAVIGATION_BAR_H = 30; // height of navigation/breadcrumb bar
+ const int TIMEBAR_TOOLTIP_OFFSET_V = 10;
+
+ // Other
+ const int EXPAND_ANIMATION_DURATION = 200;
+ const int AUTO_SCROLL_PERIOD = 50; // time steps (millis) while auto scrolling
+ const int AUTO_SCROLL_DELTA = 8; // increment in scroll at each time step
+ const int AUTO_SCROLL_TRIGGER = 500; // time after which auto scroll starts (millis)
+ const int AUTO_EXPAND_TIME = 500; // auto expand a hovered row (while DnD-ing)
+ const long MAX_SLIDE_TIME = 3599000; // milliseconds
+
+ const int TIMELINE_SCROLL_MAX_DELTA = 25; // Maximum amount of pixels to scroll per frame
+ const int TIMELINE_SCROLL_DIVISOR = 6; // Divisor for timeline autoscroll distance
+
+ // TODO: move the colors and dimensions to StudioPreferences.
+}
+
+#endif // TIMELINECONSTANTS_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp
new file mode 100644
index 00000000..378b20da
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineControl.h"
+#include "TimelineGraphicsScene.h"
+#include "RowManager.h"
+#include "RowTree.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "StudioApp.h"
+#include "Dialogs.h"
+
+TimelineControl::TimelineControl(TimelineGraphicsScene *scene)
+ : m_scene(scene)
+{
+}
+
+void TimelineControl::setRowTimeline(RowTimeline *rowTimeline)
+{
+ m_rowTimeline = rowTimeline;
+ m_timebar = m_rowTimeline->rowTree()->getBinding()->GetTimelineItem()->GetTimebar();
+ m_startTime = m_rowTimeline->getStartTime();
+ m_endTime = m_rowTimeline->getEndTime();
+ m_rowTimeline->updateBoundChildren(true);
+ m_rowTimeline->updateBoundChildren(false);
+}
+
+void TimelineControl::showDurationEditDialog()
+{
+ g_StudioApp.GetDialogs()->asyncDisplayDurationEditDialog(m_startTime, m_endTime, this);
+}
+
+void TimelineControl::ChangeStartTime(long inTime)
+{
+ m_rowTimeline->setStartTime(inTime);
+}
+
+void TimelineControl::ChangeEndTime(long inTime)
+{
+ m_rowTimeline->setEndTime(inTime);
+ m_scene->rowManager()->updateRulerDuration();
+}
+
+void TimelineControl::Commit()
+{
+ m_timebar->ChangeTime(m_rowTimeline->getStartTime(), true);
+ m_timebar->ChangeTime(m_rowTimeline->getEndTime(), false);
+ m_timebar->CommitTimeChange();
+}
+
+void TimelineControl::Rollback()
+{
+ m_rowTimeline->setStartTime(m_startTime);
+ m_rowTimeline->setEndTime(m_endTime);
+ m_scene->rowManager()->updateRulerDuration();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h
new file mode 100644
index 00000000..3c348016
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINECONTROL_H
+#define TIMELINECONTROL_H
+
+#include "DurationEditDlg.h"
+#include "RowTimeline.h"
+#include "Bindings/ITimelineTimebar.h"
+
+class TimelineGraphicsScene;
+
+class TimelineControl : public ITimeChangeCallback
+{
+public:
+ TimelineControl(TimelineGraphicsScene *scene);
+
+ void setRowTimeline(RowTimeline *rowTimeline);
+ void showDurationEditDialog();
+
+ // ITimeChangeCallback
+ void ChangeStartTime(long) override;
+ void ChangeEndTime(long) override;
+ void Commit() override;
+ void Rollback() override;
+
+private:
+ TimelineGraphicsScene *m_scene = nullptr;
+ RowTimeline *m_rowTimeline = nullptr;
+ ITimelineTimebar *m_timebar = nullptr;
+ long m_startTime = 0;
+ long m_endTime = 0;
+};
+
+#endif // TIMELINECONTROL_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp
new file mode 100644
index 00000000..b4a52760
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp
@@ -0,0 +1,1199 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineGraphicsScene.h"
+#include "TimelineItem.h"
+#include "TreeHeader.h"
+#include "Ruler.h"
+#include "PlayHead.h"
+#include "RowTree.h"
+#include "RowMover.h"
+#include "RowTimeline.h"
+#include "TimelineConstants.h"
+#include "TimelineToolbar.h"
+#include "SelectionRect.h"
+#include "RowManager.h"
+#include "KeyframeManager.h"
+#include "Keyframe.h"
+#include "IDocumentEditor.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "ResourceCache.h"
+#include "TimelineControl.h"
+#include "RowTreeContextMenu.h"
+#include "RowTimelineContextMenu.h"
+#include "StudioPreferences.h"
+#include "TimeEnums.h"
+#include "StudioClipboard.h"
+#include "Dialogs.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+
+#include <QtWidgets/qcombobox.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+#include <QtWidgets/qgraphicslinearlayout.h>
+#include <QtWidgets/qgraphicswidget.h>
+#include <QtWidgets/qgraphicsview.h>
+#include <QtWidgets/qscrollbar.h>
+#include <QtWidgets/qmenu.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qaction.h>
+#include <QtGui/qevent.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qglobal.h>
+#include <QtWidgets/qaction.h>
+
+static const QPointF invalidPoint(-999999.0, -999999.0);
+
+TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *timelineWidget)
+ : QGraphicsScene(timelineWidget)
+ , m_layoutRoot(new QGraphicsLinearLayout)
+ , m_layoutTree(new QGraphicsLinearLayout(Qt::Vertical))
+ , m_layoutTimeline(new QGraphicsLinearLayout(Qt::Vertical))
+ , m_ruler(new Ruler)
+ , m_playHead(new PlayHead(m_ruler))
+ , m_widgetTimeline(timelineWidget)
+ , m_widgetRoot(new QGraphicsWidget)
+ , m_rowMover(new RowMover(this))
+ , m_selectionRect(new SelectionRect())
+ , m_rowManager(new RowManager(this, m_layoutTree, m_layoutTimeline))
+ , m_keyframeManager(new KeyframeManager(this))
+ , m_pressPos(invalidPoint)
+ , m_pressScreenPos(invalidPoint)
+ , m_timelineControl(new TimelineControl(this))
+{
+ addItem(m_playHead);
+ addItem(m_selectionRect);
+ addItem(m_rowMover);
+ addItem(m_widgetRoot);
+
+ m_timebarToolTip = new QLabel(m_widgetTimeline);
+ m_timebarToolTip->setObjectName(QStringLiteral("timebarToolTip"));
+ m_timebarToolTip->setWindowModality(Qt::NonModal);
+ m_timebarToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
+ m_timebarToolTip->setContentsMargins(2, 2, 2, 2);
+
+ m_variantsToolTip = new QLabel(m_widgetTimeline);
+ m_variantsToolTip->setObjectName(QStringLiteral("variantsToolTip"));
+ m_variantsToolTip->setWindowModality(Qt::NonModal);
+ m_variantsToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip
+ | Qt::WindowTransparentForInput);
+ m_variantsToolTip->setContentsMargins(2, 2, 2, 2);
+
+ connect(qApp, &QApplication::focusChanged,
+ this, &TimelineGraphicsScene::handleApplicationFocusLoss);
+
+ m_rowMover->setVisible(false);
+
+ m_autoScrollTimelineTimer.setInterval(10); // 10 ms
+
+ m_layoutRoot->setSpacing(0);
+ m_layoutRoot->setContentsMargins(0, 0, 0, 0);
+ m_layoutRoot->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+ m_widgetRoot->setLayout(m_layoutRoot);
+
+ m_layoutTree->setSpacing(0);
+ m_layoutTree->setContentsMargins(0, 0, 0, 0);
+ m_layoutTree->setMinimumWidth(TimelineConstants::TREE_BOUND_W);
+ m_layoutTree->setMaximumWidth(TimelineConstants::TREE_BOUND_W);
+ m_layoutTree->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ m_layoutTimeline->setSpacing(0);
+ m_layoutTimeline->setContentsMargins(0, 0, 0, 0);
+ m_layoutTimeline->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+
+ m_layoutRoot->addItem(m_layoutTree);
+ m_layoutRoot->addItem(m_layoutTimeline);
+
+ m_treeHeader = new TreeHeader(this);
+
+ m_layoutTree->addItem(m_treeHeader);
+ m_layoutTimeline->addItem(m_ruler);
+
+ // auto scrolling (when DnD is active and hovering on top or bottom of the tree list)
+ connect(&m_autoScrollTimer, &QTimer::timeout, [this]() {
+ QScrollBar *scrollbar = m_widgetTimeline->viewTreeContent()->verticalScrollBar();
+ if (m_autoScrollUpOn)
+ scrollbar->setValue(scrollbar->value() - TimelineConstants::AUTO_SCROLL_DELTA);
+ else if (m_autoScrollDownOn)
+ scrollbar->setValue(scrollbar->value() + TimelineConstants::AUTO_SCROLL_DELTA);
+ });
+
+ connect(&m_autoScrollTimelineTimer, &QTimer::timeout, [this]() {
+ if (!qApp->focusWindow() && !g_StudioApp.isOnProgress()) {
+ resetMousePressParams();
+ return;
+ }
+ QGraphicsView *timelineContent = m_widgetTimeline->viewTimelineContent();
+ const QPoint scrollBarOffsets(getScrollbarOffsets());
+ const QRect contentRect = timelineContent->contentsRect();
+ const double right = timelineContent->width() - scrollBarOffsets.x();
+ QPoint p = m_widgetTimeline->mapFromGlobal(QCursor::pos())
+ - QPoint(m_widgetTimeline->viewTreeContent()->width()
+ + TimelineConstants::SPLITTER_W, 0);
+
+ // Limit the maximum scroll speed
+ if (p.x() < 0) {
+ p.setX(qMax(-TimelineConstants::TIMELINE_SCROLL_MAX_DELTA,
+ p.x() / TimelineConstants::TIMELINE_SCROLL_DIVISOR));
+ } else if (p.x() > right) {
+ p.setX(qMin(right + TimelineConstants::TIMELINE_SCROLL_MAX_DELTA,
+ right + 1 + ((p.x() - right)
+ / TimelineConstants::TIMELINE_SCROLL_DIVISOR)));
+ }
+
+ if (m_selectionRect->isActive()) {
+ p -= QPoint(0, m_widgetTimeline->navigationBar()->height() + TimelineConstants::ROW_H);
+ const double bottom = timelineContent->contentsRect().height() - scrollBarOffsets.y();
+ if (m_lastAutoScrollX != p.x() || p.x() <= 0 || p.x() >= right
+ || m_lastAutoScrollY != p.y() || p.y() <= 0 || p.y() >= bottom) {
+ m_lastAutoScrollX = p.x();
+ m_lastAutoScrollY = p.y();
+
+ if (p.y() < 0) {
+ p.setY(qMax(-TimelineConstants::TIMELINE_SCROLL_MAX_DELTA,
+ p.y() / TimelineConstants::TIMELINE_SCROLL_DIVISOR));
+ } else if (p.y() > bottom) {
+ p.setY(qMin(bottom + TimelineConstants::TIMELINE_SCROLL_MAX_DELTA,
+ bottom + 1 + ((p.y() - bottom)
+ / TimelineConstants::TIMELINE_SCROLL_DIVISOR)));
+ }
+
+ // Resize keyframe selection rect
+ const QPointF scenePoint = timelineContent->mapToScene(p);
+ timelineContent->ensureVisible(scenePoint.x(), scenePoint.y(),
+ 0, 0, 0, 0);
+ QRectF visibleScene(
+ timelineContent->mapToScene(contentRect.topLeft()),
+ timelineContent->mapToScene(contentRect.bottomRight()
+ - scrollBarOffsets));
+ m_selectionRect->updateSize(scenePoint, visibleScene);
+ m_keyframeManager->selectKeyframesInRect(m_selectionRect->rect());
+ }
+ } else if (m_lastAutoScrollX != p.x() || p.x() <= 0 || p.x() >= right) {
+ m_lastAutoScrollX = p.x();
+
+ bool shift = QGuiApplication::queryKeyboardModifiers() & Qt::ShiftModifier;
+ double scroll = timelineContent->horizontalScrollBar()->value();
+ if (scroll != 0)
+ scroll -= TimelineConstants::TREE_BOUND_W;
+
+ double distance = p.x() + scroll - TimelineConstants::RULER_EDGE_OFFSET;
+ if (m_clickedTimelineControlType == TimelineControlType::Duration
+ && !m_editedTimelineRow.isNull()) {
+ distance -= m_editedTimelineRow->getDurationMoveOffsetX();
+ }
+
+ if (shift)
+ snap(distance, !m_rulerPressed);
+
+ if (m_rulerPressed) {
+ long time = m_ruler->distanceToTime(distance);
+ if (time < 0)
+ time = 0;
+ g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(time);
+ } else {
+ if (m_editedTimelineRow.isNull()) {
+ resetMousePressParams();
+ return;
+ }
+
+ if (m_dragging) {
+ if (m_clickedTimelineControlType == TimelineControlType::StartHandle) {
+ double visiblePtX = distance > 0 ? m_editedTimelineRow->getStartX() : 0;
+ if (distance > m_editedTimelineRow->getEndX())
+ visiblePtX += TimelineConstants::RULER_EDGE_OFFSET;
+
+ m_editedTimelineRow->setStartX(distance);
+ m_editedTimelineRow->showToolTip(QCursor::pos());
+ timelineContent->ensureVisible(TimelineConstants::TREE_BOUND_W
+ + TimelineConstants::RULER_EDGE_OFFSET
+ + visiblePtX,
+ m_editedTimelineRow->y(), 0, 0, 0, 0);
+ } else if (m_clickedTimelineControlType == TimelineControlType::EndHandle) {
+ long time = m_ruler->distanceToTime(distance);
+ double edgeMargin = 0;
+ if (time > TimelineConstants::MAX_SLIDE_TIME) {
+ distance = m_ruler->timeToDistance(TimelineConstants::MAX_SLIDE_TIME);
+ edgeMargin = TimelineConstants::RULER_EDGE_OFFSET;
+ } else if (time < m_editedTimelineRow->getStartTime()) {
+ edgeMargin = -TimelineConstants::RULER_EDGE_OFFSET;
+ }
+ m_editedTimelineRow->setEndX(distance);
+ m_editedTimelineRow->showToolTip(QCursor::pos());
+ rowManager()->updateRulerDuration(p.x() > right);
+ timelineContent->ensureVisible(
+ TimelineConstants::TREE_BOUND_W
+ + TimelineConstants::RULER_EDGE_OFFSET
+ + m_editedTimelineRow->getEndX() + edgeMargin,
+ m_editedTimelineRow->y(), 0, 0, 0, 0);
+ } else if (m_clickedTimelineControlType == TimelineControlType::Duration) {
+ long time = m_ruler->distanceToTime(distance)
+ + m_editedTimelineRow->getDuration(); // milliseconds
+ double visiblePtX = distance
+ + m_editedTimelineRow->getDurationMoveOffsetX();
+ if (time > TimelineConstants::MAX_SLIDE_TIME) {
+ distance = m_ruler->timeToDistance(TimelineConstants::MAX_SLIDE_TIME
+ - m_editedTimelineRow->getDuration());
+ visiblePtX = m_editedTimelineRow->getEndX()
+ + TimelineConstants::RULER_EDGE_OFFSET;
+ }
+
+ m_editedTimelineRow->moveDurationTo(distance);
+ m_editedTimelineRow->showToolTip(QCursor::pos());
+ rowManager()->updateRulerDuration(p.x() > right);
+ timelineContent->ensureVisible(
+ TimelineConstants::TREE_BOUND_W
+ + TimelineConstants::RULER_EDGE_OFFSET + visiblePtX,
+ m_editedTimelineRow->y(), 0, 0, 0, 0);
+ }
+ }
+ }
+ }
+ });
+
+ connect(&m_autoScrollTriggerTimer, &QTimer::timeout, [this]() {
+ m_autoScrollTimer.start(TimelineConstants::AUTO_SCROLL_PERIOD);
+ });
+
+ QTimer::singleShot(0, this, [this]() {
+ m_playHead->setPosition(0);
+ m_widgetTimeline->viewTreeContent()->horizontalScrollBar()->setValue(0);
+ });
+
+ QAction *action = new QAction(this);
+ action->setShortcut(Qt::Key_S);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleInsertKeyframe);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_K));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this,
+ &TimelineGraphicsScene::handleDeleteChannelKeyframes);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_T));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleSetTimeBarTime);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_G));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleMakeComponent);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_G));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleEditComponent);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_C));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleCopyObjectPath);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_H));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, &g_StudioApp, &CStudioApp::toggleShy);
+ timelineWidget->addAction(action);
+
+ action = new QAction(this);
+ action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_H));
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ connect(action, &QAction::triggered, &g_StudioApp, &CStudioApp::toggleLocked);
+ timelineWidget->addAction(action);
+}
+
+TimelineGraphicsScene::~TimelineGraphicsScene()
+{
+ disconnect(qApp, &QApplication::focusChanged,
+ this, &TimelineGraphicsScene::handleApplicationFocusLoss);
+ delete m_dataInputSelector;
+}
+
+void TimelineGraphicsScene::setTimelineScale(int scl)
+{
+ m_ruler->setTimelineScale(scl);
+ m_playHead->updatePosition();
+ updateTimelineLayoutWidth();
+
+ for (int i = 1; i < m_layoutTimeline->count(); i++)
+ static_cast<RowTimeline *>(m_layoutTimeline->itemAt(i)->graphicsItem())->updatePosition();
+}
+
+void TimelineGraphicsScene::setControllerText(const QString &controller)
+{
+ // check that we have scene/container root item at index 1
+ if (m_layoutTimeline->count() < 2)
+ return;
+
+ RowTimeline *rt = static_cast<RowTimeline *>(m_layoutTimeline->itemAt(1)->graphicsItem());
+ rt->setControllerText(controller);
+}
+
+void TimelineGraphicsScene::updateTimelineLayoutWidth()
+{
+ double timelineWidth = TimelineConstants::RULER_EDGE_OFFSET * 2
+ + m_ruler->maxDuration() * TimelineConstants::RULER_MILLI_W
+ * m_ruler->timelineScale();
+
+ m_layoutTimeline->setMinimumWidth(timelineWidth);
+ m_layoutTimeline->setMaximumWidth(timelineWidth);
+}
+
+void TimelineGraphicsScene::updateControllerLayoutWidth()
+{
+ if (m_layoutTimeline->count() < 2)
+ return;
+ auto root = m_layoutTimeline->itemAt(1);
+
+ static_cast<RowTimeline *>(root->graphicsItem())->setEndTime(ruler()->duration());
+}
+
+void TimelineGraphicsScene::updateController()
+{
+ setControllerText(m_widgetTimeline->toolbar()->getCurrentController());
+}
+
+void TimelineGraphicsScene::commitMoveRows()
+{
+ if (!m_rowMover->insertionTarget()
+ || m_rowMover->sourceRows().contains(m_rowMover->insertionTarget())) {
+ return;
+ }
+
+ // handles for the moving rows
+ qt3dsdm::TInstanceHandleList sourceHandles;
+ const auto sourceRows = m_rowMover->sourceRows();
+ for (auto sourceRow : sourceRows) {
+ qt3dsdm::Qt3DSDMInstanceHandle handleSource = static_cast<Qt3DSDMTimelineItemBinding *>
+ (sourceRow->getBinding())->GetInstance();
+ sourceHandles.push_back(handleSource);
+ }
+ qt3dsdm::Qt3DSDMInstanceHandle handleTarget = static_cast<Qt3DSDMTimelineItemBinding *>
+ (m_rowMover->insertionTarget()->getBinding())->GetInstance();
+
+ if (!m_rowMover->insertionParent()->expanded())
+ m_rowMover->insertionParent()->updateExpandStatus(RowTree::ExpandState::Expanded, false);
+
+ // Remove sourcerows for items that will be deleted as result of RearrangeObjects,
+ // f.ex objects that will be moved to a component; otherwise we try to update
+ // timeline rows that no longer have valid scene objects linked to them.
+ // Note that we remove all sourcerows that are being dragged currently, because they
+ // all share the same drop target anyway.
+ if (m_rowMover->shouldDeleteAfterMove()) {
+ for (auto sourceRow : sourceRows)
+ m_rowMover->removeSourceRow(sourceRow);
+ }
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(),
+ QObject::tr("Move Rows"))
+ ->RearrangeObjects(sourceHandles, handleTarget, m_rowMover->insertionType());
+
+ // updating the UI happens in TimelineWidget.onChildAdded()
+}
+
+void TimelineGraphicsScene::updateTreeWidth(double treeWidth)
+{
+ if (m_treeWidth != treeWidth) {
+ m_treeWidth = treeWidth;
+ update();
+ }
+}
+
+double TimelineGraphicsScene::treeWidth() const
+{
+ return m_treeWidth;
+}
+
+void TimelineGraphicsScene::setMouseCursor(CMouseCursor::Qt3DSMouseCursor cursor)
+{
+ if (m_currentCursor != cursor) {
+ if (m_currentCursor != -1)
+ qApp->changeOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor));
+ else
+ qApp->setOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor));
+ m_currentCursor = cursor;
+ }
+}
+
+void TimelineGraphicsScene::resetMouseCursor()
+{
+ if (m_currentCursor != -1) {
+ // Restoring back to no-override state seems to not change the cursor automatically
+ // to the default cursor, so let's do that manually before restoring the cursor
+ qApp->changeOverrideCursor(Qt::ArrowCursor);
+ qApp->restoreOverrideCursor();
+ m_currentCursor = -1;
+ }
+}
+
+void TimelineGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(m_widgetTimeline);
+
+ if ((event->modifiers() & Qt::AltModifier) && !m_dragging) {
+ if (event->button() == Qt::RightButton && !m_timelinePanning) {
+ // Start zooming
+ m_timelineZooming = true;
+ m_pressScreenPos = event->screenPos();
+ event->accept();
+ return;
+ } else if (event->button() == Qt::MiddleButton && !m_timelineZooming) {
+ // Start panning
+ m_timelinePanning = true;
+ m_pressPos = event->scenePos();
+ event->accept();
+ return;
+ }
+ }
+
+ // Ignore non-left presses if dragging
+ if (event->button() != Qt::LeftButton && (m_dragging || m_startRowMoverOnNextDrag)) {
+ event->accept();
+ return;
+ }
+
+ if (m_widgetTimeline->blockMousePress())
+ return;
+
+ if (!m_widgetTimeline->isFullReconstructPending() && event->button() == Qt::LeftButton) {
+ resetMousePressParams();
+ m_pressPos = event->scenePos();
+ QGraphicsItem *item = itemAt(m_pressPos, QTransform());
+ const bool ctrlKeyDown = event->modifiers() & Qt::ControlModifier;
+ if (item) {
+ item = getItemBelowType(TimelineItem::TypePlayHead, item, m_pressPos);
+ if (item->type() == TimelineItem::TypeRuler) {
+ m_rulerPressed = true;
+ m_autoScrollTimelineTimer.start();
+ } else if (item->type() == TimelineItem::TypeTreeHeader) {
+ if (m_treeHeader->handleButtonsClick(m_pressPos) != TreeControlType::None) {
+ m_rowManager->updateFiltering();
+ updateSnapSteps();
+ }
+ } else if (item->type() == TimelineItem::TypeRowTree
+ || item->type() == TimelineItem::TypeRowTreeLabelItem) {
+ item = getItemBelowType(TimelineItem::TypeRowTreeLabelItem, item, m_pressPos);
+ RowTree *rowTree = static_cast<RowTree *>(item);
+ m_clickedTreeControlType = rowTree->getClickedControl(m_pressPos);
+ if (m_clickedTreeControlType == TreeControlType::Shy
+ || m_clickedTreeControlType == TreeControlType::Hide
+ || m_clickedTreeControlType == TreeControlType::Lock) {
+ m_rowManager->updateFiltering(rowTree);
+ updateSnapSteps();
+ } else if (m_clickedTreeControlType == TreeControlType::None) {
+ // Prepare to change selection to single selection at release if a multiselected
+ // row is clicked without ctrl.
+ if (!ctrlKeyDown && m_rowManager->isRowSelected(rowTree)
+ && !m_rowManager->isSingleSelected() ) {
+ m_releaseSelectRow = rowTree;
+ }
+ m_rowManager->selectRow(rowTree, ctrlKeyDown);
+ if (rowTree->draggable())
+ m_startRowMoverOnNextDrag = true;
+ } else if (m_clickedTreeControlType == TreeControlType::Arrow) {
+ updateSnapSteps();
+ }
+ } else if (item->type() == TimelineItem::TypeRowTimeline) {
+ m_editedTimelineRow = static_cast<RowTimeline *>(item);
+ Keyframe *keyframe = m_editedTimelineRow->getClickedKeyframe(m_pressPos);
+ if (keyframe) { // pressed a keyframe
+ if (ctrlKeyDown && keyframe->selected()) {
+ if (m_editedTimelineRow->rowTree()->isProperty())
+ m_keyframeManager->deselectKeyframe(keyframe);
+ else
+ m_keyframeManager->deselectConnectedKeyframes(keyframe);
+ } else {
+ if (!ctrlKeyDown && !keyframe->selected())
+ m_keyframeManager->deselectAllKeyframes();
+
+ if (m_editedTimelineRow->rowTree()->isProperty())
+ m_keyframeManager->selectKeyframe(keyframe);
+ else
+ m_keyframeManager->selectConnectedKeyframes(keyframe);
+
+ m_pressPosInKeyframe = (m_pressPos.x() - m_ruler->x())
+ - (TimelineConstants::RULER_EDGE_OFFSET
+ + m_ruler->timeToDistance(keyframe->time));
+ m_pressedKeyframe = keyframe;
+ }
+ } else {
+ m_keyframeManager->deselectAllKeyframes();
+ m_clickedTimelineControlType
+ = m_editedTimelineRow->getClickedControl(m_pressPos);
+
+ // clicked an empty spot on a timeline row, start selection rect.
+ if (m_clickedTimelineControlType == TimelineControlType::None) {
+ m_selectionRect->start(m_pressPos);
+ } else if (m_clickedTimelineControlType == TimelineControlType::Duration) {
+ if (!ctrlKeyDown
+ && m_rowManager->isRowSelected(m_editedTimelineRow->rowTree())
+ && !m_rowManager->isSingleSelected()) {
+ m_releaseSelectRow = m_editedTimelineRow->rowTree();
+ }
+
+ m_rowManager->selectRow(m_editedTimelineRow->rowTree(), ctrlKeyDown);
+ // click position in ruler space
+ m_editedTimelineRow->startDurationMove(m_pressPos.x() - m_ruler->x());
+ } else if (m_clickedTimelineControlType == TimelineControlType::StartHandle
+ || m_clickedTimelineControlType == TimelineControlType::EndHandle) {
+ m_editedTimelineRow->updateBoundChildren(
+ m_clickedTimelineControlType
+ == TimelineControlType::StartHandle);
+ }
+ m_autoScrollTimelineTimer.start();
+ }
+ }
+ } else {
+ m_keyframeManager->deselectAllKeyframes();
+
+ if (m_pressPos.x() > m_ruler->x() && m_pressPos.y() > TimelineConstants::ROW_H) {
+ m_selectionRect->start(m_pressPos);
+ m_autoScrollTimelineTimer.start();
+ }
+ }
+ }
+
+ QGraphicsScene::mousePressEvent(event);
+}
+
+void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (m_timelineZooming) {
+ int deltaY = event->screenPos().y() - m_pressScreenPos.y();
+ int deltaX = event->screenPos().x() - m_pressScreenPos.x();
+ // Zooming in when moving down/right.
+ int delta = -deltaX - deltaY;
+ const int threshold = 20;
+ if (delta < -threshold) {
+ m_widgetTimeline->toolbar()->onZoomInButtonClicked();
+ m_pressScreenPos = event->screenPos();
+ } else if (delta > threshold) {
+ m_widgetTimeline->toolbar()->onZoomOutButtonClicked();
+ m_pressScreenPos = event->screenPos();
+ }
+ } else if (m_timelinePanning) {
+ int deltaX = event->scenePos().x() - m_pressPos.x();
+ QScrollBar *scrollbar = m_widgetTimeline->viewTimelineContent()->horizontalScrollBar();
+ scrollbar->setValue(scrollbar->value() - deltaX);
+ }
+
+ if (m_editedTimelineRow.isNull())
+ updateHoverStatus(event->scenePos());
+
+ if (!m_dragging && !m_timelineZooming && !m_timelinePanning
+ && m_pressPos != invalidPoint
+ && (event->scenePos() - m_pressPos).manhattanLength() > 10) {
+ m_dragging = true;
+ }
+
+ bool shift = event->modifiers() & Qt::ShiftModifier;
+ if (m_dragging) {
+ if (m_startRowMoverOnNextDrag || m_rowMover->isActive()) {
+ // moving rows vertically (reorder/reparent)
+ if (m_startRowMoverOnNextDrag) {
+ m_startRowMoverOnNextDrag = false;
+ m_rowMover->start(m_rowManager->selectedRows());
+ }
+ if (m_rowMover->isActive()) {
+ m_rowMover->updateTargetRow(event->scenePos());
+ updateAutoScrolling(event->scenePos().y());
+ }
+ } else if (m_pressedKeyframe) { // moving selected keyframes
+ double newX = event->scenePos().x() - m_ruler->x()
+ - TimelineConstants::RULER_EDGE_OFFSET - m_pressPosInKeyframe;
+
+ if (newX < 0)
+ newX = 0;
+ if (shift)
+ snap(newX);
+
+ m_keyframeManager->moveSelectedKeyframes(ruler()->distanceToTime(newX));
+
+ m_pressPos.setX(newX);
+ }
+ }
+
+ QGraphicsScene::mouseMoveEvent(event);
+}
+
+// auto scroll when the mouse is at the top or bottom of the tree list
+void TimelineGraphicsScene::updateAutoScrolling(double scenePosY)
+{
+ QScrollBar *scrollbar = m_widgetTimeline->viewTreeContent()->verticalScrollBar();
+ double mouseY = scenePosY - scrollbar->value();
+ int bottomY = m_widgetTimeline->height() - m_widgetTimeline->toolbar()->height()
+ - TimelineConstants::ROW_H;
+ if (mouseY > 0 && mouseY < TimelineConstants::ROW_H) {
+ if (!m_autoScrollUpOn) {
+ m_autoScrollTriggerTimer.start(TimelineConstants::AUTO_SCROLL_TRIGGER);
+ m_autoScrollUpOn = true;
+ }
+ } else if (m_autoScrollUpOn) {
+ m_autoScrollTimer.stop();
+ m_autoScrollTriggerTimer.stop();
+ m_autoScrollUpOn = false;
+ }
+
+ if (mouseY > bottomY - TimelineConstants::ROW_H - TimelineConstants::TOOLBAR_MARGIN
+ && mouseY < bottomY) {
+ if (!m_autoScrollDownOn) {
+ m_autoScrollTriggerTimer.start(TimelineConstants::AUTO_SCROLL_TRIGGER);
+ m_autoScrollDownOn = true;
+ }
+ } else if (m_autoScrollDownOn) {
+ m_autoScrollTimer.stop();
+ m_autoScrollTriggerTimer.stop();
+ m_autoScrollDownOn = false;
+ }
+}
+
+void TimelineGraphicsScene::stopAutoScroll() {
+ m_autoScrollTimer.stop();
+ m_autoScrollTriggerTimer.stop();
+ m_autoScrollUpOn = false;
+ m_autoScrollDownOn = false;
+}
+
+void TimelineGraphicsScene::updateSnapSteps()
+{
+ m_snapSteps.clear();
+ // i = 1 is always the scene row (or component root)
+ for (int i = 2; i < m_layoutTimeline->count(); i++) {
+ RowTree *rowTree = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem());
+ if (rowTree->hasDurationBar() && rowTree->isVisible()) {
+ double startX = rowTree->rowTimeline()->getStartX();
+ if (!m_snapSteps.contains(startX))
+ m_snapSteps.push_back(startX);
+
+ double endX = rowTree->rowTimeline()->getEndX();
+ if (!m_snapSteps.contains(endX))
+ m_snapSteps.push_back(endX);
+
+ // add keyframes times
+ if (rowTree->hasPropertyChildren()) {
+ const QList<Keyframe *> keyframes = rowTree->rowTimeline()->keyframes();
+ for (Keyframe *k : keyframes) {
+ double kX = m_ruler->timeToDistance(k->time);
+ if (!m_snapSteps.contains(kX))
+ m_snapSteps.push_back(kX);
+ }
+ }
+ }
+ }
+}
+
+TExpandMap &TimelineGraphicsScene::expandMap()
+{
+ return m_expandMap;
+}
+
+void TimelineGraphicsScene::resetMousePressParams()
+{
+ m_autoScrollTimelineTimer.stop();
+ m_selectionRect->end();
+ m_rowMover->end();
+ m_dragging = false;
+ m_timelineZooming = false;
+ m_timelinePanning = false;
+ m_startRowMoverOnNextDrag = false;
+ m_rulerPressed = false;
+ m_pressedKeyframe = nullptr;
+ m_clickedTimelineControlType = TimelineControlType::None;
+ m_editedTimelineRow.clear();
+ m_releaseSelectRow.clear();
+ m_autoScrollTimer.stop();
+ m_autoScrollTriggerTimer.stop();
+ m_timebarToolTip->hide();
+ m_pressPos = invalidPoint;
+ m_pressScreenPos = invalidPoint;
+ m_lastAutoScrollX = -1.0;
+ m_lastAutoScrollY = -1.0;
+}
+
+void TimelineGraphicsScene::resetPressedKeyframe()
+{
+ m_pressedKeyframe = nullptr;
+}
+
+QLabel *TimelineGraphicsScene::timebarTooltip()
+{
+ return m_timebarToolTip;
+}
+
+void TimelineGraphicsScene::snap(double &value, bool snapToPlayHead)
+{
+ // snap to play head
+ if (snapToPlayHead) {
+ double playHeadX = m_playHead->x() - TimelineConstants::TREE_BOUND_W
+ - TimelineConstants::RULER_EDGE_OFFSET;
+ if (abs(value - playHeadX) < CStudioPreferences::GetSnapRange()) {
+ value = playHeadX;
+ return;
+ }
+ }
+
+ // duration edges snap
+ for (double v : qAsConst(m_snapSteps)) {
+ if (abs(value - v) < CStudioPreferences::GetSnapRange()) {
+ value = v;
+ return;
+ }
+ }
+
+ // time steps snap
+ if (CStudioPreferences::IsTimelineSnappingGridActive()) {
+ double snapStep = TimelineConstants::RULER_SEC_W * m_ruler->timelineScale();
+ if (CStudioPreferences::GetTimelineSnappingGridResolution() == SNAPGRID_HALFSECONDS)
+ snapStep *= .5;
+ else if (CStudioPreferences::GetTimelineSnappingGridResolution() == SNAPGRID_TICKMARKS)
+ snapStep *= .1;
+
+ double snapValue = round(value / snapStep) * snapStep;
+ if (abs(value - snapValue) < CStudioPreferences::GetSnapRange())
+ value = snapValue;
+ }
+}
+
+void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton) {
+ if (m_dragging) {
+ if (m_rowMover->isActive()) { // moving rows (reorder/reparent)
+ commitMoveRows();
+ } else if (m_pressedKeyframe) {
+ // update keyframe movement (time) to binding
+ m_keyframeManager->commitMoveSelectedKeyframes();
+ } else if (m_clickedTimelineControlType == TimelineControlType::StartHandle) {
+ if (!m_editedTimelineRow.isNull()) {
+ ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding()
+ ->GetTimelineItem()->GetTimebar();
+ timebar->ChangeTime(m_editedTimelineRow->getStartTime(), true);
+ timebar->CommitTimeChange();
+ }
+ } else if (m_clickedTimelineControlType == TimelineControlType::EndHandle) {
+ if (!m_editedTimelineRow.isNull()) {
+ ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding()
+ ->GetTimelineItem()->GetTimebar();
+ timebar->ChangeTime(m_editedTimelineRow->getEndTime(), false);
+ timebar->CommitTimeChange();
+ if (m_playHead->time() > ruler()->duration())
+ g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(ruler()->duration());
+ }
+ } else if (m_clickedTimelineControlType == TimelineControlType::Duration) {
+ if (!m_editedTimelineRow.isNull()) {
+ ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding()
+ ->GetTimelineItem()->GetTimebar();
+ timebar->OffsetTime(m_editedTimelineRow->getDurationMoveTime());
+ timebar->CommitTimeChange();
+ if (m_playHead->time() > ruler()->duration())
+ g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(ruler()->duration());
+ }
+ }
+ } else if (!m_rulerPressed && (!m_releaseSelectRow.isNull() || !itemAt(event->scenePos(),
+ QTransform()))) {
+ m_rowManager->selectRow(nullptr);
+ if (!m_releaseSelectRow.isNull())
+ m_rowManager->selectRow(m_releaseSelectRow);
+ }
+ }
+
+ if (m_timelineZooming)
+ updateSnapSteps();
+
+ resetMousePressParams();
+
+ QGraphicsScene::mouseReleaseEvent(event);
+}
+
+void TimelineGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton) {
+ const QPointF scenePos = event->scenePos();
+ QGraphicsItem *item = itemAt(scenePos, QTransform());
+ if (item) {
+ QGraphicsItem *itemBelowPlayhead =
+ getItemBelowType(TimelineItem::TypePlayHead, item, scenePos);
+ if (item->type() == TimelineItem::TypeRuler
+ || itemBelowPlayhead->type() == TimelineItem::TypeRuler) {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(doc->GetCurrentViewTime(),
+ doc, PLAYHEAD,
+ m_keyframeManager);
+ } else {
+ item = itemBelowPlayhead;
+ if (item->type() == TimelineItem::TypeRowTree) {
+ RowTree *treeItem = static_cast<RowTree *>(item);
+ if (treeItem->isProperty())
+ treeItem->togglePropertyExpanded();
+ } else if (item->type() == TimelineItem::TypeRowTreeLabelItem) {
+ RowTreeLabelItem *treeLabelItem = static_cast<RowTreeLabelItem *>(item);
+ if (treeLabelItem->parentRow()->isProperty()) {
+ treeLabelItem->parentRow()->togglePropertyExpanded();
+ } else if (!treeLabelItem->isLocked()
+ && treeLabelItem->parentRow()->objectType() != OBJTYPE_SCENE
+ && treeLabelItem->parentRow()->objectType() != OBJTYPE_IMAGE) {
+ int instance = treeLabelItem->parentRow()->instance();
+ const auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge();
+ if (bridge->GetObjectType(instance) != OBJTYPE_REFERENCEDMATERIAL
+ || bridge->GetSourcePath(instance).isEmpty()) {
+ // Tree labels text can be edited with double-click,
+ // except for Scene label and basic materials
+ treeLabelItem->setEnabled(true);
+ treeLabelItem->setFocus();
+ }
+ }
+ } else if (item->type() == TimelineItem::TypeRowTimeline) {
+ RowTimeline *rowTimeline = static_cast<RowTimeline *>(item);
+ Keyframe *clickedKeyframe = rowTimeline->getClickedKeyframe(scenePos);
+ if (clickedKeyframe) {
+ m_pressedKeyframe = clickedKeyframe;
+ g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(
+ clickedKeyframe->time, g_StudioApp.GetCore()->GetDoc(),
+ ASSETKEYFRAME, m_keyframeManager);
+ } else {
+ if (!rowTimeline->rowTree()->locked())
+ handleSetTimeBarTime();
+ }
+ }
+ }
+ }
+ }
+
+ QGraphicsScene::mouseDoubleClickEvent(event);
+}
+
+void TimelineGraphicsScene::wheelEvent(QGraphicsSceneWheelEvent *wheelEvent)
+{
+ // Make sure drag states update on wheel scrolls done during drag
+ m_lastAutoScrollX = -1.0;
+ m_lastAutoScrollY = -1.0;
+ QGraphicsScene::wheelEvent(wheelEvent);
+}
+
+void TimelineGraphicsScene::keyPressEvent(QKeyEvent *keyEvent)
+{
+ // Eat left/right arrow keys on tree side unless some item (e.g. label) has focus
+ if ((keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right)
+ && (qApp->focusObject() == m_widgetTimeline->viewTreeContent() && !focusItem())) {
+ keyEvent->accept();
+ return;
+ } else if (keyEvent->key() == Qt::Key_Escape && m_rowMover->isActive()) {
+ m_rowMover->end();
+ } else if (keyEvent->key() == Qt::Key_Delete && !m_rowMover->isActive()
+ && !focusItem()) {
+ g_StudioApp.DeleteSelectedObject(); // Despite the name, this deletes objects and keyframes
+ }
+ // Make sure drag states update on keyboard scrolls done during drag
+ if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right
+ || keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
+ m_lastAutoScrollX = -1.0;
+ m_lastAutoScrollY = -1.0;
+ }
+
+ QGraphicsScene::keyPressEvent(keyEvent);
+}
+
+void TimelineGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+}
+
+void TimelineGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+ // No context menu if user is pressing ALT (so panning/zooming timeline)
+ bool alt = event->modifiers() & Qt::AltModifier;
+ RowTree *row = m_rowManager->getRowAtPos(QPointF(0, event->scenePos().y()));
+ if (!row || m_widgetTimeline->isFullReconstructPending() || m_dragging
+ || m_startRowMoverOnNextDrag || row->locked() || alt) {
+ return;
+ }
+
+ resetMousePressParams(); // Make sure our mouse handling doesn't get confused by context menu
+
+ // Internally some things like make component depend on the correct row being selected,
+ // so make sure it is.
+ m_rowManager->selectRow(row);
+ if (event->scenePos().x() > TimelineConstants::TREE_BOUND_W) { // timeline context menu
+ RowTimelineContextMenu timelineContextMenu(row, m_keyframeManager, event,
+ m_timelineControl);
+ timelineContextMenu.exec(event->screenPos());
+ } else { // tree context menu
+ if (!row->isProperty()) {
+ RowTreeContextMenu treeContextMenu(row);
+ treeContextMenu.exec(event->screenPos());
+ }
+ }
+}
+
+bool TimelineGraphicsScene::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ShortcutOverride:
+ if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Delete) {
+ QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event));
+ event->accept();
+ return true;
+ }
+ Q_FALLTHROUGH();
+
+ default:
+ return QGraphicsScene::event(event);
+ }
+}
+
+void TimelineGraphicsScene::updateHoverStatus(const QPointF &scenePos)
+{
+ bool variantsAreaHovered = false;
+ QGraphicsItem *item = itemAt(scenePos, QTransform());
+ if (item) {
+ item = getItemBelowType(TimelineItem::TypePlayHead, item, scenePos);
+ // update timeline row cursor
+ if (item->type() == TimelineItem::TypeRowTimeline) {
+ RowTimeline *timelineItem = static_cast<RowTimeline *>(item);
+ TimelineControlType controlType = timelineItem->getClickedControl(scenePos);
+ if (controlType == TimelineControlType::StartHandle
+ || controlType == TimelineControlType::EndHandle) {
+ setMouseCursor(CMouseCursor::CURSOR_RESIZE_LEFTRIGHT);
+ } else {
+ resetMouseCursor();
+ }
+ } else if (!m_dragging && (item->type() == TimelineItem::TypeRowTree
+ || item->type() == TimelineItem::TypeRowTreeLabelItem)) {
+ // update tree row variants tooltip
+ RowTree *rowTree = item->type() == TimelineItem::TypeRowTree
+ ? static_cast<RowTree *>(item)
+ : static_cast<RowTreeLabelItem *>(item)->parentRow();
+ if (!rowTree->isProperty()) {
+ int left = rowTree->clipX();
+ int right = (int)rowTree->treeWidth() - TimelineConstants::TREE_ICONS_W;
+ variantsAreaHovered = scenePos.x() > left && scenePos.x() < right;
+ if (variantsAreaHovered && rowTree != m_variantsRowTree) {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ auto property = bridge->getVariantsProperty(rowTree->instance());
+
+ using namespace qt3dsdm;
+ SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(rowTree->instance(), property,
+ sValue)) {
+ QString propVal = get<TDataStrPtr>(sValue)->toQString();
+ if (!propVal.isEmpty()) {
+ // parse propVal into variantsHash (group => tags)
+ const QStringList tagPairs = propVal.split(QLatin1Char(','));
+ QHash<QString, QStringList> variantsHash;
+ QStringList variantsHashKeys; // maintain traverse order
+ for (auto &tagPair : tagPairs) {
+ const QStringList pair = tagPair.split(QLatin1Char(':'));
+ variantsHash[pair[0]].append(pair[1]);
+ if (!variantsHashKeys.contains(pair[0]))
+ variantsHashKeys.append(pair[0]);
+ }
+
+ // parse variantsHash into tooltipStr
+ const auto variantsDef
+ = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ QString templ = QStringLiteral("<font color='%1'>%2</font>");
+ QString tooltipStr("<table>");
+ for (auto &g : qAsConst(variantsHashKeys)) {
+ tooltipStr.append("<tr><td>");
+ tooltipStr.append(templ.arg(variantsDef[g].m_color).arg(g + ": "));
+ tooltipStr.append("</td><td>");
+ for (auto &t : qAsConst(variantsHash[g]))
+ tooltipStr.append(t + ", ");
+ tooltipStr.chop(2);
+ tooltipStr.append("</td></tr>");
+ }
+ tooltipStr.append("</table>");
+
+ int ttY = int(rowTree->y())
+ + widgetTimeline()->navigationBar()->height();
+
+ m_variantsToolTip->setText(tooltipStr);
+ m_variantsToolTip->adjustSize();
+ m_variantsToolTip->move(m_widgetTimeline->mapToGlobal({right, ttY}));
+ m_variantsToolTip->raise();
+ m_variantsToolTip->show();
+ m_variantsRowTree = rowTree;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (m_variantsRowTree && !variantsAreaHovered) {
+ m_variantsToolTip->hide();
+ m_variantsRowTree = nullptr;
+ }
+}
+
+// Return next item below [type] item, or item itself
+// Used at least for skipping PlayHead and RowTreeLabelItem
+QGraphicsItem *TimelineGraphicsScene::getItemBelowType(TimelineItem::ItemType type,
+ QGraphicsItem *item,
+ const QPointF &scenePos) const
+{
+ if (item->type() == type) {
+ const QList<QGraphicsItem *> hoverItems = items(scenePos);
+ if (hoverItems.size() > 1)
+ return hoverItems.at(1);
+ }
+ return item;
+}
+
+QPoint TimelineGraphicsScene::getScrollbarOffsets() const
+{
+ QGraphicsView *timelineContent = m_widgetTimeline->viewTimelineContent();
+ return QPoint(timelineContent->verticalScrollBar()->isVisible()
+ ? timelineContent->verticalScrollBar()->width() : 0,
+ timelineContent->horizontalScrollBar()->isVisible()
+ ? timelineContent->horizontalScrollBar()->height() : 0);
+}
+
+void TimelineGraphicsScene::handleInsertKeyframe()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow)
+ selectedRow->getBinding()->InsertKeyframe();
+}
+
+void TimelineGraphicsScene::handleDeleteChannelKeyframes()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow)
+ selectedRow->getBinding()->DeleteAllChannelKeyframes();
+}
+
+void TimelineGraphicsScene::handleSetTimeBarTime()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow && selectedRow->hasDurationBar()) {
+ m_timelineControl->setRowTimeline(selectedRow->rowTimeline());
+ m_timelineControl->showDurationEditDialog();
+ }
+}
+
+void TimelineGraphicsScene::handleMakeComponent()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow) {
+ selectedRow->getBinding()->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_MakeComponent);
+ }
+}
+
+void TimelineGraphicsScene::handleCopyObjectPath()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow) {
+ CStudioClipboard::CopyTextToClipboard(
+ selectedRow->getBinding()->GetObjectPath().toQString());
+ }
+}
+
+void TimelineGraphicsScene::handleEditComponent()
+{
+ RowTree *selectedRow = m_rowManager->selectedRow();
+ if (selectedRow && selectedRow->getBinding()->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_EditComponent)) {
+ selectedRow->getBinding()->OpenAssociatedEditor();
+ }
+}
+
+void TimelineGraphicsScene::handleApplicationFocusLoss()
+{
+ // Hide the timebar and variants tooltips if application loses focus
+ if (!QApplication::focusWidget()) {
+ m_timebarToolTip->hide();
+ m_variantsToolTip->hide();
+ }
+}
+
+void TimelineGraphicsScene::handleShowDISelector(const QString &propertyname,
+ qt3dsdm::Qt3DSDMInstanceHandle inInst,
+ const QPoint &pos)
+{
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = doc->GetPropertySystem()
+ ->GetAggregateInstancePropertyByName(inInst, propertyname.toStdWString().c_str());
+
+ QVector<EDataType> allowedTypes = CDataInputDlg::getAcceptedTypes(
+ doc->GetPropertySystem()->GetDataType(propHandle));
+
+ // Instantiate selector in TimelineGraphicsScene instead of the originating context menu,
+ // as context menu gets destructed when a selection is made.
+ if (!m_dataInputSelector)
+ m_dataInputSelector = new DataInputSelectView(allowedTypes, widgetTimeline());
+
+ QVector<QPair<QString, int>> dataInputList;
+ for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems))
+ dataInputList.append({it->name, it->type});
+ // needs to be set just in case we are reusing an existing datainput selector instance
+ m_dataInputSelector->setMatchingTypes(allowedTypes);
+ m_dataInputSelector->setTypeFilter(DataInputTypeFilter::MatchingTypes);
+ m_dataInputSelector->setData(dataInputList, m_dataInputSelector->getNoneString(),
+ propHandle, inInst);
+ m_dataInputSelector->setCurrentController(doc->GetCurrentController(inInst, propHandle));
+
+ connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged,
+ [&](int handle, int instance, const QString &controllerName) {
+ bool controlled = controllerName != m_dataInputSelector->getNoneString();
+ g_StudioApp.GetCore()->GetDoc()
+ ->SetInstancePropertyControlled(instance, Q3DStudio::CString(), handle,
+ Q3DStudio::CString::fromQString(controllerName),
+ controlled);
+ });
+
+ CDialogs::showWidgetBrowser(widgetTimeline(), m_dataInputSelector, pos);
+}
+
+// Getters
+Ruler *TimelineGraphicsScene::ruler() const { return m_ruler; }
+PlayHead *TimelineGraphicsScene::playHead() const { return m_playHead; }
+TreeHeader *TimelineGraphicsScene::treeHeader() const { return m_treeHeader; }
+RowMover *TimelineGraphicsScene::rowMover() const { return m_rowMover; }
+RowManager *TimelineGraphicsScene::rowManager() const { return m_rowManager; }
+QGraphicsWidget *TimelineGraphicsScene::widgetRoot() const { return m_widgetRoot; }
+KeyframeManager *TimelineGraphicsScene::keyframeManager() const { return m_keyframeManager; }
+QGraphicsLinearLayout *TimelineGraphicsScene::layoutTree() const { return m_layoutTree; }
+QGraphicsLinearLayout *TimelineGraphicsScene::layoutTimeline() const { return m_layoutTimeline; }
+TimelineWidget *TimelineGraphicsScene::widgetTimeline() const { return m_widgetTimeline; }
+Keyframe *TimelineGraphicsScene::pressedKeyframe() const { return m_pressedKeyframe; }
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h
new file mode 100644
index 00000000..146009ee
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h
@@ -0,0 +1,174 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINEGRAPHICSSCENE_H
+#define TIMELINEGRAPHICSSCENE_H
+
+#include "RowTree.h"
+#include "TimelineWidget.h"
+#include "RowTimeline.h"
+#include "RowTypes.h"
+#include "TimelineConstants.h"
+#include "MouseCursor.h"
+#include "DataInputSelectView.h"
+
+#include <QtWidgets/qgraphicsscene.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qpointer.h>
+
+class Ruler;
+class PlayHead;
+class TreeHeader;
+class SelectionRect;
+class RowMover;
+class RowManager;
+class KeyframeManager;
+class TimelineControl;
+class IKeyframesManager;
+struct Keyframe;
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+QT_FORWARD_DECLARE_CLASS(QGraphicsView)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+
+typedef QHash<qt3dsdm::Qt3DSDMInstanceHandle, RowTree::ExpandState> TExpandMap;
+
+class TimelineGraphicsScene : public QGraphicsScene
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineGraphicsScene(TimelineWidget *timelineWidget);
+ virtual ~TimelineGraphicsScene();
+
+ void setTimelineScale(int scale);
+ void setControllerText(const QString &controller);
+ void updateTimelineLayoutWidth();
+ void updateControllerLayoutWidth();
+ void updateController();
+ Ruler *ruler() const;
+ PlayHead *playHead() const;
+ RowManager *rowManager() const;
+ RowMover *rowMover() const;
+ QGraphicsWidget *widgetRoot() const;
+ KeyframeManager *keyframeManager() const;
+ QGraphicsLinearLayout *layoutTree() const;
+ QGraphicsLinearLayout *layoutTimeline() const;
+ TreeHeader *treeHeader() const;
+ double treeWidth() const;
+ TimelineWidget *widgetTimeline() const;
+ void updateTreeWidth(double x);
+ void setMouseCursor(CMouseCursor::Qt3DSMouseCursor cursor);
+ void resetMouseCursor();
+ void updateSnapSteps();
+ TExpandMap &expandMap();
+ void resetMousePressParams();
+ QLabel *timebarTooltip();
+ void updateAutoScrolling(double scenePosY);
+ void stopAutoScroll();
+ QPoint getScrollbarOffsets() const;
+ void handleShowDISelector(const QString &propertyname, qt3dsdm::Qt3DSDMInstanceHandle inInst,
+ const QPoint &pos);
+ void resetPressedKeyframe();
+ Keyframe *pressedKeyframe() const;
+
+protected:
+ bool event(QEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+ void wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) override;
+ void keyPressEvent(QKeyEvent *keyEvent) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ void commitMoveRows();
+ void updateHoverStatus(const QPointF &scenePos);
+ void snap(double &value, bool snapToPlayHead = true);
+ QGraphicsItem *getItemBelowType(TimelineItem::ItemType type,
+ QGraphicsItem *item,
+ const QPointF &scenePos) const;
+ void handleInsertKeyframe();
+ void handleDeleteChannelKeyframes();
+ void handleSetTimeBarTime();
+ void handleMakeComponent();
+ void handleCopyObjectPath();
+ void handleEditComponent();
+ void handleApplicationFocusLoss();
+
+ QGraphicsLinearLayout *m_layoutRoot;
+ QGraphicsLinearLayout *m_layoutTree;
+ QGraphicsLinearLayout *m_layoutTimeline;
+
+ TreeHeader *m_treeHeader;
+ Ruler *m_ruler;
+ PlayHead *m_playHead;
+ TimelineWidget *m_widgetTimeline;
+ QGraphicsWidget *m_widgetRoot;
+ RowMover *m_rowMover = nullptr;
+ QPointer<RowTimeline> m_editedTimelineRow = nullptr;
+ SelectionRect *m_selectionRect;
+ RowManager *m_rowManager = nullptr;
+ KeyframeManager *m_keyframeManager = nullptr;
+ QPointF m_pressPos;
+ QPointF m_pressScreenPos;
+ QList<double> m_snapSteps;
+ CMouseCursor::Qt3DSMouseCursor m_currentCursor = -1;
+ TimelineControl *m_timelineControl = nullptr;
+ DataInputSelectView *m_dataInputSelector = nullptr; // triggered by context menu but owned by
+ // rowtree
+
+ bool m_rulerPressed = false;
+ Keyframe *m_pressedKeyframe = nullptr;
+ bool m_dragging = false;
+ bool m_startRowMoverOnNextDrag = false;
+ bool m_timelineZooming = false;
+ bool m_timelinePanning = false;
+ TimelineControlType m_clickedTimelineControlType = TimelineControlType::None;
+ TreeControlType m_clickedTreeControlType = TreeControlType::None;
+ double m_pressPosInKeyframe;
+ double m_treeWidth = TimelineConstants::TREE_DEFAULT_W;
+ double m_lastAutoScrollX = -1.0;
+ double m_lastAutoScrollY = -1.0;
+ TExpandMap m_expandMap;
+ QPointer<RowTree> m_releaseSelectRow = nullptr;
+ bool m_autoScrollDownOn = false;
+ bool m_autoScrollUpOn = false;
+ QTimer m_autoScrollTimelineTimer;
+ QTimer m_autoScrollTimer;
+ QTimer m_autoScrollTriggerTimer; // triggers m_autoScrollTimer
+ QLabel *m_timebarToolTip = nullptr;
+ QLabel *m_variantsToolTip = nullptr;
+ RowTree* m_variantsRowTree = nullptr;
+};
+
+#endif // TIMELINEGRAPHICSSCENE_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp
new file mode 100644
index 00000000..b8c71f26
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineSplitter.h"
+#include "TimelineConstants.h"
+
+#include <QtGui/qevent.h>
+#include <QtWidgets/qapplication.h>
+
+TimelineSplitter::TimelineSplitter(QWidget *parent) : QWidget(parent)
+{
+ setFixedWidth(TimelineConstants::SPLITTER_W);
+ setAttribute(Qt::WA_Hover, true);
+}
+
+void TimelineSplitter::enterEvent(QEvent *event)
+{
+ qApp->setOverrideCursor(Qt::SplitHCursor);
+ QWidget::enterEvent(event);
+}
+
+void TimelineSplitter::leaveEvent(QEvent *event)
+{
+ qApp->changeOverrideCursor(Qt::ArrowCursor);
+ qApp->restoreOverrideCursor();
+ QWidget::leaveEvent(event);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h
new file mode 100644
index 00000000..1be64967
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINESPLITTER_H
+#define TIMELINESPLITTER_H
+
+#include <QtWidgets/qwidget.h>
+
+QT_FORWARD_DECLARE_CLASS(QEvent)
+
+class TimelineSplitter : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineSplitter(QWidget *parent = nullptr);
+
+ protected:
+ void enterEvent(QEvent *event) override;
+ void leaveEvent(QEvent *event) override;
+};
+
+#endif // TIMELINESPLITTER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp
new file mode 100644
index 00000000..d37825cb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp
@@ -0,0 +1,1292 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineWidget.h"
+#include "TimelineGraphicsScene.h"
+#include "TimelineConstants.h"
+#include "TimelineToolbar.h"
+#include "RowManager.h"
+#include "RowMover.h"
+#include "KeyframeManager.h"
+#include "RowTree.h"
+#include "Keyframe.h"
+#include "PlayHead.h"
+#include "Ruler.h"
+#include "TimelineSplitter.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Dispatch.h"
+#include "MainFrm.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "ClientDataModelBridge.h"
+#include "Bindings/TimelineTranslationManager.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "Bindings/ITimelineTimebar.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "Bindings/Qt3DSDMTimelineItemProperty.h"
+#include "Bindings/TimelineBreadCrumbProvider.h"
+#include "IDocumentEditor.h"
+#include "Control.h"
+#include "TimelineDropTarget.h"
+#include "StudioPreferences.h"
+#include "Dialogs.h"
+#include "TimeEnums.h"
+
+#include <QtGui/qevent.h>
+#include <QtWidgets/qgraphicslinearlayout.h>
+#include <QtWidgets/qgraphicsview.h>
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qscrollbar.h>
+#include <QtWidgets/qslider.h>
+#include <QtWidgets/qlabel.h>
+
+class Eventfilter : public QObject
+{
+public:
+ Eventfilter(QObject *parent) : QObject(parent) {}
+
+ bool eventFilter(QObject *, QEvent *event) override
+ {
+ if (event->type() == QEvent::Wheel) {
+ event->accept();
+ return true;
+ }
+
+ return false;
+ }
+};
+
+TimelineWidget::TimelineWidget(const QSize &preferredSize, QWidget *parent)
+ : QWidget(parent)
+ , m_viewTreeHeader(new TreeHeaderView(this))
+ , m_viewTreeContent(new QGraphicsView(this))
+ , m_viewTimelineHeader(new QGraphicsView(this))
+ , m_viewTimelineContent(new QGraphicsView(this))
+ , m_toolbar(new TimelineToolbar())
+ , m_graphicsScene(new TimelineGraphicsScene(this))
+ , m_preferredSize(preferredSize)
+{
+ int treeWidth = CStudioPreferences::GetTimelineSplitterLocation();
+
+ // Mahmoud_TODO: CTimelineTranslationManager should be eventually removed or cleaned. Already
+ // most of its functionality is implemented in this class
+ m_translationManager = new CTimelineTranslationManager();
+ m_BreadCrumbProvider = new CTimelineBreadCrumbProvider(g_StudioApp.GetCore()->GetDoc());
+
+ setWindowTitle(tr("Timeline", "Title of timeline view"));
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ m_viewTreeHeader->setScene(m_graphicsScene);
+ m_viewTreeHeader->setFixedHeight(TimelineConstants::ROW_H);
+ m_viewTreeHeader->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_viewTreeHeader->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_viewTreeHeader->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_viewTreeHeader->viewport()->installEventFilter(new Eventfilter(this));
+ m_viewTreeHeader->viewport()->setFocusPolicy(Qt::NoFocus);
+ m_viewTreeHeader->setFixedWidth(treeWidth);
+
+ m_viewTreeContent->setScene(m_graphicsScene);
+ m_viewTreeContent->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_viewTreeContent->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_viewTreeContent->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_viewTreeContent->setFixedWidth(treeWidth);
+
+ m_viewTimelineHeader->setScene(m_graphicsScene);
+ m_viewTimelineHeader->setFixedHeight(TimelineConstants::ROW_H);
+ m_viewTimelineHeader->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_viewTimelineHeader->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_viewTimelineHeader->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_viewTimelineHeader->verticalScrollBar()->hide();
+ m_viewTimelineHeader->viewport()->installEventFilter(new Eventfilter(this));
+ m_viewTimelineHeader->viewport()->setFocusPolicy(Qt::NoFocus);
+ m_viewTimelineHeader->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ m_viewTimelineContent->setScene(m_graphicsScene);
+ m_viewTimelineContent->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_viewTimelineContent->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ m_viewTimelineContent->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_viewTimelineContent->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ setTreeWidth(treeWidth);
+
+ auto *layoutTree = new QVBoxLayout;
+ layoutTree->setContentsMargins(QMargins(0, 0, 0, 0));
+ layoutTree->addWidget(m_viewTreeHeader);
+ layoutTree->addWidget(m_viewTreeContent);
+
+ m_splitter = new TimelineSplitter(this);
+
+ auto *layoutTimeline = new QVBoxLayout;
+ layoutTimeline->setContentsMargins(QMargins(0, 0, 0, 0));
+ layoutTimeline->addWidget(m_viewTimelineHeader);
+ layoutTimeline->addWidget(m_viewTimelineContent);
+
+ auto *layoutContent = new QHBoxLayout;
+ layoutContent->setContentsMargins(QMargins(0, 0, 0, TimelineConstants::TOOLBAR_MARGIN));
+ layoutContent->addLayout(layoutTree);
+ layoutContent->addWidget(m_splitter);
+ layoutContent->addLayout(layoutTimeline);
+
+ m_navigationBar = new NavigationBar(this);
+
+ auto *layoutRoot = new QVBoxLayout;
+ layoutRoot->setContentsMargins(0, 0, 0, 0);
+ layoutRoot->setSpacing(0);
+ layoutRoot->addWidget(m_navigationBar);
+ layoutRoot->addLayout(layoutContent);
+ layoutRoot->addWidget(m_toolbar);
+ setLayout(layoutRoot);
+
+ g_StudioApp.GetCore()->GetDoc()->SetKeyframesManager(
+ static_cast<IKeyframesManager *>(m_graphicsScene->keyframeManager()));
+
+ // connect graphics scene geometryChanged
+ connect(m_graphicsScene->widgetRoot(), &QGraphicsWidget::geometryChanged, this, [this]() {
+ const QRectF rect = m_graphicsScene->widgetRoot()->rect();
+
+ m_viewTreeContent->setSceneRect(QRectF(0, TimelineConstants::ROW_H,
+ TimelineConstants::TREE_MAX_W, rect.height()));
+
+ m_viewTimelineHeader->setSceneRect(QRectF(TimelineConstants::TREE_BOUND_W, 0,
+ rect.width() - TimelineConstants::TREE_BOUND_W,
+ TimelineConstants::ROW_H));
+
+ m_viewTimelineContent->setSceneRect(QRectF(TimelineConstants::TREE_BOUND_W,
+ TimelineConstants::ROW_H,
+ rect.width() - TimelineConstants::TREE_BOUND_W,
+ rect.height()));
+
+ m_graphicsScene->playHead()->setHeight(m_graphicsScene->widgetRoot()->geometry().height());
+ });
+
+ // connect timeline and ruler horizontalScrollBars
+ connect(m_viewTimelineContent->horizontalScrollBar(), &QAbstractSlider::valueChanged, this,
+ [this](int value) {
+ m_viewTimelineHeader->horizontalScrollBar()->setValue(value);
+ // Note: Slider value starts from TREE_BOUND_W, make start from 0
+ int viewportX = std::max(0, value - (int)TimelineConstants::TREE_BOUND_W);
+ m_graphicsScene->ruler()->setViewportX(viewportX);
+ });
+
+ // connect timeline and tree verticalScrollBars
+ connect(m_viewTimelineContent->verticalScrollBar(), &QAbstractSlider::valueChanged, this,
+ [this](int value) {
+ m_viewTreeContent->verticalScrollBar()->setValue(value);
+
+ // make sure the 2 scrollbars stay in sync
+ if (m_viewTreeContent->verticalScrollBar()->value() != value) {
+ m_viewTimelineContent->verticalScrollBar()->setValue(
+ m_viewTreeContent->verticalScrollBar()->value());
+ }
+ });
+
+ // connect tree and timeline verticalScrollBars
+ connect(m_viewTreeContent->verticalScrollBar(), &QAbstractSlider::valueChanged, this,
+ [this](int value) {
+ m_viewTimelineContent->verticalScrollBar()->setValue(value);
+ });
+
+ // connect tree and tree header horizontalScrollBars
+ connect(m_viewTreeContent->horizontalScrollBar(), &QAbstractSlider::valueChanged, this,
+ [this](int value) {
+ m_viewTreeHeader->horizontalScrollBar()->setValue(value);
+ // Keep m_viewTreeContent always positioned at 0
+ // This hack is required due to RowTimelineCommentItem (QGraphicsTextItem)
+ // ensuring that all views see the text item when it gets focus or content
+ // changes with setPlainText(). See QTBUG-71241 and QT3DS-1508.
+ if (value != 0)
+ m_viewTreeContent->horizontalScrollBar()->setValue(0);
+ });
+
+ connect(m_toolbar, &TimelineToolbar::newLayerTriggered, this, [this]() {
+ using namespace Q3DStudio;
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+
+ // If active instance is component, just bail as we can't add layers to components
+ qt3dsdm::Qt3DSDMInstanceHandle rootInstance = doc->GetActiveRootInstance();
+ if (m_bridge->GetObjectType(rootInstance) == OBJTYPE_COMPONENT)
+ return;
+
+ qt3dsdm::Qt3DSDMSlideHandle slide = doc->GetActiveSlide();
+ qt3dsdm::Qt3DSDMInstanceHandle layer = doc->GetActiveLayer();
+
+ SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Add Layer"))
+ ->CreateSceneGraphInstance(qt3dsdm::ComposerObjectTypes::Layer, layer, slide,
+ DocumentEditorInsertType::PreviousSibling,
+ CPt(), PRIMITIVETYPE_UNKNOWN, -1);
+ });
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ connect(m_toolbar, &TimelineToolbar::deleteLayerTriggered,
+ [=](){ doc->deleteSelectedObject(); });
+
+ connect(m_toolbar, &TimelineToolbar::gotoTimeTriggered, this, [=]() {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(doc->GetCurrentViewTime(),
+ doc, PLAYHEAD,
+ m_graphicsScene->keyframeManager());
+ });
+
+ connect(m_toolbar, &TimelineToolbar::firstFrameTriggered, this, []() {
+ g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(0);
+ });
+
+ connect(m_toolbar, &TimelineToolbar::stopTriggered, this, []() {
+ g_StudioApp.PlaybackStopNoRestore();
+ });
+
+ connect(m_toolbar, &TimelineToolbar::playTriggered, this, [this]() {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ if (getPlaybackMode() == "Stop at end" && doc->isPlayHeadAtEnd())
+ g_StudioApp.PlaybackRewind();
+
+ g_StudioApp.PlaybackPlay();
+ });
+
+ connect(m_toolbar, &TimelineToolbar::lastFrameTriggered, this, [this]() {
+ long dur = m_graphicsScene->ruler()->duration();
+ g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(dur);
+ });
+
+ connect(m_toolbar, &TimelineToolbar::timelineScaleChanged, this, [this](int scale) {
+ m_graphicsScene->setTimelineScale(scale);
+ });
+
+ connect(m_toolbar, &TimelineToolbar::controllerChanged, this,
+ [this](const QString &controller) {
+ m_graphicsScene->setControllerText(controller);
+ });
+
+ connect(m_toolbar, &TimelineToolbar::variantsFilterToggled, this,
+ std::bind(&TimelineWidget::updateVariantsFiltering, this, nullptr, true));
+
+ connect(m_graphicsScene->ruler(), &Ruler::maxDurationChanged, this, [this]() {
+ m_graphicsScene->updateTimelineLayoutWidth();
+ });
+
+ connect(m_graphicsScene->ruler(), &Ruler::durationChanged, this, [this]() {
+ m_graphicsScene->updateControllerLayoutWidth();
+ });
+
+ // data model listeners
+ g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this);
+ g_StudioApp.GetCore()->GetDispatch()->AddClientPlayChangeListener(this);
+
+ m_asyncUpdateTimer.setInterval(0);
+ m_asyncUpdateTimer.setSingleShot(true);
+ connect(&m_asyncUpdateTimer, &QTimer::timeout, this, &TimelineWidget::onAsyncUpdate);
+}
+
+Q3DStudio::CString TimelineWidget::getPlaybackMode()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMSlideHandle theActiveSlide(doc->GetActiveSlide());
+ // clock has passed the end, check whether needs to switch slide
+ qt3dsdm::Qt3DSDMInstanceHandle instance = doc->GetStudioSystem()->GetSlideSystem()
+ ->GetSlideInstance(theActiveSlide);
+
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::IPropertySystem *propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ qt3dsdm::SValue theValue;
+ propertySystem->GetInstancePropertyValue(instance, bridge->GetSlide().m_PlayMode, theValue);
+ return qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->GetData();
+}
+
+TimelineWidget::~TimelineWidget()
+{
+ CStudioPreferences::SetTimelineSplitterLocation(m_graphicsScene->treeWidth());
+ m_graphicsScene->keyframeManager()->deselectAllKeyframes();
+ delete m_BreadCrumbProvider;
+}
+
+QSize TimelineWidget::sizeHint() const
+{
+ return m_preferredSize;
+}
+
+void TimelineWidget::OnNewPresentation()
+{
+ // Disable scrolling of treeview now that all show related initial singnaling is behind us
+ m_viewTreeHeader->disableScrolling();
+
+ // Register callbacks
+ auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider
+ = studioSystem->GetFullSystemSignalProvider();
+ m_bridge = studioSystem->GetClientDataModelBridge();
+
+ m_connections.push_back(theSignalProvider->ConnectActiveSlide(
+ std::bind(&TimelineWidget::onActiveSlide, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)));
+
+ CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch();
+
+ m_connections.push_back(theDispatch->ConnectSelectionChange(
+ std::bind(&TimelineWidget::onSelectionChange, this, std::placeholders::_1)));
+
+ // object created/deleted
+ m_connections.push_back(theSignalProvider->ConnectInstanceCreated(
+ std::bind(&TimelineWidget::onAssetCreated, this, std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectInstanceDeleted(
+ std::bind(&TimelineWidget::onAssetDeleted, this, std::placeholders::_1)));
+
+ // animation created/deleted
+ m_connections.push_back(theSignalProvider->ConnectAnimationCreated(
+ std::bind(&TimelineWidget::onAnimationCreated, this,
+ std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theSignalProvider->ConnectAnimationDeleted(
+ std::bind(&TimelineWidget::onAnimationDeleted, this,
+ std::placeholders::_2, std::placeholders::_3)));
+
+ // keyframe added/deleted
+ m_connections.push_back(theSignalProvider->ConnectKeyframeInserted(
+ std::bind(&TimelineWidget::onKeyframeInserted, this,
+ std::placeholders::_1, std::placeholders::_2)));
+ m_connections.push_back(theSignalProvider->ConnectKeyframeErased(
+ std::bind(&TimelineWidget::onKeyframeDeleted, this,
+ std::placeholders::_1, std::placeholders::_2)));
+ m_connections.push_back(theSignalProvider->ConnectKeyframeUpdated(
+ std::bind(&TimelineWidget::onKeyframeUpdated, this, std::placeholders::_1)));
+ m_connections.push_back(theSignalProvider->ConnectInstancePropertyValue(
+ std::bind(&TimelineWidget::onPropertyChanged, this,
+ std::placeholders::_1, std::placeholders::_2)));
+ m_connections.push_back(theSignalProvider->ConnectFirstKeyframeDynamicSet(
+ std::bind(&TimelineWidget::onFirstKeyframeDynamicSet, this,
+ std::placeholders::_1)));
+
+ // action created/deleted
+ m_connections.push_back(theSignalProvider->ConnectActionCreated(
+ std::bind(&TimelineWidget::onActionEvent, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theSignalProvider->ConnectActionDeleted(
+ std::bind(&TimelineWidget::onActionEvent, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+
+ // connect property linked/unlinked
+ m_connections.push_back(theSignalProvider->ConnectPropertyLinked(
+ std::bind(&TimelineWidget::onPropertyLinked, this,
+ std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theSignalProvider->ConnectPropertyUnlinked(
+ std::bind(&TimelineWidget::onPropertyUnlinked, this,
+ std::placeholders::_2, std::placeholders::_3)));
+
+ // object add, remove, move
+ Q3DStudio::CGraph &theGraph(*g_StudioApp.GetCore()->GetDoc()->GetAssetGraph());
+ m_connections.push_back(theGraph.ConnectChildAdded(
+ std::bind(&TimelineWidget::onChildAdded, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theGraph.ConnectChildRemoved(
+ std::bind(&TimelineWidget::onChildRemoved, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
+ m_connections.push_back(theGraph.ConnectChildMoved(
+ std::bind(&TimelineWidget::onChildMoved, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)));
+
+ // Connect toolbar play/stop now when m_pMainWnd exists
+ connect(g_StudioApp.m_pMainWnd, &CMainFrame::playStateChanged,
+ m_toolbar, &TimelineToolbar::updatePlayButtonState);
+
+ // Clear active slide
+ m_activeSlide = qt3dsdm::Qt3DSDMSlideHandle();
+
+ // Reset timeline time
+ OnTimeChanged(0);
+}
+
+void TimelineWidget::OnClosingPresentation()
+{
+ m_connections.clear();
+ m_graphicsScene->expandMap().clear();
+}
+
+void TimelineWidget::OnTimeChanged(long inTime)
+{
+ m_graphicsScene->playHead()->setTime(inTime);
+ m_toolbar->setTime(inTime);
+
+ double left = m_viewTimelineHeader->horizontalScrollBar()->value()
+ + TimelineConstants::PLAYHEAD_W * .5;
+ double right = m_viewTimelineHeader->horizontalScrollBar()->value()
+ + m_viewTimelineHeader->width() - TimelineConstants::RULER_EDGE_OFFSET;
+ double playHeadX = m_graphicsScene->playHead()->x();
+
+ if (playHeadX < left || playHeadX > right) {
+ m_viewTimelineContent->ensureVisible(m_graphicsScene->playHead()->x()
+ - TimelineConstants::PLAYHEAD_W * .5,
+ m_viewTimelineContent->verticalScrollBar()->value()
+ + TimelineConstants::ROW_H,
+ TimelineConstants::PLAYHEAD_W, 0, 0, 0);
+ }
+
+ if (inTime <= 0 && g_StudioApp.IsPlaying() && getPlaybackMode() == "Ping"
+ && !g_StudioApp.isPlaybackPreviewOn()) {
+ g_StudioApp.PlaybackStopNoRestore();
+ }
+}
+
+void TimelineWidget::onActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex,
+ const qt3dsdm::Qt3DSDMSlideHandle &inSlide)
+{
+ Q_UNUSED(inMaster);
+ Q_UNUSED(inIndex);
+
+ if (m_activeSlide == inSlide)
+ return;
+
+ m_activeSlide = inSlide;
+
+ if (!m_fullReconstruct) {
+ m_fullReconstruct = true;
+ m_graphicsScene->resetMousePressParams();
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ }
+}
+
+void TimelineWidget::insertToHandlesMapRecursive(Qt3DSDMTimelineItemBinding *binding)
+{
+ insertToHandlesMap(binding);
+
+ const QList<ITimelineItemBinding *> children = binding->GetChildren();
+ for (auto child : children)
+ insertToHandlesMapRecursive(static_cast<Qt3DSDMTimelineItemBinding *>(child));
+}
+
+void TimelineWidget::insertToHandlesMap(Qt3DSDMTimelineItemBinding *binding)
+{
+ m_handlesMap.insert(binding->GetInstance(), binding->getRowTree());
+}
+
+void TimelineWidget::onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable)
+{
+ // Full update will set selection anyway
+ if (m_fullReconstruct)
+ return;
+
+ qt3dsdm::TInstanceHandleList theInstances = inNewSelectable.GetSelectedInstances();
+
+ // First deselect all items in UI
+ m_graphicsScene->rowManager()->clearSelection();
+
+ if (theInstances.size() > 0) {
+ for (size_t idx = 0, end = theInstances.size(); idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(theInstances[idx]);
+
+ if (g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->IsInstance(theInstance)) {
+ auto *binding = getBindingForHandle(theInstance, m_binding);
+ if (binding)
+ m_graphicsScene->rowManager()->setRowSelection(binding->getRowTree(), true);
+ }
+ }
+ }
+}
+
+void TimelineWidget::onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ if (m_fullReconstruct)
+ return;
+
+ if (m_bridge->IsSceneGraphInstance(inInstance)) {
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(inInstance, m_binding);
+
+ if (!binding) {
+ // if binding is not found, refresh it (so far the only known case where this is needed
+ // is when setting a subpresentation on a ref mat row and checking 'Detach material')
+ m_fullReconstruct = true;
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ } else {
+ if (!binding->getRowTree()) { // row doesn't exist
+ auto parentInstance = m_bridge->GetParentInstance(inInstance);
+ Qt3DSDMTimelineItemBinding *bindingParent = getBindingForHandle(parentInstance,
+ m_binding);
+ if (bindingParent) {
+ RowTree *row = m_graphicsScene->rowManager()
+ ->createRowFromBinding(binding, bindingParent->getRowTree());
+ row->updateSubpresentations();
+ insertToHandlesMap(binding);
+
+ // refresh the created object variants if it has a variants property set.
+ if (m_bridge->GetObjectType(inInstance) & OBJTYPE_IS_VARIANT) {
+ const auto propertySystem = g_StudioApp.GetCore()->GetDoc()
+ ->GetPropertySystem();
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(inInstance,
+ m_bridge->getVariantsProperty(inInstance),
+ sValue)) {
+ if (qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->GetLength() != 0)
+ refreshVariants(inInstance);
+ }
+ }
+ } else {
+ qWarning() << "Binding parent was not found.";
+ }
+ }
+ }
+ }
+}
+
+void TimelineWidget::onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ if (m_fullReconstruct)
+ return;
+
+ RowTree *row = m_handlesMap.value(inInstance);
+ if (row) { // scene object exists
+ row->updateSubpresentations(-1);
+ m_graphicsScene->rowManager()->deleteRow(row);
+ m_handlesMap.remove(inInstance);
+ m_graphicsScene->expandMap().remove(inInstance);
+ // Ensure row deletions are finalized
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ }
+}
+
+void TimelineWidget::onAnimationCreated(qt3dsdm::Qt3DSDMInstanceHandle parentInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle property)
+{
+ if (m_fullReconstruct)
+ return;
+
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(parentInstance, m_binding);
+ if (binding) {
+ ITimelineItemProperty *propBinding = binding->GetPropertyBinding(property);
+
+ // create the binding if doesn't exist
+ if (!propBinding) {
+ propBinding = binding->GetOrCreatePropertyBinding(property);
+
+ // create the property UI row
+ RowTree *propRow = m_graphicsScene->rowManager()
+ ->getOrCreatePropertyRow(binding->getRowTree(), propBinding->GetName().toQString(),
+ binding->getAnimatedPropertyIndex(property));
+
+ // connect the row and binding
+ propBinding->setRowTree(propRow);
+ propRow->setPropBinding(propBinding);
+
+ // add keyframes
+ for (int i = 0; i < propBinding->GetKeyframeCount(); i++) {
+ IKeyframe *kf = propBinding->GetKeyframeByIndex(i);
+ Keyframe *kfUI = m_graphicsScene->keyframeManager()->insertKeyframe(
+ propRow->rowTimeline(), kf->GetTime(), false).at(0);
+
+ kf->setUI(kfUI);
+ kfUI->binding = static_cast<Qt3DSDMTimelineKeyframe *>(kf);
+ kfUI->dynamic = kf->IsDynamic();
+ }
+
+ propRow->update();
+ }
+ }
+}
+
+void TimelineWidget::onAnimationDeleted(qt3dsdm::Qt3DSDMInstanceHandle parentInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle property)
+{
+ if (m_fullReconstruct)
+ return;
+
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(parentInstance, m_binding);
+ if (binding) {
+ ITimelineItemProperty *propBinding = binding->GetPropertyBinding(property);
+ bool propAnimated = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetAnimationSystem()->IsPropertyAnimated(parentInstance, property);
+
+ if (propBinding && !propAnimated) {
+ m_graphicsScene->rowManager()->deleteRow(propBinding->getRowTree());
+ binding->RemovePropertyRow(property);
+ m_keyframeChangesMap.insert(parentInstance, property);
+ // Ensure row deletions are finalized
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ }
+ }
+}
+
+void TimelineWidget::onKeyframeInserted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe)
+{
+ if (m_fullReconstruct)
+ return;
+
+ refreshKeyframe(inAnimation, inKeyframe, ETimelineKeyframeTransaction_Add);
+}
+
+void TimelineWidget::onKeyframeDeleted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe)
+{
+ if (m_fullReconstruct)
+ return;
+
+ refreshKeyframe(inAnimation, inKeyframe, ETimelineKeyframeTransaction_Delete);
+}
+
+void TimelineWidget::onKeyframeUpdated(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe)
+{
+ if (m_fullReconstruct)
+ return;
+
+ qt3dsdm::IAnimationCore *theAnimationCore =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetAnimationCore();
+ if (theAnimationCore->KeyframeValid(inKeyframe)) {
+ qt3dsdm::Qt3DSDMAnimationHandle theAnimationHandle =
+ theAnimationCore->GetAnimationForKeyframe(inKeyframe);
+ refreshKeyframe(theAnimationHandle, inKeyframe, ETimelineKeyframeTransaction_Update);
+ }
+}
+
+void TimelineWidget::refreshKeyframe(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ ETimelineKeyframeTransaction inTransaction)
+{
+ qt3dsdm::CStudioSystem *studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem();
+ if (studioSystem->GetAnimationCore()->AnimationValid(inAnimation)) {
+ qt3dsdm::SAnimationInfo theAnimationInfo =
+ studioSystem->GetAnimationCore()->GetAnimationInfo(inAnimation);
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(theAnimationInfo.m_Instance,
+ m_binding);
+ if (binding) {
+ binding->RefreshPropertyKeyframe(theAnimationInfo.m_Property, inKeyframe,
+ inTransaction);
+
+ // update UI asynchronously to make sure binding is completely up to date.
+ m_keyframeChangesMap.insert(theAnimationInfo.m_Instance, theAnimationInfo.m_Property);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ }
+ }
+}
+
+void TimelineWidget::onFirstKeyframeDynamicSet(qt3dsdm::Qt3DSDMAnimationHandle inAnimation)
+{
+ refreshKeyframe(inAnimation, 0, ETimelineKeyframeTransaction_DynamicChanged);
+}
+
+void TimelineWidget::updateActionStates(const QSet<RowTree *> &rows)
+{
+ for (RowTree *row : rows) {
+ Qt3DSDMTimelineItemBinding *binding =
+ static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding());
+
+ RowTree::ActionStates states = RowTree::ActionState::None;
+ if (binding->HasAction(true)) // has master action
+ states |= RowTree::ActionState::MasterAction;
+ else if (binding->HasAction(false)) // has action
+ states |= RowTree::ActionState::Action;
+
+ if (binding->ChildrenHasAction(true)) // children have master action
+ states |= RowTree::ActionState::MasterChildAction;
+ else if (binding->ChildrenHasAction(false)) // children have action
+ states |= RowTree::ActionState::ChildAction;
+
+ if (row->isComponent()) {
+ if (binding->ComponentHasAction(true)) // component has master action
+ states |= RowTree::ActionState::MasterComponentAction;
+ else if (binding->ComponentHasAction(false)) // component has action
+ states |= RowTree::ActionState::ComponentAction;
+ }
+ row->setActionStates(states);
+ }
+}
+
+void TimelineWidget::setTreeWidth(int width)
+{
+ int treeWidth = qBound(int(TimelineConstants::TREE_MIN_W), width,
+ int(TimelineConstants::TREE_MAX_W));
+
+ m_viewTreeHeader->setFixedWidth(treeWidth);
+ m_viewTreeContent->setFixedWidth(treeWidth);
+ m_graphicsScene->updateTreeWidth(treeWidth);
+}
+
+void TimelineWidget::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ if (m_fullReconstruct)
+ return;
+
+ if (!m_bridge->IsSceneGraphInstance(inInstance))
+ return;
+
+ const SDataModelSceneAsset &asset = m_bridge->GetSceneAsset();
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ auto ctrldPropHandle = doc->GetPropertySystem()
+ ->GetAggregateInstancePropertyByName(inInstance, L"controlledproperty");
+
+ if (inProperty == asset.m_Eyeball || inProperty == asset.m_Locked || inProperty == asset.m_Shy
+ || inProperty == asset.m_StartTime || inProperty == asset.m_EndTime
+ || inProperty == m_bridge->GetNameProperty() || inProperty == ctrldPropHandle) {
+ m_dirtyProperties.insert(inInstance, inProperty);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ } else if (inProperty == m_bridge->GetSceneImage().m_SubPresentation
+ || (inProperty == m_bridge->GetSourcePathProperty()
+ && m_bridge->GetObjectType(inInstance) == OBJTYPE_LAYER)) {
+ m_subpresentationChanges.insert(inInstance);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ } else if (inProperty == m_bridge->getVariantsProperty(inInstance)) {
+ qt3dsdm::SValue sValue;
+ if (doc->GetPropertySystem()->GetInstancePropertyValue(inInstance, inProperty, sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ if (!propVal.isEmpty()) {
+ QStringList tagPairs = propVal.split(QLatin1Char(','));
+ QStringList groups;
+ for (int i = 0; i < tagPairs.size(); ++i) {
+ QString group = tagPairs[i].left(tagPairs[i].indexOf(QLatin1Char(':')));
+ if (!groups.contains(group))
+ groups.append(group);
+ }
+
+ m_variantsMap[inInstance] = groups;
+ } else {
+ m_variantsMap[inInstance].clear();
+ }
+
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+ }
+ }
+}
+
+void TimelineWidget::onAsyncUpdate()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+
+ if (m_fullReconstruct) {
+ m_translationManager->Clear();
+ m_binding = static_cast<Qt3DSDMTimelineItemBinding *>(
+ m_translationManager->GetOrCreate(
+ doc->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(m_activeSlide)));
+ m_graphicsScene->rowManager()->recreateRowsFromBinding(m_binding);
+ m_handlesMap.clear();
+ insertToHandlesMapRecursive(m_binding);
+ updateActionStates(m_handlesMap.values().toSet());
+ m_navigationBar->updateNavigationItems(m_BreadCrumbProvider);
+ m_graphicsScene->updateSnapSteps();
+ m_fullReconstruct = false;
+ m_graphicsScene->updateController();
+ onSelectionChange(doc->GetSelectedValue());
+ m_toolbar->setNewLayerEnabled(!m_graphicsScene->rowManager()->isComponentRoot());
+ refreshVariants();
+ updateVariantsFiltering();
+
+ // update sub-presentation indicators
+ for (auto *row : qAsConst(m_handlesMap))
+ row->updateSubpresentations();
+ } else {
+ if (!m_moveMap.isEmpty()) {
+ // Flip the hash around so that we collect moves by parent.
+ // We can't do this with m_moveMap originally, as things break if
+ // same row receives consecutive moves to different parents.
+ QMultiHash<int, int> flippedMap;
+ QHashIterator<int, int> it(m_moveMap);
+ while (it.hasNext()) {
+ it.next();
+ flippedMap.insert(it.value(), it.key());
+ }
+ const auto parentHandles = flippedMap.keys();
+ QSet<RowTree *> expandRows;
+ for (const auto parentHandle : parentHandles) {
+ QSet<int> movedInstances(flippedMap.values(parentHandle).toSet());
+ RowTree *rowParent = m_handlesMap.value(parentHandle);
+ if (rowParent) {
+ Qt3DSDMTimelineItemBinding *bindingParent
+ = static_cast<Qt3DSDMTimelineItemBinding *>(rowParent->getBinding());
+ if (bindingParent) {
+ // Resolve indexes for handles. QMap used for its automatic sorting by keys.
+ QMap<int, int> indexMap;
+ bindingParent->getTimeContextIndices(movedInstances, indexMap);
+ QMapIterator<int, int> indexIt(indexMap);
+ while (indexIt.hasNext()) {
+ indexIt.next();
+ RowTree *row = m_handlesMap.value(indexIt.value());
+ if (row) {
+ bool isReparent = rowParent != row->parentRow();
+ if (isReparent)
+ row->updateSubpresentations(-1);
+ rowParent->addChildAt(row, indexIt.key());
+ if (isReparent)
+ row->updateSubpresentations(1);
+ }
+ }
+ expandRows.insert(rowParent);
+ }
+ }
+ }
+
+ // Make sure selections on UI matches bindings
+ onSelectionChange(doc->GetSelectedValue());
+
+ // Expand the parents of the added rows, but only for topmost ancestors of the moved
+ // rows as expanding all moved rows indiscriminately would not work intuitively
+ // in case of e.g. mass delete undo.
+ // Rest of expandRows will be force-updated to their current state to ensure
+ // their children are in proper state. This is relevant in cases like grouping,
+ // where existing potentially visible rows are moved under newly created group,
+ // which is collapsed by default.
+ for (RowTree *row : qAsConst(expandRows)) {
+ if (!expandRows.contains(row->parentRow()))
+ m_graphicsScene->rowManager()->ensureRowExpandedAndVisible(row, true);
+ else
+ row->updateExpandStatus(row->expandState(), false, true);
+ }
+ }
+ // Update properties
+ if (!m_dirtyProperties.isEmpty()) {
+ const SDataModelSceneAsset &asset = m_bridge->GetSceneAsset();
+ qt3dsdm::Qt3DSDMPropertyHandle nameProp = m_bridge->GetNameProperty();
+ const auto instances = m_dirtyProperties.keys();
+ QSet<RowTree *> updateArrowParents;
+ for (int instance : instances) {
+ bool filterProperty = false;
+ bool timeProperty = false;
+ bool nameProperty = false;
+ const auto props = m_dirtyProperties.values(instance);
+ const auto ctrldPropHandle =
+ doc->GetPropertySystem()->GetAggregateInstancePropertyByName(
+ instance, L"controlledproperty");
+ for (auto prop : props) {
+ filterProperty = filterProperty || prop == asset.m_Eyeball
+ || prop == asset.m_Locked || prop == asset.m_Shy
+ || prop == ctrldPropHandle;
+ timeProperty = timeProperty
+ || prop == asset.m_StartTime || prop == asset.m_EndTime;
+ nameProperty = nameProperty || prop == nameProp;
+ }
+ if (filterProperty || timeProperty || nameProperty) {
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(instance, m_binding);
+ if (binding) {
+ RowTree *row = binding->getRowTree();
+ if (row) {
+ if (timeProperty) {
+ row->rowTimeline()->updateDurationFromBinding();
+ m_graphicsScene->rowManager()->updateRulerDuration();
+ }
+ if (filterProperty) {
+ row->updateFromBinding();
+ m_graphicsScene->rowManager()->updateFiltering(row);
+ // Filtering changes to children affect arrow visibility in parents.
+ if (row->parentRow())
+ updateArrowParents.insert(row->parentRow());
+
+ update();
+ }
+ if (nameProperty)
+ row->updateLabel();
+ }
+ }
+ }
+ }
+ for (RowTree *row : qAsConst(updateArrowParents))
+ row->updateArrowVisibility();
+ m_graphicsScene->updateSnapSteps();
+ }
+ if (!m_actionChanges.isEmpty()) {
+ QSet<RowTree *> rowSet;
+ for (int id : qAsConst(m_actionChanges)) {
+ RowTree *row = m_handlesMap.value(id);
+ if (row) {
+ rowSet.insert(row);
+ RowTree *parentRow = row->parentRow();
+ while (parentRow) {
+ rowSet.insert(parentRow);
+ parentRow = parentRow->parentRow();
+ }
+ }
+ }
+ updateActionStates(rowSet);
+ }
+
+ if (!m_subpresentationChanges.isEmpty()) {
+ for (int id : qAsConst(m_subpresentationChanges)) {
+ RowTree *row = m_handlesMap.value(id);
+ if (row)
+ row->updateSubpresentations();
+ }
+ }
+
+ if (!m_keyframeChangesMap.isEmpty()) {
+ const auto objects = m_keyframeChangesMap.keys();
+ for (int object : objects) {
+ RowTree *row = m_handlesMap.value(object);
+ if (row) {
+ const auto properties = m_keyframeChangesMap.values(object);
+ row->rowTimeline()->updateKeyframesFromBinding(properties);
+ }
+ }
+ m_graphicsScene->updateSnapSteps();
+ }
+
+ if (!m_variantsMap.isEmpty()) {
+ const auto instances = m_variantsMap.keys();
+ for (int instance : instances) {
+ if (m_handlesMap.contains(instance)) {
+ RowTree *row = m_handlesMap[instance];
+ if (row) {
+ row->updateVariants(m_variantsMap[instance]); // variants groups names
+ updateVariantsFiltering(row);
+ }
+ }
+ }
+ }
+ }
+ m_dirtyProperties.clear();
+ m_moveMap.clear();
+ m_actionChanges.clear();
+ m_variantsMap.clear();
+ m_subpresentationChanges.clear();
+ m_keyframeChangesMap.clear();
+ m_graphicsScene->rowManager()->finalizeRowDeletions();
+}
+
+void TimelineWidget::onActionEvent(qt3dsdm::Qt3DSDMActionHandle inAction,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner)
+{
+ Q_UNUSED(inAction)
+ Q_UNUSED(inSlide)
+
+ if (m_fullReconstruct)
+ return;
+
+ m_actionChanges.insert(inOwner);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+}
+
+void TimelineWidget::onPropertyLinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ if (m_fullReconstruct)
+ return;
+
+ Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(inInstance, m_binding);
+
+ if (binding) {
+ ITimelineItemProperty *propBinding = binding->GetPropertyBinding(inProperty);
+
+ if (propBinding) {
+ RowTree *propRow = binding->GetPropertyBinding(inProperty)->getRowTree();
+
+ // this call deletes and recreates the property binding so we need to reconnect the
+ // property binding and its RowTree, and the keyframes also
+ binding->OnPropertyLinked(inProperty);
+
+ propBinding = binding->GetPropertyBinding(inProperty);
+ propBinding->setRowTree(propRow);
+ propRow->setPropBinding(propBinding);
+
+ // recreate and connect prop row keyframes
+ m_graphicsScene->keyframeManager()->deleteKeyframes(propRow->rowTimeline(), false);
+ for (int i = 0; i < propBinding->GetKeyframeCount(); i++) {
+ IKeyframe *kf = propBinding->GetKeyframeByIndex(i);
+ Keyframe *kfUI = m_graphicsScene->keyframeManager()->insertKeyframe(
+ propRow->rowTimeline(), kf->GetTime(), false).at(0);
+
+ kf->setUI(kfUI);
+ kfUI->binding = static_cast<Qt3DSDMTimelineKeyframe *>(kf);
+ kfUI->dynamic = kf->IsDynamic();
+ }
+ }
+ }
+}
+
+void TimelineWidget::onPropertyUnlinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+{
+ onPropertyLinked(inInstance, inProperty);
+}
+
+void TimelineWidget::onChildAdded(int inParent, int inChild, long inIndex)
+{
+ Q_UNUSED(inIndex)
+
+ if (m_fullReconstruct)
+ return;
+
+ // Handle row moves async, as we won't be able to get the final order correct otherwise
+ m_moveMap.insert(inChild, inParent);
+ m_actionChanges.insert(inParent);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+}
+
+void TimelineWidget::onChildRemoved(int inParent, int inChild, long inIndex)
+{
+ Q_UNUSED(inParent)
+ Q_UNUSED(inChild)
+ Q_UNUSED(inIndex)
+
+ m_actionChanges.insert(inParent);
+ if (!m_asyncUpdateTimer.isActive())
+ m_asyncUpdateTimer.start();
+
+ // Note: Actual child removal handling unimplemented by design, see QT3DS-1684
+}
+
+void TimelineWidget::onChildMoved(int inParent, int inChild, long inOldIndex,
+ long inNewIndex)
+{
+ Q_UNUSED(inOldIndex)
+
+ // Move and add are essentially the same operation
+ onChildAdded(inParent, inChild, inNewIndex);
+}
+
+CDropTarget *TimelineWidget::FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType)
+{
+ Q_UNUSED(inFlags)
+
+ CTimeLineDropTarget *theTarget = new CTimeLineDropTarget();
+
+ int mouseY = inMousePoint.y - m_navigationBar->height()
+ + viewTreeContent()->verticalScrollBar()->value()
+ - viewTreeContent()->verticalScrollBar()->minimum();
+ RowMover *mover = m_graphicsScene->rowMover();
+ mover->updateTargetRow(QPointF(inMousePoint.x, mouseY), objectType, fileType);
+
+ if (mover->insertionTarget() && !mover->insertionTarget()->isProperty()) {
+ mover->insertionTarget()->getBinding()->SetDropTarget(theTarget);
+
+ switch (mover->insertionType()) {
+ case Q3DStudio::DocumentEditorInsertType::LastChild:
+ theTarget->SetDestination(EDROPDESTINATION_ON);
+ break;
+ case Q3DStudio::DocumentEditorInsertType::PreviousSibling:
+ theTarget->SetDestination(EDROPDESTINATION_ABOVE);
+ break;
+ default:
+ theTarget->SetDestination(EDROPDESTINATION_BELOW);
+ break;
+ }
+ }
+ m_graphicsScene->updateAutoScrolling(mouseY);
+
+ return theTarget;
+}
+
+void TimelineWidget::OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ Q_UNUSED(inFlags)
+
+ if (inPoint.x == -1 && inPoint.y == -1) { // drag leave
+ // upon cancelling a DnD, the mouse press event fires, this bool is to prevent that
+ m_blockMousePress = true;
+ QTimer::singleShot(500, [this]() {
+ m_blockMousePress = false;
+ });
+ }
+}
+
+bool TimelineWidget::blockMousePress() const
+{
+ return m_blockMousePress;
+}
+
+CPt TimelineWidget::GetPreferredSize()
+{
+ return CPt(m_preferredSize.width(), m_preferredSize.height());
+}
+
+void TimelineWidget::SetSize(long inX, long inY)
+{
+ setFixedSize(inX, inY);
+}
+
+// If views are interactive they block the DnD. If we could think of a way to make them do not block
+// DnD, then this method can be removed (and it's callers)
+void TimelineWidget::enableDnD(bool b)
+{
+ m_viewTreeHeader->setEnabled(!b);
+ m_viewTreeContent->setEnabled(!b);
+ m_viewTimelineHeader->setEnabled(!b);
+ m_viewTimelineContent->setEnabled(!b);
+
+ if (!b) { // object successfully dropped on the timeline tree
+ m_graphicsScene->rowMover()->end(true);
+ m_graphicsScene->stopAutoScroll();
+ }
+}
+
+Qt3DSDMTimelineItemBinding *TimelineWidget::getBindingForHandle(int handle,
+ Qt3DSDMTimelineItemBinding *binding) const
+{
+ const RowTree *row = m_handlesMap.value(handle);
+ if (row && row->getBinding())
+ return static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding());
+
+ if (binding) {
+ if (binding->GetInstance().GetHandleValue() == handle)
+ return binding;
+
+ const QList<ITimelineItemBinding *> children = binding->GetChildren();
+ for (auto child : children) {
+ Qt3DSDMTimelineItemBinding *b = getBindingForHandle(handle,
+ static_cast<Qt3DSDMTimelineItemBinding *>(child));
+
+ if (b)
+ return b;
+ }
+ }
+ return nullptr;
+}
+
+void TimelineWidget::mousePressEvent(QMouseEvent *event)
+{
+ if (childAt(event->pos()) == m_splitter)
+ m_splitterPressed = true;
+ g_StudioApp.setLastActiveView(this);
+}
+
+void TimelineWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ if (m_splitterPressed)
+ setTreeWidth(event->pos().x() - m_splitter->size().width() * .5);
+}
+
+void TimelineWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ Q_UNUSED(event)
+ m_splitterPressed = false;
+}
+
+QGraphicsView *TimelineWidget::viewTimelineContent() const
+{
+ return m_viewTimelineContent;
+}
+
+QGraphicsView *TimelineWidget::viewTreeContent() const
+{
+ return m_viewTreeContent;
+}
+
+TimelineToolbar *TimelineWidget::toolbar() const
+{
+ return m_toolbar;
+}
+
+bool TimelineWidget::dndActive() const
+{
+ return m_graphicsScene->rowMover()->isActive();
+}
+
+bool TimelineWidget::hasSelectedKeyframes() const
+{
+ return m_graphicsScene->keyframeManager()->hasSelectedKeyframes();
+}
+
+QVector<RowTree *> TimelineWidget::selectedRows() const
+{
+ return m_graphicsScene->rowManager()->selectedRows();
+}
+
+void TimelineWidget::openBarColorDialog()
+{
+ auto rows = selectedRows();
+ if (rows.isEmpty())
+ return;
+
+ // Note: Setup color dialog with bar color of last selected row as it can only default to one.
+ QColor previousColor = rows.first()->rowTimeline()->barColor();
+ CDialogs *dialogs = g_StudioApp.GetDialogs();
+ connect(dialogs, &CDialogs::onColorChanged, this, &TimelineWidget::onTimeBarColorChanged);
+ QColor selectedColor = dialogs->displayColorDialog(previousColor);
+ disconnect(dialogs, &CDialogs::onColorChanged, this, &TimelineWidget::onTimeBarColorChanged);
+ setSelectedTimeBarsColor(selectedColor, selectedColor == previousColor);
+}
+
+void TimelineWidget::onTimeBarColorChanged(const QColor &color)
+{
+ setSelectedTimeBarsColor(color, true);
+}
+
+// Set the color of all currently selected timeline bars.
+// When preview, only set the UI without property changes.
+void TimelineWidget::setSelectedTimeBarsColor(const QColor &color, bool preview)
+{
+ using namespace Q3DStudio; // Needed for SCOPED_DOCUMENT_EDITOR macro
+ const auto rows = selectedRows();
+ for (RowTree *row : rows) {
+ row->rowTimeline()->setBarColor(color);
+ if (!preview) {
+ Qt3DSDMTimelineItemBinding *timelineItemBinding =
+ static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding());
+ SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(),
+ QObject::tr("Set Timebar Color"))
+ ->SetTimebarColor(timelineItemBinding->GetInstanceHandle(), color);
+ }
+ }
+}
+
+void TimelineWidget::refreshVariants(int instance)
+{
+ const auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem();
+ QVector<int> instances;
+ if (instance)
+ instances << instance;
+ else
+ instances = g_StudioApp.GetCore()->GetDoc()->getVariantInstances();
+
+ for (auto instance : qAsConst(instances)) {
+ if (!m_handlesMap.contains(instance))
+ continue;
+
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance,
+ m_bridge->getVariantsProperty(instance),
+ sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ if (!propVal.isEmpty()) {
+ QStringList tagPairs = propVal.split(QLatin1Char(','));
+ QStringList groups;
+ for (int i = 0; i < tagPairs.size(); ++i) {
+ QString group = tagPairs[i].left(tagPairs[i].indexOf(QLatin1Char(':')));
+ if (!groups.contains(group))
+ groups.append(group);
+ }
+
+ m_handlesMap[instance]->updateVariants(groups);
+ } else {
+ m_handlesMap[instance]->updateVariants({});
+ }
+ }
+ }
+}
+
+void TimelineWidget::updateVariantsFiltering(RowTree *row, bool force)
+{
+ if (force || m_toolbar->isVariantsFilterOn())
+ m_graphicsScene->rowManager()->updateFiltering(row);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h
new file mode 100644
index 00000000..a45be156
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINEWIDGET_H
+#define TIMELINEWIDGET_H
+
+#include <QtWidgets/qwidget.h>
+#include <QtCore/qtimer.h>
+#include "DispatchListeners.h"
+#include "ObjectListModel.h"
+#include "Qt3DSDMHandles.h"
+#include "Qt3DSDMSignals.h"
+#include "SelectedValueImpl.h"
+#include "TreeHeaderView.h"
+#include "Bindings/Qt3DSDMTimeline.h"
+#include "NavigationBar.h"
+#include "Control.h"
+
+class RowTree;
+class TimelineToolbar;
+class TimelineSplitter;
+class TimelineGraphicsScene;
+class CTimelineTranslationManager;
+class Qt3DSDMTimelineItemBinding;
+class CClientDataModelBridge;
+class IBreadCrumbProvider;
+
+QT_FORWARD_DECLARE_CLASS(QMouseEvent)
+QT_FORWARD_DECLARE_CLASS(QGraphicsView)
+
+class TimelineWidget : public QWidget,
+ public CPresentationChangeListener,
+ public CClientPlayChangeListener,
+ public CControl
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineWidget(const QSize &preferredSize, QWidget *parent = nullptr);
+ ~TimelineWidget();
+
+ QSize sizeHint() const override;
+
+ TimelineToolbar *toolbar() const;
+ QGraphicsView *viewTimelineContent() const;
+ QGraphicsView *viewTreeContent() const;
+ QVector<RowTree *> selectedRows() const;
+ void openBarColorDialog();
+ void onTimeBarColorChanged(const QColor &color);
+ void setSelectedTimeBarsColor(const QColor &color, bool preview);
+ void refreshVariants(int instance = 0);
+ void updateVariantsFiltering(RowTree *row = nullptr, bool force = false);
+ void enableDnD(bool b = true);
+ bool dndActive() const;
+ bool blockMousePress() const;
+
+ // Presentation Change Listener
+ void OnNewPresentation() override;
+ void OnClosingPresentation() override;
+ void onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable);
+
+ //CClientPlayChangeListener
+ void OnTimeChanged(long inTime) override;
+ bool hasSelectedKeyframes() const;
+
+ // CControl
+ CDropTarget *FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType) override;
+ void OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags) override;
+ CPt GetPreferredSize() override;
+ void SetSize(long inX, long inY) override;
+ bool isFullReconstructPending() const { return m_fullReconstruct; }
+ NavigationBar *navigationBar() const { return m_navigationBar; }
+
+protected:
+ // DataModel callbacks
+ virtual void onActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex,
+ const qt3dsdm::Qt3DSDMSlideHandle &inSlide);
+ void onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void onAnimationCreated(qt3dsdm::Qt3DSDMInstanceHandle parentInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle property);
+ void onKeyframeInserted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe);
+ void onKeyframeDeleted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe);
+ void onKeyframeUpdated(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe);
+ void onFirstKeyframeDynamicSet(qt3dsdm::Qt3DSDMAnimationHandle inAnimation);
+ void onAnimationDeleted(qt3dsdm::Qt3DSDMInstanceHandle parentInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle property);
+ void onActionEvent(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide,
+ qt3dsdm::Qt3DSDMInstanceHandle inOwner);
+ void onPropertyLinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void onPropertyUnlinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void onChildAdded(int inParent, int inChild, long inIndex);
+ void onChildRemoved(int inParent, int inChild, long inIndex);
+ void onChildMoved(int inParent, int inChild, long inOldIndex, long inNewIndex);
+ void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMPropertyHandle inProperty);
+ void onAsyncUpdate();
+
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+private:
+ typedef QHash<qt3dsdm::Qt3DSDMInstanceHandle, RowTree *> THandleMap;
+
+ Qt3DSDMTimelineItemBinding *getBindingForHandle(int handle,
+ Qt3DSDMTimelineItemBinding *binding) const;
+ void insertToHandlesMapRecursive(Qt3DSDMTimelineItemBinding *binding);
+ void insertToHandlesMap(Qt3DSDMTimelineItemBinding *binding);
+ Q3DStudio::CString getPlaybackMode();
+ void refreshKeyframe(qt3dsdm::Qt3DSDMAnimationHandle inAnimation,
+ qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe,
+ ETimelineKeyframeTransaction inTransaction);
+ void updateActionStates(const QSet<RowTree *> &rows);
+ void setTreeWidth(int width);
+
+ TreeHeaderView *m_viewTreeHeader = nullptr;
+ QGraphicsView *m_viewTreeContent = nullptr;
+ QGraphicsView *m_viewTimelineHeader = nullptr;
+ QGraphicsView *m_viewTimelineContent = nullptr;
+ NavigationBar *m_navigationBar = nullptr;
+ TimelineToolbar *m_toolbar = nullptr;
+ TimelineGraphicsScene *m_graphicsScene;
+ TimelineSplitter *m_splitter = nullptr;
+ CTimelineTranslationManager *m_translationManager = nullptr;
+ FlatObjectListModel *m_model = nullptr;
+ Qt3DSDMTimelineItemBinding *m_binding = nullptr;
+ bool m_splitterPressed = false;
+ QSize m_preferredSize;
+ QMultiHash<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMPropertyHandle> m_dirtyProperties;
+ QHash<int, int> m_moveMap; // key: child handle, value: parent handle
+ QHash<int, QStringList> m_variantsMap; // key: obj handle, value: variant groups
+ QSet<int> m_actionChanges; // key: object handle
+ QSet<int> m_subpresentationChanges; // key: object handle
+ QMultiHash<int, int> m_keyframeChangesMap; // key: object handle, value: property handle
+ QTimer m_asyncUpdateTimer;
+ bool m_fullReconstruct = false;
+ bool m_blockMousePress = false;
+ CClientDataModelBridge *m_bridge = nullptr;
+ IBreadCrumbProvider *m_BreadCrumbProvider = nullptr;
+
+ // data model connection
+ std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_connections;
+ qt3dsdm::Qt3DSDMSlideHandle m_activeSlide;
+ THandleMap m_handlesMap;
+};
+
+#endif // TIMELINEWIDGET_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp
new file mode 100644
index 00000000..efc7a2c1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "InteractiveTimelineItem.h"
+
+InteractiveTimelineItem::InteractiveTimelineItem(TimelineItem *parent) : TimelineItem(parent)
+{
+ setAcceptHoverEvents(true);
+}
+
+void InteractiveTimelineItem::setState(State state)
+{
+ m_state = state;
+}
+
+int InteractiveTimelineItem::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeInteractiveTimelineItem;
+}
+
+void InteractiveTimelineItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
+{
+ Q_UNUSED(event)
+ if (m_state != Selected)
+ setState(Hovered);
+}
+
+void InteractiveTimelineItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+{
+ Q_UNUSED(event)
+ if (m_state != Selected)
+ setState(Normal);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h
new file mode 100644
index 00000000..a6a5e5ee
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INTERACTIVETIMELINEITEM_H
+#define INTERACTIVETIMELINEITEM_H
+
+#include "TimelineItem.h"
+
+class InteractiveTimelineItem : public TimelineItem {
+ Q_OBJECT
+
+public:
+ enum State {
+ Pressed,
+ Hovered,
+ Selected,
+ Normal
+ };
+
+ explicit InteractiveTimelineItem(TimelineItem *parent = nullptr);
+
+ virtual void setState(State state);
+
+ int type() const override;
+
+protected:
+ void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
+
+ State m_state = Normal;
+};
+
+#endif // INTERACTIVETIMELINEITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp
new file mode 100644
index 00000000..f41c2952
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "NavigationBar.h"
+#include "NavigationBarItem.h"
+#include "TimelineConstants.h"
+#include <QtCore/qdebug.h>
+
+NavigationBar::NavigationBar(QWidget *parent)
+ : QWidget(parent)
+{
+ setMaximumHeight(0);
+ m_layout = new QHBoxLayout(this);
+ m_layout->setMargin(4);
+ m_layout->setSpacing(4);
+ setLayout(m_layout);
+ // Initialize hide/show animation
+ m_expandAnimation.setTargetObject(this);
+ m_expandAnimation.setPropertyName("maximumHeight");
+ m_expandAnimation.setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION);
+}
+
+void NavigationBar::updateNavigationItems(IBreadCrumbProvider *inBreadCrumbProvider)
+{
+ if (!inBreadCrumbProvider)
+ return;
+
+ m_breadCrumbProvider = inBreadCrumbProvider;
+
+ const IBreadCrumbProvider::TTrailList &trailList = m_breadCrumbProvider->GetTrail();
+ int listSize = (int)trailList.size();
+
+ // Remove "stretch" from end
+ QLayoutItem *stretch = m_layout->takeAt(m_layout->count() - 1);
+ if (stretch)
+ delete stretch;
+
+ // Update current items or create new as needed
+ for (int i = 0; i < listSize; ++i) {
+ SBreadCrumb item = trailList.at(i);
+ NavigationBarItem *barItem = nullptr;
+ bool newItem = (m_itemAmount <= 0) || (i > m_itemAmount - 1);
+ if (newItem) {
+ barItem = new NavigationBarItem(this);
+ } else {
+ // Every other item is NavigationBarItem, every other separator
+ int barItemIndex = i * 2;
+ barItem = static_cast<NavigationBarItem *>(
+ m_layout->itemAt(barItemIndex)->widget());
+ barItem->setHighlight(false);
+ }
+ bool isLastItem = (i == listSize - 1);
+ barItem->setEnabled(!isLastItem);
+ barItem->setIndex(i);
+ barItem->setText(item.m_String);
+ if (i == 0)
+ barItem->setIcon(m_breadCrumbProvider->GetRootImage());
+ else
+ barItem->setIcon(m_breadCrumbProvider->GetBreadCrumbImage());
+
+ if (newItem) {
+ QObject::connect(barItem, &NavigationBarItem::clicked,
+ this, &NavigationBar::itemClicked);
+ if (i != 0) {
+ // Separator before all items except first
+ QLabel *separator = new QLabel(this);
+ separator->setPixmap(m_breadCrumbProvider->GetSeparatorImage());
+ m_layout->addWidget(separator);
+ }
+ m_layout->addWidget(barItem);
+ }
+ }
+
+ // Remove possible extra items, when user navigates back
+ // First item (scene) is never removed
+ QLayoutItem *child;
+ int lastIndex = (listSize <= 1) ? 1 : (listSize * 2) - 1;
+ while ((child = m_layout->takeAt(lastIndex)) != 0) {
+ if (child->widget())
+ delete child->widget();
+ delete child;
+ }
+
+ // When list contains single item (scene), hide the bar
+ setBarVisibility(listSize > 1);
+
+ // Stretch at end for proper item sizing
+ m_layout->addStretch(1);
+
+ m_itemAmount = listSize;
+}
+
+void NavigationBar::itemClicked(int index)
+{
+ m_breadCrumbProvider->OnBreadCrumbClicked((long)index);
+}
+
+void NavigationBar::setBarVisibility(bool visible)
+{
+ int endHeight = visible ? TimelineConstants::NAVIGATION_BAR_H : 0;
+ if (height() != endHeight) {
+ m_expandAnimation.setEndValue(endHeight);
+ m_expandAnimation.start();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h
new file mode 100644
index 00000000..f08e4220
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef NAVIGATIONBAR_H
+#define NAVIGATIONBAR_H
+
+#include <QtCore/qpropertyanimation.h>
+#include <QtWidgets/qwidget.h>
+#include <QtWidgets/qboxlayout.h>
+#include "Bindings/IBreadCrumbProvider.h"
+
+class NavigationBar : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit NavigationBar(QWidget *parent = nullptr);
+ void updateNavigationItems(IBreadCrumbProvider *inBreadCrumbProvider);
+
+public slots:
+ void itemClicked(int index);
+
+private:
+ void setBarVisibility(bool visible);
+ IBreadCrumbProvider *m_breadCrumbProvider = nullptr;
+ QHBoxLayout *m_layout = nullptr;
+ int m_itemAmount = 0;
+ QPropertyAnimation m_expandAnimation;
+};
+
+#endif // NAVIGATIONBAR_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp
new file mode 100644
index 00000000..b9b47642
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "NavigationBarItem.h"
+#include "StudioPreferences.h"
+#include "ResourceCache.h"
+
+#include <QtCore/qdebug.h>
+#include <QtWidgets/qsizepolicy.h>
+
+NavigationBarItem::NavigationBarItem(QWidget *parent)
+ : QWidget(parent)
+{
+ setHighlight(false);
+ m_layout.setMargin(0);
+ m_layout.setSpacing(0);
+ m_iconLabel.setFixedWidth(20);
+ m_iconLabel.setStyleSheet("padding: 0 0 0 4;");
+ m_textLabel.setStyleSheet("padding: 0 4 0 0;");
+ m_textLabel.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ m_layout.addWidget(&m_iconLabel);
+ m_layout.addWidget(&m_textLabel);
+ setLayout(&m_layout);
+}
+
+void NavigationBarItem::setIndex(int index)
+{
+ m_index = index;
+}
+
+void NavigationBarItem::setIcon(const QPixmap &pixmap)
+{
+ m_iconLabel.setPixmap(pixmap);
+}
+
+void NavigationBarItem::setText(const QString &text)
+{
+ QColor textColor = isEnabled() ? CStudioPreferences::GetNormalColor()
+ : CStudioPreferences::GetInactiveColor();
+ const QString fonttemplate = tr("<font color='%1'>%2</font>");
+ m_textLabel.setText(fonttemplate.arg(textColor.name(), text));
+}
+
+void NavigationBarItem::setHighlight(bool highlight)
+{
+ if (highlight) {
+ QColor bgColor = CStudioPreferences::GetMouseOverHighlightColor();
+ QString bgColorStyle = QStringLiteral("background-color: ") + bgColor.name();
+ setStyleSheet(bgColorStyle);
+ } else {
+ setStyleSheet("background-color: transparent;");
+ }
+}
+
+void NavigationBarItem::mousePressEvent(QMouseEvent *event)
+{
+ Q_UNUSED(event);
+ emit clicked(m_index);
+}
+
+void NavigationBarItem::enterEvent(QEvent *event)
+{
+ Q_UNUSED(event);
+ if (isEnabled())
+ setHighlight(true);
+}
+
+void NavigationBarItem::leaveEvent(QEvent *event)
+{
+ Q_UNUSED(event);
+ if (isEnabled())
+ setHighlight(false);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h
new file mode 100644
index 00000000..8b1fc3ab
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef NAVIGATIONBARITEM_H
+#define NAVIGATIONBARITEM_H
+
+#include <QtGui/qpixmap.h>
+#include <QtWidgets/qwidget.h>
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qlabel.h>
+
+class NavigationBarItem : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit NavigationBarItem(QWidget *parent = nullptr);
+
+ void setIndex(int index);
+ void setText(const QString &text);
+ void setIcon(const QPixmap &pixmap);
+ void setHighlight(bool highlight);
+
+protected:
+ void mousePressEvent(QMouseEvent *event) override;
+ void enterEvent(QEvent *event) override;
+ void leaveEvent(QEvent *event) override;
+
+signals:
+ void clicked(int index);
+
+private:
+ int m_index = 0;
+ QHBoxLayout m_layout;
+ QLabel m_iconLabel;
+ QLabel m_textLabel;
+};
+
+#endif // NAVIGATIONBARITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp
new file mode 100644
index 00000000..3ddfdcad
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "PlayHead.h"
+#include "Ruler.h"
+#include "TimelineConstants.h"
+#include "StudioPreferences.h"
+#include "StudioUtils.h"
+
+#include <QtGui/qpainter.h>
+#include <QtGui/qcursor.h>
+#include <QtWidgets/qwidget.h>
+
+PlayHead::PlayHead(Ruler *ruler)
+ : QGraphicsRectItem()
+ , m_ruler(ruler)
+{
+ setZValue(99);
+ setRect(-TimelineConstants::PLAYHEAD_W * .5, 0, TimelineConstants::PLAYHEAD_W, 0);
+}
+
+void PlayHead::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0;
+ static const QPixmap pixHead = QPixmap(":/images/PlaybackHead.png");
+ static const QPixmap pixHead2x = QPixmap(":/images/PlaybackHead@2x.png");
+
+ static const int PLAY_HEAD_H = 999999; // theoretically big enough height
+ painter->drawPixmap(-TimelineConstants::PLAYHEAD_W * .5, 0, hiResIcons ? pixHead2x : pixHead);
+ painter->setPen(CStudioPreferences::timelinePlayheadLineColor());
+ painter->drawLine(0, 0, 0, PLAY_HEAD_H);
+}
+
+void PlayHead::setHeight(int height)
+{
+ setRect(rect().x(), rect().y(), rect().width(), height);
+}
+
+void PlayHead::setTime(long time)
+{
+ if (time < 0)
+ time = 0;
+ else if (time > m_ruler->duration())
+ time = m_ruler->duration();
+
+ m_time = time;
+ updatePosition();
+}
+
+void PlayHead::setPosition(double posX)
+{
+ posX = qBound(TimelineConstants::RULER_EDGE_OFFSET, posX, m_ruler->duration()
+ * TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale()
+ + TimelineConstants::RULER_EDGE_OFFSET);
+
+ setX(m_ruler->x() + posX);
+ m_time = (posX - TimelineConstants::RULER_EDGE_OFFSET)
+ / (TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale());
+}
+
+void PlayHead::updatePosition()
+{
+ setX(m_ruler->x() + TimelineConstants::RULER_EDGE_OFFSET
+ + m_time * TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale());
+}
+
+long PlayHead::time() const
+{
+ return m_time;
+}
+
+int PlayHead::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TimelineItem::TypePlayHead;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h
new file mode 100644
index 00000000..395e6317
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PLAYHEAD_H
+#define PLAYHEAD_H
+
+#include "TimelineItem.h"
+
+#include <QtWidgets/qgraphicsitem.h>
+
+class Ruler;
+
+class PlayHead : public QGraphicsRectItem
+{
+
+public:
+ explicit PlayHead(Ruler *m_ruler);
+
+ void setHeight(int height);
+ void setPosition(double posX); // set x poisiotn
+ void updatePosition(); // sync x poisiotn based on time value
+ void setTime(long time); // set time (sets x based on time (ms) input)
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ long time() const;
+ int type() const override;
+
+private:
+ long m_time = 0;
+ Ruler *m_ruler;
+};
+
+#endif // PLAYHEAD_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp
new file mode 100644
index 00000000..25b66911
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp
@@ -0,0 +1,1035 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTimeline.h"
+#include "RowTimelinePropertyGraph.h"
+#include "RowTree.h"
+#include "RowManager.h"
+#include "Ruler.h"
+#include "TimelineConstants.h"
+#include "Keyframe.h"
+#include "KeyframeManager.h"
+#include "TimelineGraphicsScene.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "Bindings/ITimelineTimebar.h"
+#include "Bindings/Qt3DSDMTimelineItemProperty.h"
+#include "AppFonts.h"
+#include "StudioPreferences.h"
+#include "TimelineToolbar.h"
+#include "StudioUtils.h"
+
+#include <QtGui/qpainter.h>
+#include <QtGui/qbrush.h>
+#include <QtWidgets/qdesktopwidget.h>
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+#include <QtWidgets/qwidget.h>
+#include <QtWidgets/qlabel.h>
+#include <QtCore/qdatetime.h>
+
+RowTimeline::RowTimeline()
+ : InteractiveTimelineItem()
+{
+ // 999999: theoretically big enough row width (~ 4.6 hrs of presentation length)
+ setMinimumWidth(999999);
+ setMaximumWidth(999999);
+}
+
+RowTimeline::~RowTimeline()
+{
+ // remove keyframes
+ if (!m_keyframes.empty()) {
+ if (m_isProperty) // non-property rows use the same keyframes from property rows.
+ qDeleteAll(m_keyframes);
+
+ m_keyframes.clear();
+ }
+}
+
+void RowTimeline::initialize()
+{
+ // Called once m_rowTree exists
+
+ m_commentItem = new RowTimelineCommentItem(this);
+ m_commentItem->setParentRow(m_rowTree);
+ updateCommentItemPos();
+
+ TimelineToolbar *toolbar = m_rowTree->m_scene->widgetTimeline()->toolbar();
+ connect(toolbar, &TimelineToolbar::showRowTextsToggled, this, [this]() {
+ updateCommentItem();
+ });
+
+ connect(m_commentItem, &RowTimelineCommentItem::labelChanged, this,
+ [this](const QString &label) {
+ // Update label on timeline and on model
+ ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar();
+ timebar->SetTimebarComment(label);
+ });
+
+ connect(m_rowTree->m_scene->ruler(), &Ruler::viewportXChanged, this,
+ &RowTimeline::updateCommentItemPos);
+}
+
+void RowTimeline::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option)
+
+ bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0;
+
+ if (!y()) // prevents flickering when the row is just inserted to the layout
+ return;
+
+ const int currentHeight = size().height() - 1;
+
+ if (isColorProperty() && !m_keyframes.empty()) {
+ drawColorPropertyGradient(painter, widget->width());
+ } else {
+ // Background
+ QColor bgColor;
+ if (m_rowTree->isProperty())
+ bgColor = CStudioPreferences::timelineRowColorNormalProp();
+ else if (m_state == Selected)
+ bgColor = CStudioPreferences::timelineRowColorSelected();
+ else if (m_state == Hovered && !m_rowTree->m_locked)
+ bgColor = CStudioPreferences::timelineRowColorOver();
+ else
+ bgColor = CStudioPreferences::timelineRowColorNormal();
+ painter->fillRect(0, 0, size().width(), currentHeight, bgColor);
+ }
+
+ const double edgeOffset = TimelineConstants::RULER_EDGE_OFFSET;
+
+ // Duration. Draw duration bar (for scene/component root) also if it has
+ // datainput controller
+ if (m_rowTree->hasDurationBar() || m_controllerDataInput.size()) {
+ painter->save();
+
+ // fully outside ancestors' limits, draw fully hashed
+ if (m_minStartX > m_endX || m_maxEndX < m_startX) {
+ painter->setBrush(QBrush(CStudioPreferences::timelineRowColorDurationOff1(),
+ Qt::BDiagPattern));
+ painter->setPen(Qt::NoPen);
+ painter->fillRect(QRect(edgeOffset + m_startX, 0, m_endX - m_startX, currentHeight),
+ CStudioPreferences::timelineRowColorDurationOff2());
+ painter->drawRect(QRect(edgeOffset + m_startX, 0, m_endX - m_startX, currentHeight));
+
+ painter->setPen(QPen(CStudioPreferences::timelineRowColorDurationEdge(), 2));
+ painter->drawLine(edgeOffset + m_startX, 0, edgeOffset + m_startX, currentHeight);
+ painter->drawLine(edgeOffset + m_endX, 0, edgeOffset + m_endX, currentHeight);
+ } else {
+ // draw main duration part
+ double x = edgeOffset + qMax(m_startX, m_minStartX);
+ double w = edgeOffset + qMin(m_endX, m_maxEndX) - x;
+ static const int marginY = 3;
+
+ painter->setPen(Qt::NoPen);
+
+ if (m_controllerDataInput.size()) {
+ painter->fillRect(QRect(x, 0, w, currentHeight),
+ CStudioPreferences::dataInputColor());
+ } else if (m_rowTree->indexInLayout() != 1) {
+ painter->fillRect(QRect(x, 0, w, currentHeight), m_barColor);
+ }
+
+ if (m_state == Selected) {
+ // draw selection overlay on bar
+ painter->fillRect(QRect(x, marginY, w, currentHeight - marginY * 2),
+ CStudioPreferences::timelineRowColorDurationSelected());
+ }
+
+ if (m_controllerDataInput.size()) {
+ static const QPixmap pixDataInput = QPixmap(":/images/Objects-DataInput-White.png");
+ static const QPixmap pixDataInput2x
+ = QPixmap(":/images/Objects-DataInput-White@2x.png");
+ static const QFontMetrics fm(painter->font());
+
+ // need clip region to limit datainput icon visibility to the same rect as we use
+ // for text
+ painter->setClipRect(x, 0, w, currentHeight);
+ painter->setClipping(true);
+ painter->setPen(QPen(CStudioPreferences::textColor(), 2));
+ // +5 added to text location to make margin comparable to other datainput controls
+ painter->drawText(QRect(x + pixDataInput.width() + 5, 0, w, currentHeight),
+ m_controllerDataInput, QTextOption(Qt::AlignCenter));
+ // place the icon in front of the text
+ int textwidth = fm.width(m_controllerDataInput);
+ int iconx = x + (w - textwidth) / 2;
+ if (iconx < x)
+ iconx = x;
+ painter->drawPixmap(iconx, marginY, hiResIcons ? pixDataInput2x : pixDataInput);
+ painter->setPen(Qt::NoPen);
+ painter->setClipping(false);
+ }
+
+ // draw hashed part before
+ painter->setBrush(QBrush(CStudioPreferences::timelineRowColorDurationOff1(),
+ Qt::BDiagPattern));
+ if (m_startX < m_minStartX) {
+ painter->setPen(Qt::NoPen);
+ painter->fillRect(QRect(edgeOffset + m_startX, 0, m_minStartX - m_startX,
+ currentHeight),
+ CStudioPreferences::timelineRowColorDurationOff2());
+ painter->drawRect(QRect(edgeOffset + m_startX, 0, m_minStartX - m_startX,
+ currentHeight));
+ painter->setPen(CStudioPreferences::timelineRowColorDurationEdge());
+ painter->drawLine(edgeOffset + m_minStartX, 0, edgeOffset + m_minStartX,
+ currentHeight);
+ }
+
+ // draw hashed part after
+ if (m_endX > m_maxEndX) {
+ painter->setPen(Qt::NoPen);
+ painter->fillRect(QRect(edgeOffset + m_maxEndX, 0, m_endX - m_maxEndX,
+ currentHeight),
+ CStudioPreferences::timelineRowColorDurationOff2());
+ painter->drawRect(QRect(edgeOffset + m_maxEndX, 0, m_endX - m_maxEndX,
+ currentHeight));
+ painter->setPen(CStudioPreferences::timelineRowColorDurationEdge());
+ painter->drawLine(edgeOffset + m_maxEndX, 0, edgeOffset + m_maxEndX, currentHeight);
+ }
+
+ if (m_rowTree->indexInLayout() != 1) {
+ painter->setPen(QPen(CStudioPreferences::timelineRowColorDurationEdge(), 2));
+ painter->drawLine(edgeOffset + m_startX, 0, edgeOffset + m_startX, currentHeight);
+ painter->drawLine(edgeOffset + m_endX, 0, edgeOffset + m_endX, currentHeight);
+ }
+ }
+
+ painter->restore();
+ }
+
+ if (m_propertyGraph) { // Property graph
+ QRectF graphRect(edgeOffset, 0, widget->width(), currentHeight);
+ m_propertyGraph->paintGraphs(painter, graphRect);
+ }
+
+ // Keyframes
+ const qreal keyFrameH = 16.0;
+ const qreal keyFrameHalfH = keyFrameH / 2.0;
+ const qreal keyFrameY = (qMin(currentHeight, TimelineConstants::ROW_H) / 2.0) - keyFrameHalfH;
+ const qreal hiddenKeyFrameY = keyFrameY + (keyFrameH * 2.0 / 3.0) + 2.0;
+ const qreal keyFrameOffset = hiResIcons ? 8 : 7.5;
+
+ // Hidden descendant keyframe indicators
+ if (!m_rowTree->expanded()) {
+ static const QPixmap pixKeyframeHidden = QPixmap(":/images/keyframe-hidden-normal.png");
+ static const QPixmap pixKeyframeHidden2x
+ = QPixmap(":/images/keyframe-hidden-normal@2x.png");
+ QVector<long> childKeyframeTimes;
+ collectChildKeyframeTimes(childKeyframeTimes);
+
+ const qreal oldOpacity = painter->opacity();
+ painter->setOpacity(0.75);
+ for (const auto time : qAsConst(childKeyframeTimes)) {
+ const qreal xCoord = edgeOffset + m_rowTree->m_scene->ruler()->timeToDistance(time)
+ - 2.5;
+ painter->drawPixmap(QPointF(xCoord, hiddenKeyFrameY), hiResIcons ? pixKeyframeHidden2x
+ : pixKeyframeHidden);
+ }
+ painter->setOpacity(oldOpacity);
+ }
+
+ if (m_rowTree->hasPropertyChildren()) { // object row keyframes
+ static const QPixmap pixKeyframeMasterDisabled
+ = QPixmap(":/images/Keyframe-Master-Disabled.png");
+ static const QPixmap pixKeyframeMasterNormal
+ = QPixmap(":/images/Keyframe-Master-Normal.png");
+ static const QPixmap pixKeyframeMasterSelected
+ = QPixmap(":/images/Keyframe-Master-Selected.png");
+ static const QPixmap pixKeyframeMasterDynamicDisabled
+ = QPixmap(":/images/Keyframe-MasterDynamic-Disabled.png");
+ static const QPixmap pixKeyframeMasterDynamicNormal
+ = QPixmap(":/images/Keyframe-MasterDynamic-Normal.png");
+ static const QPixmap pixKeyframeMasterDynamicSelected
+ = QPixmap(":/images/Keyframe-MasterDynamic-Selected.png");
+ static const QPixmap pixKeyframeMasterDisabled2x
+ = QPixmap(":/images/Keyframe-Master-Disabled@2x.png");
+ static const QPixmap pixKeyframeMasterNormal2x
+ = QPixmap(":/images/Keyframe-Master-Normal@2x.png");
+ static const QPixmap pixKeyframeMasterSelected2x
+ = QPixmap(":/images/Keyframe-Master-Selected@2x.png");
+ static const QPixmap pixKeyframeMasterDynamicDisabled2x
+ = QPixmap(":/images/Keyframe-MasterDynamic-Disabled@2x.png");
+ static const QPixmap pixKeyframeMasterDynamicNormal2x
+ = QPixmap(":/images/Keyframe-MasterDynamic-Normal@2x.png");
+ static const QPixmap pixKeyframeMasterDynamicSelected2x
+ = QPixmap(":/images/Keyframe-MasterDynamic-Selected@2x.png");
+ for (auto keyframe : qAsConst(m_keyframes)) {
+ QPixmap pixmap;
+ if (m_rowTree->locked()) {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframeMasterDynamicDisabled2x
+ : pixKeyframeMasterDynamicDisabled;
+ } else {
+ pixmap = hiResIcons ? pixKeyframeMasterDisabled2x
+ : pixKeyframeMasterDisabled;
+ }
+ } else if (keyframe->selected()) {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframeMasterDynamicSelected2x
+ : pixKeyframeMasterDynamicSelected;
+ } else {
+ pixmap = hiResIcons ? pixKeyframeMasterSelected2x
+ : pixKeyframeMasterSelected;
+ }
+ } else {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframeMasterDynamicNormal2x
+ : pixKeyframeMasterDynamicNormal;
+ } else {
+ pixmap = hiResIcons ? pixKeyframeMasterNormal2x
+ : pixKeyframeMasterNormal;
+ }
+ }
+ painter->drawPixmap(QPointF(edgeOffset + m_rowTree->m_scene->ruler()
+ ->timeToDistance(keyframe->time) - keyFrameOffset,
+ keyFrameY), pixmap);
+
+ // highlight the pressed keyframe in a multi-selection (the keyframe that is affected
+ // by snapping, and setting time dialog)
+ if (m_rowTree->m_scene->keyframeManager()->selectedKeyframes().size() > 1
+ && m_rowTree->m_scene->pressedKeyframe() == keyframe) {
+ painter->setPen(QPen(CStudioPreferences::timelinePressedKeyframeColor(), 1));
+ painter->drawArc(edgeOffset + m_rowTree->m_scene->ruler()
+ ->timeToDistance(keyframe->time) - 4, keyFrameY + 4, 9, 9, 0,
+ 5760);
+ }
+ }
+ } else if (m_rowTree->isProperty()) { // property row keyframes
+ static const QPixmap pixKeyframePropertyDisabled
+ = QPixmap(":/images/Keyframe-Property-Disabled.png");
+ static const QPixmap pixKeyframePropertyNormal
+ = QPixmap(":/images/Keyframe-Property-Normal.png");
+ static const QPixmap pixKeyframePropertySelected
+ = QPixmap(":/images/Keyframe-Property-Selected.png");
+ static const QPixmap pixKeyframePropertyDynamicDisabled
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Disabled.png");
+ static const QPixmap pixKeyframePropertyDynamicNormal
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Normal.png");
+ static const QPixmap pixKeyframePropertyDynamicSelected
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Selected.png");
+ static const QPixmap pixKeyframePropertyDisabled2x
+ = QPixmap(":/images/Keyframe-Property-Disabled@2x.png");
+ static const QPixmap pixKeyframePropertyNormal2x
+ = QPixmap(":/images/Keyframe-Property-Normal@2x.png");
+ static const QPixmap pixKeyframePropertySelected2x
+ = QPixmap(":/images/Keyframe-Property-Selected@2x.png");
+ static const QPixmap pixKeyframePropertyDynamicDisabled2x
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Disabled@2x.png");
+ static const QPixmap pixKeyframePropertyDynamicNormal2x
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Normal@2x.png");
+ static const QPixmap pixKeyframePropertyDynamicSelected2x
+ = QPixmap(":/images/Keyframe-PropertyDynamic-Selected@2x.png");
+ for (auto keyframe : qAsConst(m_keyframes)) {
+ QPixmap pixmap;
+ if (m_rowTree->locked()) {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframePropertyDynamicDisabled2x
+ : pixKeyframePropertyDynamicDisabled;
+
+ } else {
+ pixmap = hiResIcons ? pixKeyframePropertyDisabled2x
+ : pixKeyframePropertyDisabled;
+ }
+ } else if (keyframe->selected()) {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframePropertyDynamicSelected2x
+ : pixKeyframePropertyDynamicSelected;
+
+ } else {
+ pixmap = hiResIcons ? pixKeyframePropertySelected2x
+ : pixKeyframePropertySelected;
+ }
+ } else {
+ if (keyframe->dynamic) {
+ pixmap = hiResIcons ? pixKeyframePropertyDynamicNormal2x
+ : pixKeyframePropertyDynamicNormal;
+
+ } else {
+ pixmap = hiResIcons ? pixKeyframePropertyNormal2x
+ : pixKeyframePropertyNormal;
+ }
+ }
+ painter->drawPixmap(QPointF(edgeOffset + m_rowTree->m_scene->ruler()
+ ->timeToDistance(keyframe->time) - keyFrameOffset,
+ keyFrameY), pixmap);
+ }
+ }
+}
+
+bool RowTimeline::isColorProperty() const
+{
+ ITimelineItemProperty *propBinding = m_rowTree->propBinding();
+ if (propBinding) {
+ qt3dsdm::TDataTypePair type = propBinding->GetType();
+ if (m_rowTree->isProperty()
+ && type.first == qt3dsdm::DataModelDataType::Float4
+ && type.second == qt3dsdm::AdditionalMetaDataType::Color) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void RowTimeline::drawColorPropertyGradient(QPainter *painter, int width)
+{
+ // Gradient scaled width, or at least widget width
+ double minWidth = width;
+ double timelineScale = m_rowTree->m_scene->ruler()->timelineScale();
+ double scaledWidth = width * (timelineScale / 2);
+ width = qMax(minWidth, scaledWidth);
+
+ ITimelineItemProperty *propBinding = m_rowTree->propBinding();
+ QLinearGradient bgGradient(0, 0, width, 0);
+
+ for (auto keyframe : qAsConst(m_keyframes)) {
+ double xPos = m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time);
+ double gradPos = xPos / width;
+ gradPos = qBound(0.0, gradPos, 1.0);
+ QColor currentColor;
+ // Get the color at the specified time.
+ currentColor.setRed(propBinding->GetChannelValueAtTime(0, keyframe->time));
+ currentColor.setGreen(propBinding->GetChannelValueAtTime(1, keyframe->time));
+ currentColor.setBlue(propBinding->GetChannelValueAtTime(2, keyframe->time));
+ bgGradient.setColorAt(gradPos, currentColor);
+ }
+ painter->fillRect(TimelineConstants::RULER_EDGE_OFFSET, 0,
+ width, size().height() - 1, bgGradient);
+}
+
+Keyframe *RowTimeline::getClickedKeyframe(const QPointF &scenePos)
+{
+ if (rowTree()->locked())
+ return nullptr;
+
+ QPointF p = mapFromScene(scenePos.x(), scenePos.y());
+ double x;
+
+ QList<Keyframe *> keyframes;
+ if (m_rowTree->hasPropertyChildren()) {
+ const auto childProps = m_rowTree->childProps();
+ for (auto child : childProps)
+ keyframes.append(child->rowTimeline()->m_keyframes);
+ } else {
+ keyframes = m_keyframes;
+ }
+
+ for (const auto keyframe : qAsConst(keyframes)) {
+ x = TimelineConstants::RULER_EDGE_OFFSET
+ + m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time);
+
+ if (p.x() > x - 5 && p.x() < x + 5 && p.y() > 3 && p.y() < 16)
+ return keyframe;
+ }
+
+ return nullptr;
+}
+
+QList<Keyframe *> RowTimeline::getKeyframesInRange(const QRectF &rect) const
+{
+ double x;
+ QRectF localRect = mapFromScene(rect).boundingRect();
+
+ QList<Keyframe *> result;
+
+ static const int KF_CENTER_Y = 10;
+ for (auto keyframe : qAsConst(m_keyframes)) {
+ x = TimelineConstants::RULER_EDGE_OFFSET
+ + m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time);
+
+ if (localRect.left() < x && localRect.right() > x
+ && localRect.top() < KF_CENTER_Y && localRect.bottom() > KF_CENTER_Y) {
+ result.append(keyframe);
+ }
+ }
+
+ return result;
+}
+
+void RowTimeline::updateDurationFromBinding()
+{
+ if (m_rowTree->isProperty()) // this method works for main rows only
+ return;
+
+ ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar();
+ clearBoundChildren();
+ setStartTime(timebar->GetStartTime());
+ setEndTime(timebar->GetEndTime());
+}
+
+void RowTimeline::updateKeyframesFromBinding(const QList<int> &properties)
+{
+ if (m_rowTree->isProperty()) // this method works for main rows only
+ return;
+
+ const auto childProps = m_rowTree->childProps();
+ for (auto child : childProps) {
+ qt3dsdm::Qt3DSDMPropertyHandle propertyHandle =
+ static_cast<Qt3DSDMTimelineItemProperty *>(child->m_PropBinding)
+ ->getPropertyHandle();
+ if (properties.contains(propertyHandle)) {
+ m_rowTree->m_scene->keyframeManager()->deleteKeyframes(child->rowTimeline(), false);
+
+ for (int i = 0; i < child->m_PropBinding->GetKeyframeCount(); i++) {
+ Qt3DSDMTimelineKeyframe *kf = static_cast<Qt3DSDMTimelineKeyframe *>
+ (child->m_PropBinding->GetKeyframeByIndex(i));
+
+ Keyframe *kfUI = new Keyframe(kf->GetTime(), child->rowTimeline());
+ kfUI->binding = kf;
+ kfUI->dynamic = kf->IsDynamic();
+ kf->setUI(kfUI);
+ child->rowTimeline()->insertKeyframe(kfUI);
+ child->parentRow()->rowTimeline()->insertKeyframe(kfUI);
+ if (kf->IsSelected())
+ m_rowTree->m_scene->keyframeManager()->selectKeyframe(kfUI);
+ }
+
+ if (isVisible()) {
+ child->rowTimeline()->update();
+ } else {
+ // Find the first visible parent and update that to show hidden keyframes
+ RowTree *updateRow = m_rowTree->parentRow();
+ while (updateRow && !updateRow->isVisible())
+ updateRow = updateRow->parentRow();
+ if (updateRow)
+ updateRow->rowTimeline()->update();
+ }
+ }
+ }
+ update();
+}
+
+void RowTimeline::insertKeyframe(Keyframe *keyframe)
+{
+ if (!m_keyframes.contains(keyframe))
+ m_keyframes.append(keyframe);
+}
+
+void RowTimeline::removeKeyframe(Keyframe *keyframe)
+{
+ m_keyframes.removeAll(keyframe);
+}
+
+void RowTimeline::putSelectedKeyframesOnTop()
+{
+ if (!m_keyframes.empty()) {
+ std::partition(m_keyframes.begin(), m_keyframes.end(), [](Keyframe *kf) {
+ return !kf->selected();
+ });
+ }
+
+ if (m_rowTree->hasPropertyChildren()) { // has property rows
+ const auto childProps = m_rowTree->childProps();
+ for (auto child : childProps) {
+ std::partition(child->rowTimeline()->m_keyframes.begin(),
+ child->rowTimeline()->m_keyframes.end(), [](Keyframe *kf) {
+ return !kf->selected();
+ });
+ }
+ }
+}
+
+void RowTimeline::updateKeyframes()
+{
+ update();
+
+ if (m_rowTree->hasPropertyChildren()) { // master keyframes
+ const auto childProps = m_rowTree->childProps();
+ for (const auto child : childProps)
+ child->rowTimeline()->update();
+ }
+}
+
+TimelineControlType RowTimeline::getClickedControl(const QPointF &scenePos) const
+{
+ if (!m_rowTree->hasDurationBar())
+ return TimelineControlType::None;
+
+ if (!m_rowTree->locked()) {
+ QPointF p = mapFromScene(scenePos.x(), scenePos.y());
+ p.setX(p.x() - TimelineConstants::RULER_EDGE_OFFSET);
+
+ const int halfHandle = TimelineConstants::DURATION_HANDLE_W * .5;
+ // Never choose start handle if end time is zero, as you cannot adjust it in that case
+ bool startHandle = p.x() > m_startX - halfHandle && p.x() < m_startX + halfHandle
+ && m_endTime > 0;
+ bool endHandle = p.x() > m_endX - halfHandle && p.x() < m_endX + halfHandle;
+ if (startHandle && endHandle) {
+ // If handles overlap, choose the handle based on the side of the click relative to start
+ startHandle = p.x() < m_startX;
+ endHandle = !startHandle;
+ }
+ if (startHandle)
+ return TimelineControlType::StartHandle;
+ else if (endHandle)
+ return TimelineControlType::EndHandle;
+ else if (p.x() > m_startX && p.x() < m_endX && !rowTree()->locked())
+ return TimelineControlType::Duration;
+ }
+
+ return TimelineControlType::None;
+}
+
+void RowTimeline::startDurationMove(double clickX)
+{
+ // clickX is in ruler coordinate space
+ m_startDurationMoveStartTime = m_startTime;
+ m_startDurationMoveOffsetX = clickX - m_startX;
+}
+
+void RowTimeline::updateBoundChildren(bool start)
+{
+ // Collect all bound children
+ // Children are considered bound if the start/end time matches the parent time
+ if (start)
+ m_boundChildrenStart.clear();
+ else
+ m_boundChildrenEnd.clear();
+ if (m_rowTree->hasDurationBar()) {
+ const auto childRows = m_rowTree->childRows();
+ for (auto child : childRows) {
+ if (child->hasDurationBar() && !child->locked()) {
+ RowTimeline *rowTimeline = child->rowTimeline();
+ if (start && rowTimeline->m_startX == m_startX) {
+ m_boundChildrenStart.append(rowTimeline);
+ rowTimeline->updateBoundChildren(start);
+ } else if (!start && rowTimeline->m_endX == m_endX) {
+ m_boundChildrenEnd.append(rowTimeline);
+ rowTimeline->updateBoundChildren(start);
+ }
+ }
+ }
+ }
+}
+
+void RowTimeline::clearBoundChildren()
+{
+ m_boundChildrenStart.clear();
+ m_boundChildrenEnd.clear();
+}
+
+// move the duration area (start/end x)
+void RowTimeline::moveDurationBy(double dx)
+{
+ if (m_startX + dx < 0)
+ dx = -m_startX;
+
+ m_startX += dx;
+ m_endX += dx;
+
+ if (!m_rowTree->parentRow() || m_rowTree->objectType() == OBJTYPE_LAYER
+ || m_rowTree->hasComponentAncestor()) {
+ m_minStartX = m_startX;
+ m_maxEndX = m_endX;
+ }
+
+ Ruler *ruler = m_rowTree->m_scene->ruler();
+ m_startTime = ruler->distanceToTime(m_startX);
+ m_endTime = ruler->distanceToTime(m_endX);
+
+ // move keyframes with the row
+ if (!m_rowTree->isProperty()) { // make sure we don't move the keyframes twice
+ for (Keyframe *keyframe : qAsConst(m_keyframes))
+ keyframe->time += rowTree()->m_scene->ruler()->distanceToTime(dx);
+ }
+
+ update();
+
+ if (!m_rowTree->empty()) {
+ updateChildrenMinStartXRecursive(m_rowTree);
+ updateChildrenMaxEndXRecursive(m_rowTree);
+
+ for (RowTree *child : qAsConst(m_rowTree->m_childRows)) {
+ if (!child->locked())
+ child->m_rowTimeline->moveDurationBy(dx);
+ }
+ }
+}
+
+void RowTimeline::moveDurationTo(double newX)
+{
+ if (newX < 0)
+ newX = 0;
+
+ double dx = newX - m_startX;
+ double durationX = m_endX - m_startX;
+
+ m_startX = newX;
+ m_endX = m_startX + durationX;
+
+ if (!m_rowTree->parentRow() || m_rowTree->objectType() == OBJTYPE_LAYER
+ || m_rowTree->hasComponentAncestor()) {
+ m_minStartX = m_startX;
+ m_maxEndX = m_endX;
+ }
+
+ Ruler *ruler = m_rowTree->m_scene->ruler();
+ m_startTime = ruler->distanceToTime(m_startX);
+ m_endTime = ruler->distanceToTime(m_endX);
+
+ // move keyframes with the row
+ if (!m_rowTree->isProperty()) { // make sure we don't move the keyframes twice
+ for (Keyframe *keyframe : qAsConst(m_keyframes))
+ keyframe->time += ruler->distanceToTime(dx);
+ }
+
+ update();
+
+ if (!m_rowTree->empty()) {
+ updateChildrenMinStartXRecursive(m_rowTree);
+ updateChildrenMaxEndXRecursive(m_rowTree);
+
+ for (RowTree *child : qAsConst(m_rowTree->m_childRows)) {
+ if (!child->locked())
+ child->m_rowTimeline->moveDurationBy(dx);
+ }
+ }
+}
+
+long RowTimeline::getDurationMoveTime() const
+{
+ return m_startTime - m_startDurationMoveStartTime;
+}
+
+double RowTimeline::getDurationMoveOffsetX() const
+{
+ return m_startDurationMoveOffsetX;
+}
+
+long RowTimeline::getDuration() const
+{
+ return m_endTime - m_startTime;
+}
+
+void RowTimeline::collectChildKeyframeTimes(QVector<long> &childKeyframeTimes)
+{
+ const auto childRows = m_rowTree->childRows();
+ for (const auto row : childRows) {
+ row->rowTimeline()->collectChildKeyframeTimes(childKeyframeTimes);
+ const auto keyframes = row->rowTimeline()->keyframes();
+ for (const auto kf : keyframes)
+ childKeyframeTimes.append(kf->time);
+ }
+}
+
+// called after timeline scale is changed to update duration star/end positions
+void RowTimeline::updatePosition()
+{
+ clearBoundChildren();
+ setStartTime(m_startTime);
+ setEndTime(m_endTime);
+}
+
+// Set the position of the start of the row duration
+void RowTimeline::setStartX(double startX)
+{
+ if (startX < 0)
+ startX = 0;
+ else if (startX > m_endX)
+ startX = m_endX;
+
+ m_startX = startX;
+ m_startTime = m_rowTree->m_scene->ruler()->distanceToTime(startX);
+
+ if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE
+ || m_rowTree->hasComponentAncestor()) {
+ m_minStartX = 0;
+ }
+
+ updateChildrenStartRecursive();
+ updateChildrenMinStartXRecursive(m_rowTree);
+ update();
+}
+
+// Set the position of the end of the row duration
+void RowTimeline::setEndX(double endX)
+{
+ if (endX < m_startX)
+ endX = m_startX;
+
+ m_endX = endX;
+ m_endTime = m_rowTree->m_scene->ruler()->distanceToTime(endX);
+
+ if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE
+ || m_rowTree->hasComponentAncestor()) {
+ m_maxEndX = 999999;
+ }
+
+ updateChildrenEndRecursive();
+ updateChildrenMaxEndXRecursive(m_rowTree);
+ update();
+}
+
+QColor RowTimeline::barColor() const
+{
+ return m_barColor;
+}
+
+void RowTimeline::setBarColor(const QColor &color)
+{
+ m_barColor = color;
+ update();
+}
+
+void RowTimeline::setControllerText(const QString &controller)
+{
+ m_controllerDataInput = controller;
+ update();
+}
+
+void RowTimeline::updateChildrenStartRecursive()
+{
+ for (auto child : qAsConst(m_boundChildrenStart)) {
+ if (!child.isNull()) {
+ child->m_startX = m_startX;
+ child->m_startTime = m_startTime;
+ child->updateChildrenStartRecursive();
+ child->update();
+ }
+ }
+}
+
+void RowTimeline::updateChildrenEndRecursive()
+{
+ for (auto child : qAsConst(m_boundChildrenEnd)) {
+ if (!child.isNull()) {
+ child->m_endX = m_endX;
+ child->m_endTime = m_endTime;
+ child->updateChildrenEndRecursive();
+ child->update();
+ }
+ }
+}
+
+void RowTimeline::updateChildrenMinStartXRecursive(RowTree *rowTree)
+{
+ if (m_rowTree->objectType() != OBJTYPE_SCENE && !rowTree->empty()) {
+ const auto childRows = rowTree->childRows();
+ bool isComponentChild = m_rowTree->objectType() == OBJTYPE_COMPONENT
+ || m_rowTree->hasComponentAncestor();
+ for (auto child : childRows) {
+ if (isComponentChild) {
+ child->rowTimeline()->m_minStartX = 0;
+ } else {
+ child->rowTimeline()->m_minStartX = qMax(rowTree->rowTimeline()->m_startX,
+ rowTree->rowTimeline()->m_minStartX);
+ }
+ child->rowTimeline()->update();
+
+ updateChildrenMinStartXRecursive(child);
+ }
+ }
+}
+
+void RowTimeline::updateChildrenMaxEndXRecursive(RowTree *rowTree)
+{
+ if (m_rowTree->objectType() != OBJTYPE_SCENE && !rowTree->empty()) {
+ const auto childRows = rowTree->childRows();
+ bool isComponentChild = m_rowTree->objectType() == OBJTYPE_COMPONENT
+ || m_rowTree->hasComponentAncestor();
+ for (auto child : childRows) {
+ if (isComponentChild) {
+ child->rowTimeline()->m_maxEndX = 999999;
+ } else {
+ child->rowTimeline()->m_maxEndX = qMin(rowTree->rowTimeline()->m_endX,
+ rowTree->rowTimeline()->m_maxEndX);
+ }
+ child->rowTimeline()->update();
+
+ updateChildrenMaxEndXRecursive(child);
+ }
+ }
+}
+
+void RowTimeline::updateCommentItem()
+{
+ if (!m_commentItem)
+ return;
+ TimelineToolbar *toolbar = m_rowTree->m_scene->widgetTimeline()->toolbar();
+ // Backend allows storing comments for rows with duration bar
+ bool canHaveComment = m_rowTree->hasDurationBar();
+ bool showComments = canHaveComment && toolbar->actionShowRowTexts()->isChecked();
+ m_commentItem->setVisible(showComments);
+ if (showComments && m_rowTree->m_binding) {
+ ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar();
+ m_commentItem->setLabel(timebar->GetTimebarComment());
+ }
+}
+
+void RowTimeline::updateCommentItemPos()
+{
+ if (!m_commentItem)
+ return;
+
+ Ruler *ruler = m_rowTree->m_scene->ruler();
+ m_commentItem->setPos(TimelineConstants::RULER_EDGE_OFFSET + ruler->viewportX(),
+ -TimelineConstants::ROW_TEXT_OFFSET_Y);
+}
+
+void RowTimeline::setStartTime(long startTime)
+{
+ m_startTime = startTime;
+ m_startX = m_rowTree->m_scene->ruler()->timeToDistance(startTime);
+
+ if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE
+ || m_rowTree->hasComponentAncestor()) {
+ m_minStartX = 0;
+ }
+
+ updateChildrenStartRecursive();
+ updateChildrenMinStartXRecursive(m_rowTree);
+ update();
+}
+
+void RowTimeline::setEndTime(long endTime)
+{
+ m_endTime = endTime;
+ m_endX = m_rowTree->m_scene->ruler()->timeToDistance(endTime);
+
+ if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE
+ || m_rowTree->hasComponentAncestor()) {
+ m_maxEndX = 999999;
+ }
+
+ updateChildrenEndRecursive();
+ updateChildrenMaxEndXRecursive(m_rowTree);
+ update();
+}
+
+// duration start x in local space (x=0 at time=0)
+double RowTimeline::getStartX() const
+{
+ return m_startX;
+}
+
+// duration end x in local space
+double RowTimeline::getEndX() const
+{
+ return m_endX;
+}
+
+long RowTimeline::getStartTime() const
+{
+ return m_startTime;
+}
+
+long RowTimeline::getEndTime() const
+{
+ return m_endTime;
+}
+
+void RowTimeline::setState(State state)
+{
+ m_state = state;
+ m_rowTree->m_state = state;
+
+ update();
+ m_rowTree->update();
+}
+
+void RowTimeline::setRowTree(RowTree *rowTree)
+{
+ m_rowTree = rowTree;
+ if (m_rowTree->isProperty()) {
+ if (m_propertyGraph)
+ delete m_propertyGraph;
+ m_propertyGraph = new RowTimelinePropertyGraph(this);
+ }
+ initialize();
+}
+
+RowTree *RowTimeline::rowTree() const
+{
+ return m_rowTree;
+}
+
+QList<Keyframe *> RowTimeline::keyframes() const
+{
+ return m_keyframes;
+}
+
+QString RowTimeline::formatTime(long millis) const
+{
+ static const QString timeTemplate = tr("%1:%2.%3");
+ static const QChar fillChar = tr("0").at(0);
+
+ long mins = millis % 3600000 / 60000;
+ long secs = millis % 60000 / 1000;
+ long mils = millis % 1000;
+
+ return timeTemplate.arg(mins).arg(secs, 2, 10, fillChar).arg(mils, 3, 10, fillChar);
+}
+
+void RowTimeline::showToolTip(const QPointF &pos)
+{
+ QLabel *tooltip = m_rowTree->m_scene->timebarTooltip();
+
+ tooltip->setText(formatTime(m_startTime) + " - " + formatTime(m_endTime)
+ + " (" + formatTime(m_endTime - m_startTime) + ")");
+
+ tooltip->adjustSize();
+
+ QPoint newPos = pos.toPoint() + QPoint(-tooltip->width() / 2,
+ -tooltip->height() - TimelineConstants::TIMEBAR_TOOLTIP_OFFSET_V);
+
+ // Confine the tooltip to the current screen area to avoid artifacts from different pixel ratios
+ static const int MARGIN = 5;
+ const QRect screenGeometry = QApplication::desktop()->screenGeometry(
+ m_rowTree->m_scene->widgetTimeline());
+ int xMin = screenGeometry.x() + MARGIN;
+ int xMax = screenGeometry.x() + screenGeometry.width() - tooltip->width() - MARGIN;
+ if (newPos.x() < xMin)
+ newPos.setX(xMin);
+ else if (newPos.x() > xMax)
+ newPos.setX(xMax);
+
+ tooltip->move(newPos);
+ tooltip->raise();
+ tooltip->show();
+}
+
+RowTimeline *RowTimeline::parentRow() const
+{
+ if (!m_rowTree->m_parentRow)
+ return nullptr;
+
+ return m_rowTree->m_parentRow->rowTimeline();
+}
+
+void RowTimeline::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+{
+ InteractiveTimelineItem::hoverLeaveEvent(event);
+ // Make sure mouse cursor is reseted when moving away from timeline row
+ m_rowTree->m_scene->resetMouseCursor();
+}
+
+int RowTimeline::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeRowTimeline;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h
new file mode 100644
index 00000000..00c81696
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTIMELINE_H
+#define ROWTIMELINE_H
+
+#include "InteractiveTimelineItem.h"
+#include "RowTypes.h"
+#include "Bindings/Qt3DSDMTimelineItemProperty.h"
+#include <QtCore/qpointer.h>
+#include "RowTimelineCommentItem.h"
+
+class RowTree;
+class RowTimelinePropertyGraph;
+struct Keyframe;
+
+class RowTimeline : public InteractiveTimelineItem
+{
+ Q_OBJECT
+
+public:
+ explicit RowTimeline();
+ ~RowTimeline();
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ void setState(State state) override;
+ void setRowTree(RowTree *rowTree);
+ void updatePosition();
+ void startDurationMove(double clickX);
+ void updateBoundChildren(bool start);
+ void clearBoundChildren();
+ void moveDurationBy(double dx);
+ void moveDurationTo(double newX);
+ void setStartTime(long startTime);
+ void setEndTime(long endTime);
+ void setStartX(double startX);
+ void setEndX(double endX);
+ void setBarColor(const QColor &color);
+ void setControllerText(const QString &controller);
+ void putSelectedKeyframesOnTop();
+ void updateKeyframes();
+ void insertKeyframe(Keyframe *keyframe);
+ void removeKeyframe(Keyframe *keyframe);
+ void updateKeyframesFromBinding(const QList<int> &properties);
+ void updateDurationFromBinding();
+ TimelineControlType getClickedControl(const QPointF &scenePos) const;
+ double getStartX() const;
+ double getEndX() const;
+ long getStartTime() const;
+ long getEndTime() const;
+ long getDurationMoveTime() const; // the time a row duration has moved (to commit to binding)
+ double getDurationMoveOffsetX() const;
+ long getDuration() const;
+ QColor barColor() const;
+ int type() const override;
+ RowTimeline *parentRow() const;
+ RowTree *rowTree() const;
+ Keyframe *getClickedKeyframe(const QPointF &scenePos);
+ QList<Keyframe *> getKeyframesInRange(const QRectF &rect) const;
+ QList<Keyframe *> keyframes() const;
+ void showToolTip(const QPointF &pos);
+
+protected:
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
+
+private:
+ void initialize();
+ void updateChildrenStartRecursive();
+ void updateChildrenEndRecursive();
+ void updateChildrenMinStartXRecursive(RowTree *rowTree);
+ void updateChildrenMaxEndXRecursive(RowTree *rowTree);
+ void updateCommentItem();
+ void updateCommentItemPos();
+ void drawColorPropertyGradient(QPainter *painter, int width);
+ bool isColorProperty() const;
+ QString formatTime(long millis) const;
+ void collectChildKeyframeTimes(QVector<long> &childKeyframeTimes);
+
+ RowTree *m_rowTree;
+ RowTimelinePropertyGraph *m_propertyGraph = nullptr;
+ RowTimelineCommentItem *m_commentItem = nullptr;
+ long m_startTime = 0;
+ long m_startDurationMoveStartTime = 0;
+ double m_startDurationMoveOffsetX = 0;
+ long m_endTime = 0;
+ double m_startX = 0;
+ double m_endX = 0;
+ double m_minStartX = 0;
+ double m_maxEndX = 0;
+ bool m_isProperty = false; // used in the destructor
+ QString m_controllerDataInput;
+ QList<Keyframe *> m_keyframes;
+ QColor m_barColor;
+ QVector<QPointer<RowTimeline>> m_boundChildrenStart;
+ QVector<QPointer<RowTimeline>> m_boundChildrenEnd;
+
+ friend class RowTree;
+};
+
+#endif // ROWTIMELINE_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp
new file mode 100644
index 00000000..1bfb7163
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp
@@ -0,0 +1,151 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTimelineCommentItem.h"
+#include "TimelineConstants.h"
+#include "TimelineItem.h"
+#include "RowTree.h"
+#include "StudioPreferences.h"
+
+#include <QtWidgets/qstyleoption.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qtextcursor.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qtextoption.h>
+#include <QtGui/qtextdocument.h>
+
+static const int MAX_COMMENT_SIZE = 2000; // Should be enough
+
+RowTimelineCommentItem::RowTimelineCommentItem(QGraphicsItem *parent)
+ : QGraphicsTextItem(parent)
+ , m_acceptOnFocusOut(true)
+{
+ setTextInteractionFlags(Qt::TextEditorInteraction);
+ setTextWidth(MAX_COMMENT_SIZE);
+ setDefaultTextColor(CStudioPreferences::textColor());
+ setVisible(false);
+}
+
+QString RowTimelineCommentItem::label() const
+{
+ return m_label;
+}
+
+void RowTimelineCommentItem::setLabel(const QString &label)
+{
+ setPlainText(label);
+ if (m_label != label) {
+ m_label = label;
+ emit labelChanged(m_label);
+ }
+}
+
+RowTree *RowTimelineCommentItem::parentRow() const
+{
+ return m_rowTree;
+}
+
+void RowTimelineCommentItem::setParentRow(RowTree *row)
+{
+ m_rowTree = row;
+}
+
+int RowTimelineCommentItem::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TimelineItem::TypeRowTimelineCommentItem;
+}
+
+void RowTimelineCommentItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ // prevents flickering when the row is just inserted to the layout
+ if (m_rowTree && !m_rowTree->y())
+ return;
+
+ // Paint background
+ QRectF r = boundingRect();
+ r.adjust(-TimelineConstants::RULER_EDGE_OFFSET,
+ TimelineConstants::ROW_TEXT_OFFSET_Y, 0,
+ TimelineConstants::ROW_TEXT_OFFSET_Y);
+ painter->fillRect(r, CStudioPreferences::timelineRowCommentBgColor());
+
+ // Remove the HasFocus style state, to prevent the dotted line from being drawn.
+ QStyleOptionGraphicsItem *style = const_cast<QStyleOptionGraphicsItem *>(option);
+ style->state &= ~QStyle::State_HasFocus;
+
+ QGraphicsTextItem::paint(painter, option, widget);
+}
+
+void RowTimelineCommentItem::focusOutEvent(QFocusEvent *event)
+{
+ if (m_acceptOnFocusOut)
+ validateLabel();
+ else
+ setPlainText(m_label);
+
+ // Remove possible selection
+ QTextCursor cursor = textCursor();
+ cursor.clearSelection();
+ setTextCursor(cursor);
+ QGraphicsTextItem::focusOutEvent(event);
+ // Next time default to accepting
+ m_acceptOnFocusOut = true;
+}
+
+void RowTimelineCommentItem::keyPressEvent(QKeyEvent *event)
+{
+ int key = event->key();
+ if (key == Qt::Key_Return || key == Qt::Key_Enter) {
+ m_acceptOnFocusOut = true;
+ clearFocus();
+ event->accept();
+ return;
+ } else if (key == Qt::Key_Escape) {
+ m_acceptOnFocusOut = false;
+ clearFocus();
+ event->accept();
+ return;
+ }
+
+ QGraphicsTextItem::keyPressEvent(event);
+}
+
+QRectF RowTimelineCommentItem::boundingRect() const
+{
+ return QRectF(0, 0, parentItem()->boundingRect().width(),
+ TimelineConstants::ROW_H);
+}
+
+void RowTimelineCommentItem::validateLabel()
+{
+ QString text = toPlainText().trimmed();
+ text = text.left(MAX_COMMENT_SIZE);
+ setLabel(text);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h
new file mode 100644
index 00000000..48c5065f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTIMELINECOMMENTITEM_H
+#define ROWTIMELINECOMMENTITEM_H
+
+#include "StudioObjectTypes.h"
+#include <QtWidgets/qgraphicsitem.h>
+#include <QtCore/qstring.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+#include <QtGui/qevent.h>
+
+class RowTree;
+
+class RowTimelineCommentItem : public QGraphicsTextItem
+{
+ Q_OBJECT
+public:
+ explicit RowTimelineCommentItem(QGraphicsItem *parent = nullptr);
+
+ QString label() const;
+ void setLabel(const QString &label);
+ RowTree *parentRow() const;
+ void setParentRow(RowTree *row);
+ int type() const override;
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ QRectF boundingRect() const override;
+
+signals:
+ void labelChanged(const QString label);
+
+private:
+ void validateLabel();
+
+ RowTree *m_rowTree = nullptr;
+ QString m_label;
+ bool m_acceptOnFocusOut;
+
+};
+
+#endif // ROWTIMELINECOMMENTITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp
new file mode 100644
index 00000000..7861bb19
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp
@@ -0,0 +1,275 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTimelineContextMenu.h"
+#include "RowTree.h"
+#include "Keyframe.h"
+#include "KeyframeManager.h"
+#include "MainFrm.h"
+#include "StudioApp.h"
+#include "TimelineControl.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "TimelineGraphicsScene.h"
+#include "TimelineToolbar.h"
+
+RowTimelineContextMenu::RowTimelineContextMenu(RowTree *inRowTree,
+ KeyframeManager *inKeyframeManager,
+ QGraphicsSceneContextMenuEvent *inEvent,
+ TimelineControl *timelineControl,
+ QWidget *parent)
+ : QMenu(parent)
+ , m_rowTree(inRowTree)
+ , m_keyframeManager(inKeyframeManager)
+ , m_menuEvent(inEvent)
+ , m_timelineControl(timelineControl)
+{
+ initialize();
+}
+
+RowTimelineContextMenu::~RowTimelineContextMenu()
+{
+}
+
+void RowTimelineContextMenu::initialize()
+{
+ m_insertKeyframeAction = new QAction(tr("Insert Keyframe"), this);
+ m_insertKeyframeAction->setShortcut(Qt::Key_S);
+ m_insertKeyframeAction->setShortcutVisibleInContextMenu(true);
+ connect(m_insertKeyframeAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::insertKeyframe);
+ addAction(m_insertKeyframeAction);
+
+ m_cutSelectedKeyframesAction = new QAction(tr("Cut Selected Keyframe"), this);
+ m_cutSelectedKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_X));
+ m_cutSelectedKeyframesAction->setShortcutVisibleInContextMenu(true);
+ connect(m_cutSelectedKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::cutSelectedKeyframes);
+ addAction(m_cutSelectedKeyframesAction);
+
+ m_copySelectedKeyframesAction = new QAction(tr("Copy Selected Keyframe"), this);
+ m_copySelectedKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_C));
+ m_copySelectedKeyframesAction->setShortcutVisibleInContextMenu(true);
+ connect(m_copySelectedKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::copySelectedKeyframes);
+ addAction(m_copySelectedKeyframesAction);
+
+ m_pasteKeyframesAction = new QAction(tr("Paste Keyframes"), this);
+ m_pasteKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_V));
+ m_pasteKeyframesAction->setShortcutVisibleInContextMenu(true);
+ connect(m_pasteKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::pasteKeyframes);
+ addAction(m_pasteKeyframesAction);
+
+ m_deleteSelectedKeyframesAction = new QAction(tr("Delete Selected Keyframe"), this);
+ m_deleteSelectedKeyframesAction->setShortcut(Qt::Key_Delete);
+ m_deleteSelectedKeyframesAction->setShortcutVisibleInContextMenu(true);
+ connect(m_deleteSelectedKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::deleteSelectedKeyframes);
+ addAction(m_deleteSelectedKeyframesAction);
+
+ m_deleteRowKeyframesAction = new QAction(tr("Delete All Channel Keyframes"), this);
+ m_deleteRowKeyframesAction->setShortcut(
+ QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_K));
+ m_deleteRowKeyframesAction->setShortcutVisibleInContextMenu(true);
+ connect(m_deleteRowKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::deleteRowKeyframes);
+ addAction(m_deleteRowKeyframesAction);
+
+ m_keyframe = m_rowTree->rowTimeline()->getClickedKeyframe(m_menuEvent->scenePos());
+ bool ctrlPressed = m_menuEvent->modifiers() & Qt::ControlModifier;
+ if (m_keyframe) {
+ if (!m_keyframe->selected() && !ctrlPressed)
+ m_keyframeManager->deselectAllKeyframes();
+
+ m_keyframeManager->selectKeyframe(m_keyframe);
+ } else {
+ m_keyframeManager->deselectAllKeyframes();
+ }
+
+ if (m_rowTree->rowTimeline()->keyframes().size()) {
+ m_hasDynamicKeyframes = m_keyframeManager->hasDynamicKeyframes(m_rowTree);
+ QString label;
+ if (m_hasDynamicKeyframes)
+ label = tr("Make Animations Static");
+ else
+ label = tr("Make Animations Dynamic");
+
+ m_dynamicKeyframesAction = new QAction(label, this);
+ connect(m_dynamicKeyframesAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::toggleDynamicKeyframes);
+ addAction(m_dynamicKeyframesAction);
+ }
+
+ addSeparator();
+
+ if (m_keyframe) {
+ m_setInterpolationAction = new QAction(tr("Set Interpolation..."), this);
+ m_setInterpolationAction->setShortcut(Qt::Key_I);
+ m_setInterpolationAction->setShortcutVisibleInContextMenu(true);
+ connect(m_setInterpolationAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::setInterpolation);
+ addAction(m_setInterpolationAction);
+
+ m_setKeyframeTimeAction = new QAction(tr("Set Keyframe Time..."), this);
+ connect(m_setKeyframeTimeAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::setKeyframeTime);
+ addAction(m_setKeyframeTimeAction);
+ } else {
+ m_setTimeBarColorAction = new QAction(tr("Change Time Bar Color..."), this);
+ connect(m_setTimeBarColorAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::changeTimeBarColor);
+ addAction(m_setTimeBarColorAction);
+
+ m_setTimeBarTimeAction = new QAction(tr("Set Time Bar Time..."), this);
+ m_setTimeBarTimeAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_T));
+ m_setTimeBarTimeAction->setShortcutVisibleInContextMenu(true);
+ connect(m_setTimeBarTimeAction, &QAction::triggered, this,
+ &RowTimelineContextMenu::setTimeBarTime);
+ addAction(m_setTimeBarTimeAction);
+
+ QAction *showRowTextsAction
+ = m_rowTree->m_scene->widgetTimeline()->toolbar()->actionShowRowTexts();
+ showRowTextsAction->setShortcutVisibleInContextMenu(true);
+ addAction(showRowTextsAction);
+ }
+}
+
+void RowTimelineContextMenu::showEvent(QShowEvent *event)
+{
+ bool propRow = m_rowTree->isProperty();
+ bool hasPropRows = m_rowTree->hasPropertyChildren();
+
+ m_insertKeyframeAction->setEnabled(!m_keyframe && (propRow || hasPropRows));
+ m_cutSelectedKeyframesAction->setEnabled(m_keyframeManager->oneMasterRowSelected());
+ m_copySelectedKeyframesAction->setEnabled(m_keyframeManager->oneMasterRowSelected());
+ m_pasteKeyframesAction->setEnabled(m_keyframeManager->hasCopiedKeyframes());
+ m_deleteSelectedKeyframesAction->setEnabled(m_keyframeManager->hasSelectedKeyframes());
+ m_deleteRowKeyframesAction->setEnabled(!m_rowTree->rowTimeline()->keyframes().empty());
+ if (!m_keyframe) {
+ m_setTimeBarColorAction->setEnabled(m_rowTree->hasDurationBar());
+ m_setTimeBarTimeAction->setEnabled(m_rowTree->hasDurationBar());
+ }
+
+ QMenu::showEvent(event);
+}
+
+void RowTimelineContextMenu::insertKeyframe()
+{
+ RowTree *destinationRowTree = nullptr;
+ if (m_rowTree->isProperty()) {
+ // When inserting into a property, insert actually into
+ // its parent rowtree
+ destinationRowTree = m_rowTree->parentRow();
+ } else {
+ destinationRowTree = m_rowTree;
+ }
+
+ destinationRowTree->getBinding()->InsertKeyframe();
+}
+
+void RowTimelineContextMenu::cutSelectedKeyframes()
+{
+ m_keyframeManager->copySelectedKeyframes();
+ m_keyframeManager->deleteSelectedKeyframes();
+}
+
+void RowTimelineContextMenu::copySelectedKeyframes()
+{
+ m_keyframeManager->copySelectedKeyframes();
+}
+
+void RowTimelineContextMenu::pasteKeyframes()
+{
+ m_keyframeManager->pasteKeyframes();
+}
+
+void RowTimelineContextMenu::deleteSelectedKeyframes()
+{
+ m_keyframeManager->deleteSelectedKeyframes();
+}
+
+void RowTimelineContextMenu::deleteRowKeyframes()
+{
+ RowTree *destinationRowTree = nullptr;
+ if (m_rowTree->isProperty()) {
+ // Can't delete nicely just from property, so get the actual object row
+ destinationRowTree = m_rowTree->parentRow();
+ } else {
+ destinationRowTree = m_rowTree;
+ }
+ destinationRowTree->getBinding()->DeleteAllChannelKeyframes();
+}
+
+void RowTimelineContextMenu::setInterpolation()
+{
+ m_keyframeManager->SetKeyframeInterpolation();
+}
+
+void RowTimelineContextMenu::setKeyframeTime()
+{
+ m_keyframeManager->SetKeyframeTime(m_keyframe->time);
+}
+
+void RowTimelineContextMenu::changeTimeBarColor()
+{
+ g_StudioApp.m_pMainWnd->OnTimelineSetTimeBarColor();
+}
+
+void RowTimelineContextMenu::setTimeBarTime()
+{
+ if (m_timelineControl) {
+ m_timelineControl->setRowTimeline(m_rowTree->rowTimeline());
+ m_timelineControl->showDurationEditDialog();
+ }
+}
+
+void RowTimelineContextMenu::toggleDynamicKeyframes()
+{
+ QList<Keyframe *> selectedKeyframes = m_keyframeManager->selectedKeyframes();
+
+ if (selectedKeyframes.isEmpty()) {
+ // If property row is clicked, only make that property's first keyframe dynamic.
+ // Otherwise make all properties' first keyframes dynamic
+ // Note that it doesn't matter which keyframe we make dynamic, as the dynamic keyframe will
+ // automatically change to the first one in time order.
+ QList<Keyframe *> keyframes;
+ if (m_rowTree->isProperty()) {
+ keyframes.append(m_rowTree->rowTimeline()->keyframes().first());
+ } else {
+ const auto childProps = m_rowTree->childProps();
+ for (const auto prop : childProps)
+ keyframes.append(prop->rowTimeline()->keyframes().first());
+ }
+ m_keyframeManager->selectKeyframes(keyframes);
+ }
+
+ m_keyframeManager->SetKeyframesDynamic(!m_hasDynamicKeyframes);
+
+ if (selectedKeyframes.isEmpty())
+ m_keyframeManager->deselectAllKeyframes();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h
new file mode 100644
index 00000000..b8c2f922
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTIMELINECONTEXTMENU_H
+#define ROWTIMELINECONTEXTMENU_H
+
+#include <QtWidgets/qmenu.h>
+#include <QtWidgets/qaction.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+
+class RowTree;
+class KeyframeManager;
+class TimelineControl;
+struct Keyframe;
+
+class RowTimelineContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ explicit RowTimelineContextMenu(RowTree *inRowTree,
+ KeyframeManager *inKeyframeManager,
+ QGraphicsSceneContextMenuEvent *inEvent,
+ TimelineControl *timelineControl,
+ QWidget *parent = nullptr);
+ virtual ~RowTimelineContextMenu();
+
+protected:
+ void showEvent(QShowEvent *event) override;
+
+private:
+ void initialize();
+ void insertKeyframe();
+ void cutSelectedKeyframes();
+ void copySelectedKeyframes();
+ void pasteKeyframes();
+ void deleteSelectedKeyframes();
+ void deleteRowKeyframes();
+ void setInterpolation();
+ void setKeyframeTime();
+ void changeTimeBarColor();
+ void setTimeBarTime();
+ void toggleDynamicKeyframes();
+
+ RowTree *m_rowTree = nullptr;
+ Keyframe *m_keyframe = nullptr;
+ KeyframeManager *m_keyframeManager = nullptr;
+ QGraphicsSceneContextMenuEvent *m_menuEvent = nullptr;
+ QAction *m_insertKeyframeAction = nullptr;
+ QAction *m_cutSelectedKeyframesAction = nullptr;
+ QAction *m_copySelectedKeyframesAction = nullptr;
+ QAction *m_pasteKeyframesAction = nullptr;
+ QAction *m_deleteSelectedKeyframesAction = nullptr;
+ QAction *m_deleteRowKeyframesAction = nullptr;
+ QAction *m_setInterpolationAction = nullptr;
+ QAction *m_setKeyframeTimeAction = nullptr;
+ QAction *m_setTimeBarColorAction = nullptr;
+ QAction *m_setTimeBarTimeAction = nullptr;
+ QAction *m_dynamicKeyframesAction = nullptr;
+ TimelineControl *m_timelineControl = nullptr;
+ bool m_hasDynamicKeyframes = false;
+};
+
+#endif // ROWTIMELINECONTEXTMENU_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp
new file mode 100644
index 00000000..3c82d73b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTimelinePropertyGraph.h"
+#include "RowTimeline.h"
+#include "RowTree.h"
+#include "Ruler.h"
+#include "TimelineGraphicsScene.h"
+#include "Bindings/ITimelineItemProperty.h"
+
+RowTimelinePropertyGraph::RowTimelinePropertyGraph(QObject *parent)
+ : QObject(parent)
+{
+ m_rowTimeline = static_cast<RowTimeline *>(parent);
+}
+
+void RowTimelinePropertyGraph::paintGraphs(QPainter *painter, const QRectF &rect)
+{
+ m_rect = rect;
+ m_propBinding = m_rowTimeline->rowTree()->propBinding();
+
+ // Animate alpha 0..255 while expanding
+ int alpha = 255 * (m_rect.height() - TimelineConstants::ROW_H)
+ / (TimelineConstants::ROW_H_EXPANDED - TimelineConstants::ROW_H);
+ alpha = std::max(0, alpha);
+
+ if (alpha == 0)
+ return;
+
+ // Available line colors
+ QColor colors[6] = { QColor(255, 0, 0, alpha), QColor(0, 255, 0, alpha),
+ QColor(0, 0, 255, alpha), QColor(255, 255, 0, alpha),
+ QColor(255, 0, 255, alpha), QColor(0, 255, 255, alpha) };
+
+ long channelCount = m_propBinding->GetChannelCount();
+
+ // Don't want to overflow the color array
+ if (channelCount <= 6) {
+ // For each channel graph it.
+ for (long i = 0; i < channelCount; ++i)
+ paintSingleChannel(painter, i, colors[i]);
+ }
+}
+
+void RowTimelinePropertyGraph::paintSingleChannel(QPainter *painter, long inChannelIndex,
+ const QColor &inColor)
+{
+ float maxVal = m_propBinding->GetMaximumValue();
+ float minVal = m_propBinding->GetMinimumValue();
+
+ double timelineScale = m_rowTimeline->rowTree()->m_scene->ruler()->timelineScale();
+
+ // Step in pixels
+ int interval = 5;
+ // Margin at top & bottom of graph
+ float marginY = 10;
+ float graphY = m_rect.y() + marginY;
+ float graphHeight = m_rect.height() - marginY * 2;
+
+ QPainterPath path;
+ for (int i = 0; i < m_rect.width(); i += interval) {
+ // Value time in ms
+ long time = i / (TimelineConstants::RULER_MILLI_W * timelineScale);
+ float value = m_propBinding->GetChannelValueAtTime(inChannelIndex, time);
+ float yPos = graphY + (1.0 - (value - minVal) / (maxVal - minVal)) * graphHeight;
+
+ if (i == 0)
+ path.moveTo(m_rect.x() + i, yPos);
+ else
+ path.lineTo(m_rect.x() + i, yPos);
+ }
+
+ painter->setPen(QPen(inColor, 2));
+ painter->drawPath(path);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h
new file mode 100644
index 00000000..275c24e2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTIMELINEPROPERTYGRAPH_H
+#define ROWTIMELINEPROPERTYGRAPH_H
+
+#include <QtCore/qobject.h>
+#include <QtGui/qpainter.h>
+
+class RowTimeline;
+class ITimelineItemProperty;
+
+class RowTimelinePropertyGraph : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RowTimelinePropertyGraph(QObject *parent = nullptr);
+ void paintGraphs(QPainter *painter, const QRectF &rect);
+
+private:
+ void paintSingleChannel(QPainter *painter, long inChannelIndex,
+ const QColor &inColor);
+
+ RowTimeline *m_rowTimeline = nullptr;
+ ITimelineItemProperty *m_propBinding = nullptr;
+ QRectF m_rect;
+};
+
+#endif // ROWTIMELINEPROPERTYGRAPH_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp
new file mode 100644
index 00000000..e15258cd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp
@@ -0,0 +1,1337 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTree.h"
+#include "RowTimeline.h"
+#include "RowManager.h"
+#include "TimelineConstants.h"
+#include "StudioObjectTypes.h"
+#include "TimelineGraphicsScene.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "Qt3DSString.h"
+#include "TreeHeader.h"
+#include "StudioPreferences.h"
+#include "KeyframeManager.h"
+#include "StudioApp.h"
+#include "MainFrm.h"
+#include "Core.h"
+#include "Doc.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "StudioUtils.h"
+#include "TimelineToolbar.h"
+
+#include <QtGui/qpainter.h>
+#include "QtGui/qtextcursor.h"
+#include <QtWidgets/qgraphicslinearlayout.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+
+// object row constructor
+RowTree::RowTree(TimelineGraphicsScene *timelineScene, EStudioObjectType objType,
+ const QString &label)
+ : m_rowTimeline(new RowTimeline())
+ , m_scene(timelineScene)
+ , m_objectType(objType)
+ , m_label(label)
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ m_onMasterSlide = doc->GetStudioSystem()->GetSlideSystem()
+ ->IsMasterSlide(doc->GetActiveSlide());
+
+ initialize();
+}
+
+// property row constructor
+RowTree::RowTree(TimelineGraphicsScene *timelineScene, const QString &propType)
+ : InteractiveTimelineItem()
+ , m_rowTimeline(new RowTimeline())
+ , m_isProperty(true)
+ , m_scene(timelineScene)
+ , m_propertyType(propType)
+ , m_label(propType)
+{
+ m_rowTimeline->m_isProperty = true;
+
+ initialize();
+}
+
+RowTree::~RowTree()
+{
+ delete m_rowTimeline; // this will also delete the keyframes
+ m_rowTimeline = nullptr;
+}
+
+ITimelineItemBinding *RowTree::getBinding() const
+{
+ return m_binding;
+}
+
+// object instance handle
+ qt3dsdm::Qt3DSDMInstanceHandle RowTree::instance() const
+{
+ if (m_isProperty || !m_binding)
+ return 0;
+
+ return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->GetInstance();
+}
+
+void RowTree::initialize()
+{
+ setTimelineRow(m_rowTimeline);
+ m_rowTimeline->setRowTree(this);
+
+ setMinimumWidth(TimelineConstants::TREE_BOUND_W);
+
+ initializeAnimations();
+
+ m_labelItem.setParentItem(this);
+ m_labelItem.setParentRow(this);
+ m_labelItem.setLabel(m_label);
+ updateLabelPosition();
+
+ // Default all rows to collapsed
+ setRowVisible(false);
+ m_expandState = ExpandState::HiddenCollapsed;
+
+ connect(&m_labelItem, &RowTreeLabelItem::labelChanged, this,
+ [this](const QString &label) {
+ // Update label on timeline and on model
+ m_label = label;
+ // TODO: Get rid of CString APIs
+ auto clabel = Q3DStudio::CString::fromQString(m_label);
+ m_binding->GetTimelineItem()->SetName(clabel);
+ });
+}
+
+void RowTree::initializeAnimations()
+{
+ // Init left side expand animations
+ m_expandHeightAnimation = new QPropertyAnimation(this, "maximumSize");
+ m_expandHeightAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION);
+ m_expandAnimation.addAnimation(m_expandHeightAnimation);
+ m_expandOpacityAnimation = new QPropertyAnimation(this, "opacity");
+ m_expandOpacityAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION / 3);
+ m_expandAnimation.addAnimation(m_expandOpacityAnimation);
+
+ // Init right side expand animations
+ m_expandTimelineHeightAnimation = new QPropertyAnimation(m_rowTimeline, "maximumSize");
+ m_expandTimelineHeightAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION);
+ m_expandAnimation.addAnimation(m_expandTimelineHeightAnimation);
+ m_expandTimelineOpacityAnimation = new QPropertyAnimation(m_rowTimeline, "opacity");
+ m_expandTimelineOpacityAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION / 3);
+ m_expandAnimation.addAnimation(m_expandTimelineOpacityAnimation);
+
+ connect(&m_expandAnimation, &QAbstractAnimation::stateChanged,
+ [this](const QAbstractAnimation::State newState) {
+ if (m_rowTimeline) {
+ if (newState == QAbstractAnimation::Running) {
+ setVisible(true);
+ m_rowTimeline->setVisible(true);
+ } else if (newState == QAbstractAnimation::Stopped) {
+ if (this->maximumHeight() == 0) {
+ setVisible(false);
+ m_rowTimeline->setVisible(false);
+ }
+ }
+ }
+ });
+}
+
+void RowTree::animateExpand(ExpandState state)
+{
+ int endHeight = 0; // hidden states
+ float endOpacity = 0;
+ if (state == ExpandState::Expanded) {
+ endHeight = m_isPropertyExpanded ? TimelineConstants::ROW_H_EXPANDED
+ : TimelineConstants::ROW_H;
+ endOpacity = 1;
+ } else if (state == ExpandState::Collapsed) {
+ endHeight = TimelineConstants::ROW_H;
+ endOpacity = 1;
+ }
+ // Changing end values while animation is running does not affect currently running animation,
+ // so let's make sure the animation is stopped first.
+ m_expandAnimation.stop();
+
+ m_expandHeightAnimation->setEndValue(QSizeF(size().width(), endHeight));
+ m_expandTimelineHeightAnimation->setEndValue(QSizeF(m_rowTimeline->size().width(),
+ endHeight));
+ m_expandOpacityAnimation->setEndValue(endOpacity);
+ m_expandTimelineOpacityAnimation->setEndValue(endOpacity);
+
+ m_expandAnimation.start();
+}
+
+void RowTree::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0;
+
+ if (!y()) // prevents flickering when the row is just inserted to the layout
+ return;
+
+ static const int ICON_SIZE = 16;
+ static const int LEFT_DIVIDER = 18;
+ const int offset = 5 + m_depth * TimelineConstants::ROW_DEPTH_STEP;
+ const int iconY = (TimelineConstants::ROW_H / 2) - (ICON_SIZE / 2);
+
+ // update button bounds rects
+ m_rectArrow .setRect(offset, iconY, ICON_SIZE, ICON_SIZE);
+ m_rectType .setRect(offset + ICON_SIZE, iconY, ICON_SIZE, ICON_SIZE);
+ m_rectShy .setRect(treeWidth() - 16 * 3.3, iconY, ICON_SIZE, ICON_SIZE);
+ m_rectVisible.setRect(treeWidth() - 16 * 2.2, iconY, ICON_SIZE, ICON_SIZE);
+ m_rectLocked .setRect(treeWidth() - 16 * 1.1, iconY, ICON_SIZE, ICON_SIZE);
+
+ // Background
+ QColor bgColor;
+ if (m_dndState == DnDState::Source)
+ bgColor = CStudioPreferences::timelineRowColorDndSource();
+ else if (m_dndState == DnDState::SP_TARGET)
+ bgColor = CStudioPreferences::timelineRowColorDndTargetSP();
+ else if (m_isProperty)
+ bgColor = CStudioPreferences::timelineRowColorNormalProp();
+ else if (m_dndHover)
+ bgColor = CStudioPreferences::timelineRowColorDndTarget();
+ else if (m_state == Selected)
+ bgColor = CStudioPreferences::timelineRowColorSelected();
+ else if (m_state == Hovered && !m_locked)
+ bgColor = CStudioPreferences::timelineRowColorOver();
+ else
+ bgColor = CStudioPreferences::timelineRowColorNormal();
+
+ painter->fillRect(QRect(0, 0, size().width(), size().height() - 1), bgColor);
+
+ // left divider
+ painter->setPen(CStudioPreferences::timelineWidgetBgColor());
+ painter->drawLine(LEFT_DIVIDER, 0, LEFT_DIVIDER, size().height() - 1);
+
+ // Shy, eye, lock separator
+ painter->fillRect(QRect(treeWidth() - TimelineConstants::TREE_ICONS_W,
+ 0, 1, size().height()),
+ CStudioPreferences::timelineWidgetBgColor());
+
+ // Shy, eye, lock
+ static const QPixmap pixEmpty = QPixmap(":/images/Toggle-Empty.png");
+ static const QPixmap pixShy = QPixmap(":/images/Toggle-Shy.png");
+ static const QPixmap pixHide = QPixmap(":/images/Toggle-HideShow.png");
+ static const QPixmap pixHideDisabled = QPixmap(":/images/Toggle-HideShow-disabled.png");
+ static const QPixmap pixHideCtrld = QPixmap(":/images/Toggle-HideShowControlled.png");
+ static const QPixmap pixLock = QPixmap(":/images/Toggle-Lock.png");
+ static const QPixmap pixEmpty2x = QPixmap(":/images/Toggle-Empty@2x.png");
+ static const QPixmap pixShy2x = QPixmap(":/images/Toggle-Shy@2x.png");
+ static const QPixmap pixHide2x = QPixmap(":/images/Toggle-HideShow@2x.png");
+ static const QPixmap pixHideDisabled2x = QPixmap(":/images/Toggle-HideShow-disabled@2x.png");
+ static const QPixmap pixHideCtrld2x = QPixmap(":/images/Toggle-HideShowControlled@2x.png");
+ static const QPixmap pixLock2x = QPixmap(":/images/Toggle-Lock@2x.png");
+ if (hasActionButtons()) {
+ painter->drawPixmap(m_rectShy, hiResIcons ? (m_shy ? pixShy2x : pixEmpty2x)
+ : (m_shy ? pixShy : pixEmpty));
+ // Eyeball visibility follows the visibility setting for the object even if it has
+ // datainput controller
+ // Disable eyeball from master slide
+ if (m_onMasterSlide) {
+ painter->drawPixmap(m_rectVisible, hiResIcons ? pixHideDisabled2x
+ : pixHideDisabled);
+ } else if (m_visibilityCtrld) {
+ painter->drawPixmap(m_rectVisible, hiResIcons
+ ? (m_visible ? pixHideCtrld2x : pixEmpty2x)
+ : (m_visible ? pixHideCtrld : pixEmpty));
+ } else {
+ painter->drawPixmap(m_rectVisible, hiResIcons
+ ? (m_visible ? pixHide2x : pixEmpty2x)
+ : (m_visible ? pixHide : pixEmpty));
+ }
+ painter->drawPixmap(m_rectLocked, hiResIcons ? (m_locked ? pixLock2x : pixEmpty2x)
+ : (m_locked ? pixLock : pixEmpty));
+ }
+
+ static const QPixmap pixInsertLeft = QPixmap(":/images/Insert-Left.png");
+ static const QPixmap pixInsertRight = QPixmap(":/images/Insert-Right.png");
+ static const QPixmap pixInsertLeft2x = QPixmap(":/images/Insert-Left@2x.png");
+ static const QPixmap pixInsertRight2x = QPixmap(":/images/Insert-Right@2x.png");
+ if (m_dndState == DnDState::SP_TARGET) { // Candidate target of a subpresentation drop
+ painter->drawPixmap(19, 2, hiResIcons ? pixInsertLeft2x : pixInsertLeft);
+ painter->drawPixmap(treeWidth() - TimelineConstants::TREE_ICONS_W - 8, 2, hiResIcons
+ ? pixInsertRight2x : pixInsertRight);
+ } else if (m_dndState == DnDState::Parent) { // Candidate parent of a dragged row
+ painter->setPen(QPen(CStudioPreferences::timelineRowMoverColor(), 1));
+ painter->drawRect(QRect(1, 1, treeWidth() - 2, size().height() - 3));
+ }
+
+ // Action indicators
+ static const QPixmap pixMasterAction = QPixmap(":/images/Action-MasterAction.png");
+ static const QPixmap pixAction = QPixmap(":/images/Action-Action.png");
+ static const QPixmap pixChildMasterAction = QPixmap(":/images/Action-ChildMasterAction.png");
+ static const QPixmap pixChildAction = QPixmap(":/images/Action-ChildAction.png");
+ static const QPixmap pixCompMasterAction = QPixmap(":/images/Action-ComponentMasterAction.png");
+ static const QPixmap pixCompAction = QPixmap(":/images/Action-ComponentAction.png");
+ static const QPixmap pixMasterAction2x = QPixmap(":/images/Action-MasterAction@2x.png");
+ static const QPixmap pixAction2x = QPixmap(":/images/Action-Action@2x.png");
+ static const QPixmap pixChildMasterAction2x
+ = QPixmap(":/images/Action-ChildMasterAction@2x.png");
+ static const QPixmap pixChildAction2x = QPixmap(":/images/Action-ChildAction@2x.png");
+ static const QPixmap pixCompMasterAction2x
+ = QPixmap(":/images/Action-ComponentMasterAction@2x.png");
+ static const QPixmap pixCompAction2x = QPixmap(":/images/Action-ComponentAction@2x.png");
+
+ if (!isProperty()) {
+ // subpresentation indicators
+ if (m_hasSubpresentation) {
+ painter->fillRect(QRect(0, 0, LEFT_DIVIDER, size().height() - 1),
+ CStudioPreferences::timelineRowSubpColor());
+ } else if (!expanded() && m_numDescendantSubpresentations > 0) {
+ painter->fillRect(QRect(0, 0, LEFT_DIVIDER, size().height() - 1),
+ CStudioPreferences::timelineRowSubpDescendantColor());
+ }
+
+ if (m_actionStates & ActionState::MasterAction) // has master action
+ painter->drawPixmap(0, 0, hiResIcons ? pixMasterAction2x : pixMasterAction);
+ else if (m_actionStates & ActionState::Action) // has action
+ painter->drawPixmap(0, 0, hiResIcons ? pixAction2x : pixAction);
+
+ if (!expanded()) {
+ if (m_actionStates & ActionState::MasterChildAction) {
+ // children have master action
+ painter->drawPixmap(0, 0, hiResIcons ? pixChildMasterAction2x
+ : pixChildMasterAction);
+ } else if (m_actionStates & ActionState::ChildAction) {
+ // children have action
+ painter->drawPixmap(0, 0, hiResIcons ? pixChildAction2x : pixChildAction);
+ }
+ }
+
+ if (m_actionStates & ActionState::MasterComponentAction) // component has master action
+ painter->drawPixmap(0, 0, hiResIcons ? pixCompMasterAction2x : pixCompMasterAction);
+ else if (m_actionStates & ActionState::ComponentAction) // component has action
+ painter->drawPixmap(0, 0, hiResIcons ? pixCompAction2x : pixCompAction);
+ }
+
+ // variants indicator
+ if (m_variantsGroups.size() > 0) {
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ for (int i = 0; i < m_variantsGroups.size(); ++i) {
+ painter->fillRect(QRect(clipX() + 2 + i * 8, 6, 6, 6),
+ variantsDef[m_variantsGroups[i]].m_color);
+ painter->setPen(CStudioPreferences::timelineWidgetBgColor());
+ painter->drawRect(QRect(clipX() + 2 + i * 8, 6, 6, 6));
+ }
+ }
+
+ // The following items need to be clipped so that they do not draw overlapping shy etc. buttons
+
+ painter->setClipRect(0, 0, clipX(), TimelineConstants::ROW_H);
+
+ // expand/collapse arrow
+ static const QPixmap pixArrow = QPixmap(":/images/arrow.png");
+ static const QPixmap pixArrowDown = QPixmap(":/images/arrow_down.png");
+ static const QPixmap pixArrow2x = QPixmap(":/images/arrow@2x.png");
+ static const QPixmap pixArrowDown2x = QPixmap(":/images/arrow_down@2x.png");
+ if (m_arrowVisible) {
+ painter->drawPixmap(m_rectArrow, hiResIcons ? (expanded() ? pixArrowDown2x : pixArrow2x)
+ : (expanded() ? pixArrowDown : pixArrow));
+ }
+
+ // Row type icon
+ static const QPixmap pixSceneNormal = QPixmap(":/images/Objects-Scene-Normal.png");
+ static const QPixmap pixLayerNormal = QPixmap(":/images/Objects-Layer-Normal.png");
+ static const QPixmap pixObjectNormal = QPixmap(":/images/Objects-Model-Normal.png");
+ static const QPixmap pixLightNormal = QPixmap(":/images/Objects-Light-Normal.png");
+ static const QPixmap pixCameraNormal = QPixmap(":/images/Objects-Camera-Normal.png");
+ static const QPixmap pixTextNormal = QPixmap(":/images/Objects-Text-Normal.png");
+ static const QPixmap pixAliasNormal = QPixmap(":/images/Objects-Alias-Normal.png");
+ static const QPixmap pixGroupNormal = QPixmap(":/images/Objects-Group-Normal.png");
+ static const QPixmap pixComponentNormal = QPixmap(":/images/Objects-Component-Normal.png");
+ static const QPixmap pixMaterialNormal = QPixmap(":/images/Objects-Material-Normal.png");
+ static const QPixmap pixPropertyNormal = QPixmap(":/images/Objects-Property-Normal.png");
+ static const QPixmap pixImageNormal = QPixmap(":/images/Objects-Image-Normal.png");
+ static const QPixmap pixBehaviorNormal = QPixmap(":/images/Objects-Behavior-Normal.png");
+ static const QPixmap pixEffectNormal= QPixmap(":/images/Objects-Effect-Normal.png");
+ static const QPixmap pixSceneNormal2x = QPixmap(":/images/Objects-Scene-Normal@2x.png");
+ static const QPixmap pixLayerNormal2x = QPixmap(":/images/Objects-Layer-Normal@2x.png");
+ static const QPixmap pixObjectNormal2x = QPixmap(":/images/Objects-Model-Normal@2x.png");
+ static const QPixmap pixLightNormal2x = QPixmap(":/images/Objects-Light-Normal@2x.png");
+ static const QPixmap pixCameraNormal2x = QPixmap(":/images/Objects-Camera-Normal@2x.png");
+ static const QPixmap pixTextNormal2x = QPixmap(":/images/Objects-Text-Normal@2x.png");
+ static const QPixmap pixAliasNormal2x = QPixmap(":/images/Objects-Alias-Normal@2x.png");
+ static const QPixmap pixGroupNormal2x = QPixmap(":/images/Objects-Group-Normal@2x.png");
+ static const QPixmap pixComponentNormal2x = QPixmap(":/images/Objects-Component-Normal@2x.png");
+ static const QPixmap pixMaterialNormal2x = QPixmap(":/images/Objects-Material-Normal@2x.png");
+ static const QPixmap pixPropertyNormal2x = QPixmap(":/images/Objects-Property-Normal@2x.png");
+ static const QPixmap pixImageNormal2x = QPixmap(":/images/Objects-Image-Normal@2x.png");
+ static const QPixmap pixBehaviorNormal2x = QPixmap(":/images/Objects-Behavior-Normal@2x.png");
+ static const QPixmap pixEffectNormal2x = QPixmap(":/images/Objects-Effect-Normal@2x.png");
+
+ static const QPixmap pixSceneDisabled = QPixmap(":/images/Objects-Scene-Disabled.png");
+ static const QPixmap pixLayerDisabled = QPixmap(":/images/Objects-Layer-Disabled.png");
+ static const QPixmap pixObjectDisabled = QPixmap(":/images/Objects-Model-Disabled.png");
+ static const QPixmap pixLightDisabled = QPixmap(":/images/Objects-Light-Disabled.png");
+ static const QPixmap pixCameraDisabled = QPixmap(":/images/Objects-Camera-Disabled.png");
+ static const QPixmap pixTextDisabled = QPixmap(":/images/Objects-Text-Disabled.png");
+ static const QPixmap pixAliasDisabled = QPixmap(":/images/Objects-Alias-Disabled.png");
+ static const QPixmap pixGroupDisabled = QPixmap(":/images/Objects-Group-Disabled.png");
+ static const QPixmap pixComponentDisabled = QPixmap(":/images/Objects-Component-Disabled.png");
+ static const QPixmap pixMaterialDisabled = QPixmap(":/images/Objects-Material-Disabled.png");
+ static const QPixmap pixPropertyDisabled = QPixmap(":/images/Objects-Property-Disabled.png");
+ static const QPixmap pixImageDisabled = QPixmap(":/images/Objects-Image-Disabled.png");
+ static const QPixmap pixBehaviorDisabled = QPixmap(":/images/Objects-Behavior-Disabled.png");
+ static const QPixmap pixEffectDisabled = QPixmap(":/images/Objects-Effect-Disabled.png");
+ static const QPixmap pixSceneDisabled2x = QPixmap(":/images/Objects-Scene-Disabled@2x.png");
+ static const QPixmap pixLayerDisabled2x = QPixmap(":/images/Objects-Layer-Disabled@2x.png");
+ static const QPixmap pixObjectDisabled2x = QPixmap(":/images/Objects-Model-Disabled@2x.png");
+ static const QPixmap pixLightDisabled2x = QPixmap(":/images/Objects-Light-Disabled@2x.png");
+ static const QPixmap pixCameraDisabled2x = QPixmap(":/images/Objects-Camera-Disabled@2x.png");
+ static const QPixmap pixTextDisabled2x = QPixmap(":/images/Objects-Text-Disabled@2x.png");
+ static const QPixmap pixAliasDisabled2x = QPixmap(":/images/Objects-Alias-Disabled@2x.png");
+ static const QPixmap pixGroupDisabled2x = QPixmap(":/images/Objects-Group-Disabled@2x.png");
+ static const QPixmap pixComponentDisabled2x
+ = QPixmap(":/images/Objects-Component-Disabled@2x.png");
+ static const QPixmap pixMaterialDisabled2x
+ = QPixmap(":/images/Objects-Material-Disabled@2x.png");
+ static const QPixmap pixPropertyDisabled2x
+ = QPixmap(":/images/Objects-Property-Disabled@2x.png");
+ static const QPixmap pixImageDisabled2x = QPixmap(":/images/Objects-Image-Disabled@2x.png");
+ static const QPixmap pixBehaviorDisabled2x
+ = QPixmap(":/images/Objects-Behavior-Disabled@2x.png");
+ static const QPixmap pixEffectDisabled2x = QPixmap(":/images/Objects-Effect-Disabled@2x.png");
+
+ QPixmap pixRowType;
+ if (m_isProperty) {
+ pixRowType = hiResIcons ? (m_locked ? pixPropertyDisabled2x : pixPropertyNormal2x)
+ : (m_locked ? pixPropertyDisabled : pixPropertyNormal);
+ } else {
+ switch (m_objectType) {
+ case OBJTYPE_SCENE:
+ pixRowType = hiResIcons ? (m_locked ? pixSceneDisabled2x : pixSceneNormal2x)
+ : (m_locked ? pixSceneDisabled : pixSceneNormal);
+ break;
+ case OBJTYPE_LAYER:
+ pixRowType = hiResIcons ? (m_locked ? pixLayerDisabled2x : pixLayerNormal2x)
+ : (m_locked ? pixLayerDisabled : pixLayerNormal);
+ break;
+ case OBJTYPE_MODEL:
+ pixRowType = hiResIcons ? (m_locked ? pixObjectDisabled2x : pixObjectNormal2x)
+ : (m_locked ? pixObjectDisabled : pixObjectNormal);
+ break;
+ case OBJTYPE_LIGHT:
+ pixRowType = hiResIcons ? (m_locked ? pixLightDisabled2x : pixLightNormal2x)
+ : (m_locked ? pixLightDisabled : pixLightNormal);
+ break;
+ case OBJTYPE_CAMERA:
+ pixRowType = hiResIcons ? (m_locked ? pixCameraDisabled2x : pixCameraNormal2x)
+ : (m_locked ? pixCameraDisabled : pixCameraNormal);
+ break;
+ case OBJTYPE_TEXT:
+ pixRowType = hiResIcons ? (m_locked ? pixTextDisabled2x : pixTextNormal2x)
+ : (m_locked ? pixTextDisabled : pixTextNormal);
+ break;
+ case OBJTYPE_ALIAS:
+ pixRowType = hiResIcons ? (m_locked ? pixAliasDisabled2x : pixAliasNormal2x)
+ : (m_locked ? pixAliasDisabled : pixAliasNormal);
+ break;
+ case OBJTYPE_GROUP:
+ pixRowType = hiResIcons ? (m_locked ? pixGroupDisabled2x : pixGroupNormal2x)
+ : (m_locked ? pixGroupDisabled : pixGroupNormal);
+ break;
+ case OBJTYPE_COMPONENT:
+ pixRowType = hiResIcons ? (m_locked ? pixComponentDisabled2x : pixComponentNormal2x)
+ : (m_locked ? pixComponentDisabled : pixComponentNormal);
+ break;
+ case OBJTYPE_MATERIAL:
+ case OBJTYPE_CUSTOMMATERIAL:
+ case OBJTYPE_REFERENCEDMATERIAL:
+ pixRowType = hiResIcons ? (m_locked ? pixMaterialDisabled2x : pixMaterialNormal2x)
+ : (m_locked ? pixMaterialDisabled : pixMaterialNormal);
+ break;
+ case OBJTYPE_IMAGE:
+ pixRowType = hiResIcons ? (m_locked ? pixImageDisabled2x : pixImageNormal2x)
+ : (m_locked ? pixImageDisabled : pixImageNormal);
+ break;
+ case OBJTYPE_BEHAVIOR:
+ pixRowType = hiResIcons ? (m_locked ? pixBehaviorDisabled2x : pixBehaviorNormal2x)
+ : (m_locked ? pixBehaviorDisabled : pixBehaviorNormal);
+ break;
+ case OBJTYPE_EFFECT:
+ pixRowType = hiResIcons ? (m_locked ? pixEffectDisabled2x : pixEffectNormal2x)
+ : (m_locked ? pixEffectDisabled : pixEffectNormal);
+ break;
+ default:
+ break;
+ }
+ }
+
+ painter->drawPixmap(m_rectType, pixRowType);
+}
+
+void RowTree::updateVariants(const QStringList &groups)
+{
+ m_variantsGroups = groups;
+ update();
+}
+
+int RowTree::treeWidth() const
+{
+ return m_scene->treeWidth() - m_scene->getScrollbarOffsets().x();
+}
+
+void RowTree::setBinding(ITimelineItemBinding *binding)
+{
+ m_binding = binding;
+
+ // Restore the expansion state of rows
+ m_expandState = m_scene->expandMap().value(instance(), ExpandState::Unknown);
+
+ if (m_expandState == ExpandState::Unknown) {
+ // Everything but scene/component is initially collapsed and hidden
+ if (m_objectType == OBJTYPE_SCENE || m_objectType == OBJTYPE_COMPONENT)
+ m_expandState = ExpandState::Expanded;
+ else
+ m_expandState = ExpandState::HiddenCollapsed;
+ }
+
+ // Make sure all children of visible expanded parents are shown, and vice versa
+ if (parentRow()) {
+ if (parentRow()->expanded()) {
+ if (m_expandState == ExpandState::HiddenCollapsed)
+ m_expandState = ExpandState::Collapsed;
+ else if (m_expandState == ExpandState::HiddenExpanded)
+ m_expandState = ExpandState::Expanded;
+ } else {
+ if (m_expandState == ExpandState::Collapsed)
+ m_expandState = ExpandState::HiddenCollapsed;
+ else if (m_expandState == ExpandState::Expanded)
+ m_expandState = ExpandState::HiddenExpanded;
+ }
+ }
+
+ setRowVisible(m_expandState == ExpandState::Collapsed
+ || m_expandState == ExpandState::Expanded);
+
+ updateFromBinding();
+}
+
+// x value where label should clip
+int RowTree::clipX() const
+{
+ return treeWidth() - TimelineConstants::TREE_ICONS_W - m_variantsGroups.size() * 8 - 2;
+}
+
+ITimelineItemProperty *RowTree::propBinding()
+{
+ return m_PropBinding;
+}
+
+void RowTree::setPropBinding(ITimelineItemProperty *binding)
+{
+ m_PropBinding = binding;
+
+ if (parentRow()->expanded())
+ setRowVisible(true);
+
+ // Update label color
+ m_labelItem.setMaster(m_PropBinding->IsMaster());
+}
+
+void RowTree::setState(State state)
+{
+ m_state = state;
+ m_rowTimeline->m_state = state;
+
+ update();
+ m_rowTimeline->update();
+}
+
+void RowTree::setTimelineRow(RowTimeline *rowTimeline)
+{
+ m_rowTimeline = rowTimeline;
+}
+
+void RowTree::setParentRow(RowTree *parent)
+{
+ m_parentRow = parent;
+}
+
+void RowTree::selectLabel()
+{
+ m_labelItem.setEnabled(true);
+ m_labelItem.setFocus();
+ // Select all text
+ QTextCursor cursor = m_labelItem.textCursor();
+ cursor.select(QTextCursor::Document);
+ m_labelItem.setTextCursor(cursor);
+}
+
+RowTree *RowTree::parentRow() const
+{
+ return m_parentRow;
+}
+
+int RowTree::depth() const
+{
+ return m_depth;
+}
+
+EStudioObjectType RowTree::objectType() const
+{
+ return m_objectType;
+}
+
+QString RowTree::propertyType() const
+{
+ return m_propertyType;
+}
+
+int RowTree::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeRowTree;
+}
+
+int RowTree::index() const
+{
+ // first child in a parent has index 0
+ return m_index;
+}
+
+int RowTree::indexInLayout() const
+{
+ // first child (scene) at index 1, tree header at index 0 (invisible rows are also counted)
+ return m_indexInLayout;
+}
+
+void RowTree::addChild(RowTree *child)
+{
+ int index = getLastChildIndex(child->isProperty()) + 1;
+ addChildAt(child, index);
+}
+
+int RowTree::getLastChildIndex(bool isProperty) const
+{
+ int index = -1;
+ if (isProperty && !m_childProps.empty())
+ index = m_childProps.last()->index();
+ else if (!isProperty && !m_childRows.empty())
+ index = m_childRows.last()->index();
+
+ return index;
+}
+
+void RowTree::updateArrowVisibility()
+{
+ bool oldVisibility = m_arrowVisible;
+ if (m_childRows.empty() && m_childProps.empty()) {
+ m_arrowVisible = false;
+ } else {
+ if (m_childProps.empty()) {
+ m_arrowVisible = false;
+ for (RowTree *row : qAsConst(m_childRows)) {
+ if (!row->m_filtered) {
+ m_arrowVisible = true;
+ break;
+ }
+ }
+ } else {
+ m_arrowVisible = true;
+ }
+ }
+ if (oldVisibility != m_arrowVisible)
+ update();
+}
+
+bool RowTree::isInVariantsFilter() const
+{
+ const QString filterStr = g_StudioApp.m_pMainWnd->getVariantsFilterStr();
+
+ if (m_objectType & ~OBJTYPE_IS_VARIANT || filterStr.isEmpty()
+ || !m_scene->widgetTimeline()->toolbar()->isVariantsFilterOn()) {
+ return true;
+ }
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ auto property = bridge->getVariantsProperty(instance());
+
+ using namespace qt3dsdm;
+ SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance(), property, sValue)) {
+ QString propVal = get<TDataStrPtr>(sValue)->toQString();
+ const QStringList filterPairs = filterStr.split(QLatin1Char(','));
+ QHash<QString, bool> matches;
+ for (auto &filterPair : filterPairs) {
+ QString group = filterPair.left(filterPair.indexOf(QLatin1Char(':')) + 1);
+ if (propVal.contains(group)) { // the layer has 1 or more tags from this filter group
+ if (propVal.contains(filterPair))
+ matches[group] = true; // filter tag exists in the property variant group
+ else if (!matches.contains(group))
+ matches[group] = false;
+ }
+ }
+
+ for (auto m : qAsConst(matches)) {
+ if (!m)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void RowTree::updateFilter()
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ if (bridge->isMaterialContainer(instance()))
+ return;
+
+ bool parentOk = !m_parentRow || m_parentRow->isVisible();
+ bool shyOk = !m_shy || !m_scene->treeHeader()->filterShy();
+ bool visibleOk = m_visible || !m_scene->treeHeader()->filterHidden();
+ bool lockOk = !m_locked || !m_scene->treeHeader()->filterLocked();
+ bool expandOk = !expandHidden();
+ bool variantsOk = isInVariantsFilter();
+
+ m_filtered = !(shyOk && visibleOk && lockOk && variantsOk);
+ const bool visible = parentOk && expandOk && !m_filtered;
+ setVisible(visible);
+ m_rowTimeline->setVisible(visible);
+ for (auto propRow : qAsConst(m_childProps)) {
+ propRow->setVisible(visible);
+ propRow->m_rowTimeline->setVisible(visible);
+ }
+}
+
+int RowTree::getCountDecendentsRecursive() const
+{
+ int num = m_childProps.count();
+
+ for (auto child : qAsConst(m_childRows)) {
+ num++;
+ num += child->getCountDecendentsRecursive();
+ }
+
+ return num;
+}
+
+void RowTree::addChildAt(RowTree *child, int index)
+{
+ // Mahmoud_TODO: improvement: implement moving the child (instead of remove/add) if it is added
+ // under the same parent.
+
+ int maxIndex = getLastChildIndex(child->isProperty()) + 1;
+
+ if (index > maxIndex)
+ index = maxIndex;
+
+ if (child->parentRow() == this && index == child->m_index) // same place
+ return;
+
+ if (child->parentRow())
+ child->parentRow()->removeChild(child);
+
+ child->m_index = index;
+
+ QList<RowTree *> &childRows = child->isProperty() ? m_childProps : m_childRows;
+ int updateIndexInLayout = child->m_indexInLayout;
+ child->m_indexInLayout = m_indexInLayout + index + 1;
+
+ if (!child->isProperty()) {
+ child->m_indexInLayout += m_childProps.count();
+
+ if (m_childRows.size() >= index) {
+ for (int i = 0; i < index; ++i)
+ child->m_indexInLayout += m_childRows.at(i)->getCountDecendentsRecursive();
+ }
+ }
+
+ if (!childRows.contains(child))
+ childRows.insert(index, child);
+
+ child->m_parentRow = this;
+ child->updateDepthRecursive();
+ if (!child->isProperty()) {
+ m_rowTimeline->updateChildrenMinStartXRecursive(this);
+ m_rowTimeline->updateChildrenMaxEndXRecursive(this);
+ }
+
+ // update the layout
+ child->addToLayout(child->m_indexInLayout);
+
+ // update indices
+ updateIndexInLayout = std::min(updateIndexInLayout, child->m_indexInLayout);
+ updateIndices(true, child->m_index + 1, updateIndexInLayout, child->isProperty());
+ updateArrowVisibility();
+}
+
+int RowTree::addToLayout(int indexInLayout)
+{
+ m_scene->layoutTree()->insertItem(indexInLayout, this);
+ m_scene->layoutTimeline()->insertItem(indexInLayout, rowTimeline());
+
+ indexInLayout++;
+
+ for (auto p : qAsConst(m_childProps))
+ indexInLayout = p->addToLayout(indexInLayout);
+
+ for (auto c : qAsConst(m_childRows))
+ indexInLayout = c->addToLayout(indexInLayout);
+
+ return indexInLayout;
+}
+
+RowTree *RowTree::getChildAt(int index) const
+{
+ if (index < 0 || index > m_childRows.count() - 1)
+ return nullptr;
+
+ return m_childRows.at(index);
+}
+
+// this does not destroy the row, just remove it from the layout and parenting hierarchy
+void RowTree::removeChild(RowTree *child)
+{
+ if (m_childProps.contains(child) || m_childRows.contains(child)) { // child exists
+ removeChildFromLayout(child);
+
+ // detach from parent
+ if (child->isProperty())
+ m_childProps.removeAll(child);
+ else
+ m_childRows.removeAll(child);
+
+ child->m_depth = -1;
+ child->m_parentRow = nullptr;
+
+ updateIndices(false, child->m_index, child->m_indexInLayout, child->isProperty());
+ updateArrowVisibility();
+ }
+}
+
+int RowTree::removeChildFromLayout(RowTree *child) const
+{
+ int numRemoved = 0;
+ int deleteIndex = child->m_indexInLayout;
+ for (;;) {
+ RowTree *row_i = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(deleteIndex)
+ ->graphicsItem());
+ if (row_i->depth() <= child->depth() && numRemoved > 0)
+ break;
+
+ m_scene->layoutTree()->removeItem(row_i);
+ m_scene->layoutTimeline()->removeItem(row_i->rowTimeline());
+ numRemoved++;
+
+ if (m_scene->layoutTree()->count() == deleteIndex) // reached end of the list
+ break;
+ }
+
+ return numRemoved;
+}
+
+bool RowTree::draggable() const
+{
+ return !m_locked && !isProperty()
+ && m_objectType & ~(OBJTYPE_IMAGE | OBJTYPE_SCENE | OBJTYPE_IS_MATERIAL);
+}
+
+void RowTree::updateDepthRecursive()
+{
+ if (m_parentRow) {
+ m_depth = m_parentRow->m_depth + 1;
+ updateLabelPosition();
+
+ for (auto p : qAsConst(m_childProps))
+ p->updateDepthRecursive();
+
+ for (auto r : qAsConst(m_childRows))
+ r->updateDepthRecursive();
+ }
+}
+
+// update this parent's children indices after a child row is inserted or removed
+void RowTree::updateIndices(bool isInsertion, int index, int indexInLayout, bool isProperty)
+{
+ // update index
+ if (isProperty && index < m_childProps.count()) {
+ for (int i = index; i < m_childProps.count(); i++)
+ m_childProps.at(i)->m_index += isInsertion ? 1 : -1;
+ } else if (!isProperty && index < m_childRows.count()) {
+ for (int i = index; i < m_childRows.count(); i++)
+ m_childRows.at(i)->m_index += isInsertion ? 1 : -1;
+ }
+
+ // update indexInLayout
+ for (int i = indexInLayout; i < m_scene->layoutTree()->count(); ++i) {
+ RowTree *row_i = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(i)->graphicsItem());
+ row_i->m_indexInLayout = i;
+ }
+}
+
+void RowTree::updateFromBinding()
+{
+ // update view (shy, visible, locked)
+ m_shy = m_binding->GetTimelineItem()->IsShy();
+ m_visible = m_binding->GetTimelineItem()->IsVisible();
+ updateLock(m_binding->GetTimelineItem()->IsLocked());
+ m_visibilityCtrld = m_binding->GetTimelineItem()->IsVisibilityControlled();
+
+ // Update label color
+ Qt3DSDMTimelineItemBinding *itemBinding =
+ static_cast<Qt3DSDMTimelineItemBinding *>(m_binding);
+ m_master = itemBinding->IsMaster();
+ m_labelItem.setMaster(m_master);
+ // Update timeline comments
+ m_rowTimeline->updateCommentItem();
+}
+
+void RowTree::updateLabel()
+{
+ if (m_binding)
+ m_labelItem.setLabel(m_binding->GetTimelineItem()->GetName().toQString());
+}
+
+void RowTree::setRowVisible(bool visible)
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ if (bridge->isMaterialContainer(instance()))
+ return;
+
+ if (visible) {
+ setMaximumHeight(TimelineConstants::ROW_H);
+ setOpacity(1.0);
+ setVisible(true);
+ m_rowTimeline->setMaximumHeight(TimelineConstants::ROW_H);
+ m_rowTimeline->setOpacity(1.0);
+ m_rowTimeline->setVisible(true);
+ } else {
+ setMaximumHeight(0.0);
+ setOpacity(0.0);
+ setVisible(false);
+ m_rowTimeline->setMaximumHeight(0.0);
+ m_rowTimeline->setOpacity(0.0);
+ m_rowTimeline->setVisible(false);
+ }
+}
+
+bool RowTree::hasPropertyChildren() const
+{
+ return !m_childProps.empty();
+}
+
+void RowTree::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ QPointF p = event->pos();
+ if (m_rectType.contains(p.x(), p.y()) && !m_locked)
+ if (m_binding)
+ m_binding->OpenAssociatedEditor();
+}
+
+// handle clicked control and return its type
+TreeControlType RowTree::getClickedControl(const QPointF &scenePos)
+{
+ QPointF p = mapFromScene(scenePos.x(), scenePos.y());
+ if (m_arrowVisible && m_rectArrow.contains(p.x(), p.y())) {
+ updateExpandStatus(m_expandState == ExpandState::Expanded ? ExpandState::Collapsed
+ : ExpandState::Expanded, false);
+ update();
+ return TreeControlType::Arrow;
+ }
+
+ if (hasActionButtons()) {
+ if (m_rectShy.contains(p.x(), p.y())) {
+ toggleShy();
+ return TreeControlType::Shy;
+ } else if (!m_onMasterSlide && m_rectVisible.contains(p.x(), p.y())) {
+ // Prevent toggling hide on master slide
+ toggleVisible();
+ return TreeControlType::Hide;
+ } else if (m_rectLocked.contains(p.x(), p.y())) {
+ toggleLocked();
+ return TreeControlType::Lock;
+ }
+ }
+
+ return TreeControlType::None;
+}
+
+void RowTree::updateExpandStatus(ExpandState state, bool animate, bool forceChildUpdate)
+{
+ const bool changed = m_expandState != state;
+ if (!forceChildUpdate && !changed)
+ return;
+
+ m_expandState = state;
+
+ if (m_scene->widgetTimeline()->isFullReconstructPending())
+ return;
+
+ // Store the expanded state of items so we can restore it on slide change
+ if (changed && m_binding)
+ m_scene->expandMap().insert(instance(), m_expandState);
+
+ if (animate)
+ animateExpand(m_expandState);
+
+ // updateFilter updates the row visibility. It must be called before children are handled
+ // to ensure parent visibility is up to date.
+ if (changed)
+ updateFilter();
+
+ if (!m_childRows.empty()) {
+ for (auto child : qAsConst(m_childRows)) {
+ if (state == ExpandState::Expanded) {
+ if (child->m_expandState == ExpandState::HiddenExpanded)
+ child->updateExpandStatus(ExpandState::Expanded);
+ else if (child->m_expandState == ExpandState::HiddenCollapsed)
+ child->updateExpandStatus(ExpandState::Collapsed);
+ } else {
+ if (child->m_expandState == ExpandState::Expanded)
+ child->updateExpandStatus(ExpandState::HiddenExpanded);
+ else if (child->m_expandState == ExpandState::Collapsed)
+ child->updateExpandStatus(ExpandState::HiddenCollapsed);
+ }
+ }
+ }
+
+ if (!m_childProps.empty()) {
+ for (auto child : qAsConst(m_childProps)) {
+ // Properties can never be collapsed
+ if (state == ExpandState::Expanded)
+ child->updateExpandStatus(ExpandState::Expanded);
+ else
+ child->updateExpandStatus(ExpandState::HiddenExpanded);
+ }
+ }
+}
+
+void RowTree::updateLockRecursive(bool state)
+{
+ updateLock(state);
+ if (!m_childRows.empty()) {
+ for (auto child : qAsConst(m_childRows))
+ child->updateLockRecursive(m_locked);
+ }
+}
+
+void RowTree::updateLock(bool state)
+{
+ m_locked = state;
+
+ m_labelItem.setLocked(m_locked);
+ update();
+ if (!m_childProps.empty()) {
+ for (auto child : qAsConst(m_childProps))
+ child->updateLock(m_locked);
+ }
+ if (m_locked)
+ m_scene->keyframeManager()->deselectRowKeyframes(this);
+}
+
+void RowTree::updateSubpresentations(int updateParentsOnlyVal)
+{
+ if (updateParentsOnlyVal != 0) {
+ int n = m_numDescendantSubpresentations;
+ if (m_hasSubpresentation)
+ n++;
+ if (n > 0) {
+ RowTree *parentRow = m_parentRow;
+ while (parentRow) {
+ parentRow->m_numDescendantSubpresentations += n * updateParentsOnlyVal;
+ parentRow->update();
+ parentRow = parentRow->m_parentRow;
+ }
+ }
+ } else {
+ auto binding = static_cast<Qt3DSDMTimelineItemBinding *>(m_binding);
+ bool hasSubp = binding->hasSubpresentation();
+
+ if (m_hasSubpresentation != hasSubp) {
+ m_hasSubpresentation = hasSubp;
+ int n = hasSubp ? 1 : -1;
+ RowTree *parentRow = m_parentRow;
+ while (parentRow) {
+ parentRow->m_numDescendantSubpresentations += n;
+ parentRow->update();
+ parentRow = parentRow->m_parentRow;
+ }
+ }
+ }
+ update();
+}
+
+void RowTree::updateLabelPosition()
+{
+ int offset = 5 + m_depth * TimelineConstants::ROW_DEPTH_STEP + 30;
+ m_labelItem.setPos(offset, -1);
+}
+
+bool RowTree::expanded() const
+{
+ if (m_isProperty)
+ return false;
+ else
+ return m_expandState == ExpandState::Expanded;
+}
+
+bool RowTree::expandHidden() const
+{
+ return m_expandState == ExpandState::HiddenExpanded
+ || m_expandState == ExpandState::HiddenCollapsed;
+}
+
+bool RowTree::isDecendentOf(RowTree *row) const
+{
+ RowTree *parentRow = m_parentRow;
+
+ while (parentRow) {
+ if (parentRow == row)
+ return true;
+
+ parentRow = parentRow->parentRow();
+ }
+
+ return false;
+}
+
+void RowTree::setDnDHover(bool val)
+{
+ m_dndHover = val;
+ update();
+}
+
+void RowTree::setDnDState(DnDState state, DnDState onlyIfState, bool recursive)
+{
+ if (m_dndState == onlyIfState || onlyIfState == DnDState::Any) {
+ m_dndState = state;
+ update();
+
+ if (recursive) { // used by source rows to highlights all of their descendants
+ for (auto child : qAsConst(m_childProps))
+ child->setDnDState(state, onlyIfState, true);
+
+ for (auto child : qAsConst(m_childRows))
+ child->setDnDState(state, onlyIfState, true);
+ }
+ }
+}
+
+RowTree::DnDState RowTree::getDnDState() const
+{
+ return m_dndState;
+}
+
+void RowTree::setActionStates(ActionStates states)
+{
+ if (states != m_actionStates) {
+ m_actionStates = states;
+ update();
+ }
+}
+
+bool RowTree::isContainer() const
+{
+ return !m_isProperty && m_objectType & OBJTYPE_IS_CONTAINER;
+}
+
+bool RowTree::isProperty() const
+{
+ return m_isProperty;
+}
+
+RowTree *RowTree::getPropertyRow(const QString &type) const
+{
+ for (RowTree *prop : qAsConst(m_childProps)) {
+ if (prop->label() == type)
+ return prop;
+ }
+
+ return nullptr;
+}
+
+
+bool RowTree::isPropertyOrMaterial() const
+{
+ return m_isProperty || m_objectType & (OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE);
+}
+
+bool RowTree::isComponent() const
+{
+ return m_objectType == OBJTYPE_COMPONENT;
+}
+
+bool RowTree::isComponentRoot() const
+{
+ if (m_objectType == OBJTYPE_COMPONENT && m_binding)
+ return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->isRootComponent();
+
+ return false;
+}
+
+bool RowTree::isMaster() const
+{
+ return m_master;
+}
+
+bool RowTree::isDefaultMaterial() const
+{
+ if (m_binding)
+ return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->isDefaultMaterial();
+
+ return false;
+}
+
+bool RowTree::empty() const
+{
+ return m_childRows.empty() && m_childProps.empty();
+}
+
+bool RowTree::selected() const
+{
+ return m_state == Selected;
+}
+
+QList<RowTree *> RowTree::childRows() const
+{
+ return m_childRows;
+}
+
+QList<RowTree *> RowTree::childProps() const
+{
+ return m_childProps;
+}
+
+RowTimeline *RowTree::rowTimeline() const
+{
+ return m_rowTimeline;
+}
+
+QString RowTree::label() const
+{
+ return m_label;
+}
+
+void RowTree::toggleShy()
+{
+ if (hasActionButtons()) {
+ m_shy = !m_shy;
+ update();
+ m_binding->GetTimelineItem()->SetShy(m_shy);
+ }
+}
+
+void RowTree::toggleVisible()
+{
+ if (hasActionButtons()) {
+ m_visible = !m_visible;
+ update();
+ m_binding->GetTimelineItem()->SetVisible(m_visible);
+ }
+}
+
+void RowTree::toggleLocked()
+{
+ if (hasActionButtons()) {
+ updateLockRecursive(!m_locked);
+ m_binding->GetTimelineItem()->SetLocked(m_locked);
+ if (m_locked && selected())
+ m_scene->rowManager()->clearSelection();
+ }
+}
+
+bool RowTree::shy() const
+{
+ return m_shy;
+}
+
+bool RowTree::visible() const
+{
+ return m_visible;
+}
+
+bool RowTree::locked() const
+{
+ return m_locked;
+}
+
+// Returns true for items with shy/visible/lock buttons
+bool RowTree::hasActionButtons() const
+{
+ return !m_isProperty && m_indexInLayout != 1
+ && m_objectType & ~(OBJTYPE_SCENE | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE);
+}
+
+bool RowTree::hasComponentAncestor() const
+{
+ RowTree *parentRow = m_parentRow;
+ while (parentRow) {
+ if (parentRow->objectType() == OBJTYPE_COMPONENT)
+ return true;
+ parentRow = parentRow->parentRow();
+ }
+ return false;
+}
+
+// Returns true for items with duration bar
+bool RowTree::hasDurationBar() const
+{
+ return hasActionButtons(); // Same at least now
+}
+
+bool RowTree::propertyExpanded() const
+{
+ return m_isPropertyExpanded;
+}
+
+void RowTree::togglePropertyExpanded()
+{
+ setPropertyExpanded(!m_isPropertyExpanded);
+}
+
+void RowTree::setPropertyExpanded(bool expand)
+{
+ m_isPropertyExpanded = expand;
+ if (m_isPropertyExpanded)
+ animateExpand(ExpandState::Expanded);
+ else
+ animateExpand(ExpandState::Collapsed);
+}
+
+void RowTree::showDataInputSelector(const QString &propertyname, const QPoint &pos)
+{
+ auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+
+ // Set the datainput to control property in referenced object if this
+ // is a referenced material.
+ auto refInstance = bridge->getMaterialReference(instance());
+
+ m_scene->handleShowDISelector(propertyname, refInstance.Valid() ? refInstance : instance(),
+ pos);
+}
+
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h
new file mode 100644
index 00000000..b028e94d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h
@@ -0,0 +1,228 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTREE_H
+#define ROWTREE_H
+
+#include "InteractiveTimelineItem.h"
+#include "TimelineConstants.h"
+#include "RowTypes.h"
+#include "StudioObjectTypes.h"
+#include "RowTreeLabelItem.h"
+#include "Qt3DSDMHandles.h"
+
+#include <QtCore/qpropertyanimation.h>
+#include <QtCore/qparallelanimationgroup.h>
+
+class RowTimeline;
+class Ruler;
+class ITimelineItemBinding;
+class TimelineGraphicsScene;
+class ITimelineItemProperty;
+
+class RowTree : public InteractiveTimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum class ExpandState {
+ Unknown,
+ Collapsed,
+ Expanded,
+ HiddenCollapsed,
+ HiddenExpanded
+ };
+
+ enum class DnDState {
+ None,
+ Source, // the row being dragged while DnD-ing
+ Parent, // parent of the insertion point
+ SP_TARGET, // drop target for a subpresentation (layer, material or image rows)
+ Any // accept any state (default value in setDnDState() method)
+ };
+
+ enum class ActionState {
+ None = 0,
+ Action = 1,
+ ChildAction = 2,
+ ComponentAction = 4,
+ MasterAction = 8,
+ MasterChildAction = 16,
+ MasterComponentAction = 32
+ };
+ Q_DECLARE_FLAGS(ActionStates, ActionState)
+
+ explicit RowTree(TimelineGraphicsScene *timelineScene,
+ EStudioObjectType objectType = OBJTYPE_UNKNOWN, const QString &label = {});
+ // property row constructor
+ explicit RowTree(TimelineGraphicsScene *timelineScene, const QString &propType);
+ ~RowTree();
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ void setState(State state) override;
+ void setTimelineRow(RowTimeline *rowTimeline);
+ void setParentRow(RowTree *parent);
+ void addChild(RowTree *child);
+ void addChildAt(RowTree *child, int index);
+ void removeChild(RowTree *child);
+ void setDnDState(DnDState state, DnDState onlyIfState = DnDState::Any, bool recursive = false);
+ void setActionStates(ActionStates states);
+ void setTreeWidth(double w);
+ void setBinding(ITimelineItemBinding *binding);
+ void setPropBinding(ITimelineItemProperty *binding); // for property rows
+ void selectLabel();
+ void togglePropertyExpanded();
+ void setPropertyExpanded(bool expand);
+ void showDataInputSelector(const QString &propertyname, const QPoint &pos);
+ ITimelineItemProperty *propBinding();
+ TreeControlType getClickedControl(const QPointF &scenePos);
+ bool shy() const;
+ bool visible() const;
+ bool locked() const;
+ bool expanded() const;
+ bool expandHidden() const;
+ ExpandState expandState() const { return m_expandState; }
+ bool isDecendentOf(RowTree *row) const;
+ bool isContainer() const;
+ bool isProperty() const;
+ bool isPropertyOrMaterial() const;
+ bool isComponent() const;
+ bool isComponentRoot() const;
+ bool isMaster() const;
+ bool isDefaultMaterial() const;
+ bool hasPropertyChildren() const;
+ bool empty() const; // has zero child rows (and zero properties)
+ bool selected() const;
+ bool draggable() const;
+ bool hasDurationBar() const;
+ bool propertyExpanded() const;
+ int depth() const;
+ int type() const override;
+ int index() const;
+ int indexInLayout() const;
+ int treeWidth() const;
+ EStudioObjectType objectType() const;
+ QString propertyType() const;
+ RowTree *getChildAt(int index) const;
+ RowTree *parentRow() const;
+ RowTree *getPropertyRow(const QString &type) const;
+ QList<RowTree *> childRows() const;
+ QList<RowTree *> childProps() const;
+ RowTimeline *rowTimeline() const;
+ QString label() const;
+ void toggleShy();
+ void toggleVisible();
+ void toggleLocked();
+ void updateFromBinding();
+ void updateLabel();
+ void setRowVisible(bool visible);
+ void setDnDHover(bool val);
+ void updateVariants(const QStringList &groups);
+ DnDState getDnDState() const;
+
+ ITimelineItemBinding *getBinding() const;
+ void updateExpandStatus(ExpandState state, bool animate = true, bool forceChildUpdate = false);
+ void updateArrowVisibility();
+ void updateFilter();
+ void updateLock(bool state);
+ void updateSubpresentations(int updateParentsOnlyVal = 0);
+ int clipX() const;
+ qt3dsdm::Qt3DSDMInstanceHandle instance() const;
+
+protected:
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+
+private:
+ void initialize();
+ void initializeAnimations();
+ void animateExpand(ExpandState state);
+ void updateDepthRecursive();
+ void updateLockRecursive(bool state);
+ void updateLabelPosition();
+ void updateIndices(bool isInsertion, int startIndex, int startIndexInLayout, bool isProperty);
+ bool hasActionButtons() const;
+ bool hasComponentAncestor() const;
+ bool isInVariantsFilter() const;
+ int removeChildFromLayout(RowTree *child) const;
+ int getCountDecendentsRecursive() const;
+ int addToLayout(int indexInLayout);
+ int getLastChildIndex(bool isProperty) const;
+
+ RowTree *m_parentRow = nullptr;
+ RowTimeline *m_rowTimeline = nullptr;
+ int m_depth = 1;
+ int m_index = 0;
+ int m_indexInLayout = 1;
+ bool m_shy = false;
+ bool m_visible = true;
+ bool m_locked = false;
+ bool m_isProperty = false;
+ bool m_isPropertyExpanded = false;
+ bool m_master = false;
+ bool m_filtered = false;
+ bool m_arrowVisible = false;
+ bool m_dndHover = false;
+ bool m_visibilityCtrld = false;
+ bool m_onMasterSlide = false;
+ DnDState m_dndState = DnDState::None;
+ ActionStates m_actionStates = ActionState::None;
+ bool m_hasSubpresentation = false;
+ int m_numDescendantSubpresentations = 0;
+ ExpandState m_expandState = ExpandState::HiddenCollapsed;
+ TimelineGraphicsScene *m_scene;
+ RowTreeLabelItem m_labelItem;
+ EStudioObjectType m_objectType = OBJTYPE_UNKNOWN;
+ QString m_propertyType; // for property rows
+ QString m_label;
+ QList<RowTree *> m_childRows;
+ QList<RowTree *> m_childProps;
+ QStringList m_variantsGroups;
+ ITimelineItemBinding *m_binding = nullptr;
+ ITimelineItemProperty *m_PropBinding = nullptr; // for property rows
+
+ QRect m_rectArrow;
+ QRect m_rectShy;
+ QRect m_rectVisible;
+ QRect m_rectLocked;
+ QRect m_rectType;
+
+ QParallelAnimationGroup m_expandAnimation;
+ QPropertyAnimation *m_expandHeightAnimation;
+ QPropertyAnimation *m_expandTimelineHeightAnimation;
+ QPropertyAnimation *m_expandOpacityAnimation;
+ QPropertyAnimation *m_expandTimelineOpacityAnimation;
+
+ friend class RowTimeline;
+ friend class RowTimelinePropertyGraph;
+ friend class RowTimelineContextMenu;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(RowTree::ActionStates)
+
+#endif // ROWTREE_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp
new file mode 100644
index 00000000..ae5c0bbb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp
@@ -0,0 +1,441 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTreeContextMenu.h"
+#include "RowTree.h"
+#include "StudioClipboard.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "Core.h"
+#include "Bindings/ITimelineItemBinding.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "ChooseImagePropertyDlg.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "qcursor.h"
+
+RowTreeContextMenu::RowTreeContextMenu(RowTree *inRowTree, QWidget *parent)
+ : QMenu(parent)
+ , m_RowTree(inRowTree)
+ , m_TimelineItemBinding(inRowTree->getBinding())
+{
+ initialize();
+}
+
+RowTreeContextMenu::~RowTreeContextMenu()
+{
+}
+
+void RowTreeContextMenu::initialize()
+{
+ CDoc &doc(*g_StudioApp.GetCore()->GetDoc());
+ qt3dsdm::Qt3DSDMInstanceHandle instance = m_RowTree->instance();
+
+ // add sub-presentations submenu
+ if (m_RowTree->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)) {
+ m_subpMenu = addMenu(tr("Set sub-presentation"));
+ connect(m_subpMenu, &QMenu::triggered, this, &RowTreeContextMenu::addSubPresentation);
+
+ m_subpMenu->addAction(tr("[None]"));
+ for (auto sp : qAsConst(g_StudioApp.m_subpresentations))
+ m_subpMenu->addAction(sp.m_id);
+
+ addSeparator();
+ }
+
+ // add datainput controller submenu
+ if (m_RowTree->objectType() & ~(OBJTYPE_GUIDE | OBJTYPE_EFFECT | OBJTYPE_ALIAS | OBJTYPE_SCENE)
+ && !m_RowTree->isDefaultMaterial()) {
+
+ m_diMenu = addMenu(tr("Set datainput controller"));
+ connect(m_diMenu, &QMenu::triggered, this, &RowTreeContextMenu::addDiController);
+
+ QVector<qt3dsdm::Qt3DSDMPropertyHandle> propList;
+
+ // If this is a referenced material instance, we need to get the property list from
+ // the referenced source, and set datainput control to point to the property
+ // in the referenced source.
+ auto refInstance = doc.GetStudioSystem()->GetClientDataModelBridge()
+ ->getMaterialReference(instance);
+ propList = doc.GetStudioSystem()->GetPropertySystem()
+ ->GetControllableProperties(refInstance ? refInstance : instance);
+
+ QMap<int, QAction *> sections;
+ for (const auto &prop : propList) {
+ QAction *action
+ = new QAction(QString::fromStdWString(doc.GetPropertySystem()
+ ->GetFormalName(
+ refInstance ? refInstance : instance,
+ prop).wide_str()));
+ action->setData(QString::fromStdWString(
+ doc.GetPropertySystem()
+ ->GetName(prop).wide_str()));
+
+ auto metadata = doc.GetStudioSystem()->GetActionMetaData()->GetMetaDataPropertyInfo(
+ doc.GetStudioSystem()->GetActionMetaData()->GetMetaDataProperty(
+ refInstance ? refInstance : instance, prop));
+
+ if (sections.contains(metadata->m_CompleteType) ) {
+ m_diMenu->insertAction(sections[metadata->m_CompleteType], action);
+ } else {
+ // Create a QAction for a section so that we can insert properties above it
+ // to maintain category groupings. Sections are shown as separators in Studio
+ // style i.e. enum text is not shown.
+ QAction *section = m_diMenu->addSection(QString(metadata->m_CompleteType));
+ sections.insert(metadata->m_CompleteType, section);
+ m_diMenu->insertAction(section, action);
+ }
+ }
+ }
+ m_renameAction = new QAction(tr("Rename Object"), this);
+ connect(m_renameAction, &QAction::triggered, this, &RowTreeContextMenu::renameObject);
+ addAction(m_renameAction);
+
+ m_duplicateAction = new QAction(tr("Duplicate Object"), this);
+ m_duplicateAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_D));
+ m_duplicateAction->setShortcutVisibleInContextMenu(true);
+ connect(m_duplicateAction, &QAction::triggered,
+ this, &RowTreeContextMenu::duplicateObject);
+ addAction(m_duplicateAction);
+
+ m_deleteAction = new QAction(tr("Delete Object"), this);
+ m_deleteAction->setShortcut(Qt::Key_Delete);
+ m_deleteAction->setShortcutVisibleInContextMenu(true);
+ connect(m_deleteAction, &QAction::triggered, this, &RowTreeContextMenu::deleteObject);
+ addAction(m_deleteAction);
+
+ m_groupAction = new QAction(tr("Group Objects"), this);
+ m_groupAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_G));
+ m_groupAction->setShortcutVisibleInContextMenu(true);
+ connect(m_groupAction, &QAction::triggered, this, &RowTreeContextMenu::groupObjects);
+ addAction(m_groupAction);
+
+ addSeparator();
+
+ m_addLayerAction = new QAction(tr("Add Layer"), this);
+ m_addLayerAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_L));
+ m_addLayerAction->setShortcutVisibleInContextMenu(true);
+ connect(m_addLayerAction, &QAction::triggered, this, &RowTreeContextMenu::addLayer);
+ addAction(m_addLayerAction);
+
+ addSeparator();
+
+ m_copyAction = new QAction(tr("Copy"), this);
+ m_copyAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_C));
+ m_copyAction->setShortcutVisibleInContextMenu(true);
+ connect(m_copyAction, &QAction::triggered, this, &RowTreeContextMenu::copyObject);
+ addAction(m_copyAction);
+
+ m_pasteAction = new QAction(tr("Paste"), this);
+ m_pasteAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_V));
+ m_pasteAction->setShortcutVisibleInContextMenu(true);
+ connect(m_pasteAction, &QAction::triggered, this, &RowTreeContextMenu::pasteObject);
+ addAction(m_pasteAction);
+
+ m_cutAction = new QAction(tr("Cut"), this);
+ m_cutAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_X));
+ m_cutAction->setShortcutVisibleInContextMenu(true);
+ connect(m_cutAction, &QAction::triggered, this, &RowTreeContextMenu::cutObject);
+ addAction(m_cutAction);
+ addSeparator();
+
+ m_makeAction = new QAction(tr("Make Component"), this);
+ m_makeAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_G));
+ m_makeAction->setShortcutVisibleInContextMenu(true);
+ connect(m_makeAction, &QAction::triggered, this, &RowTreeContextMenu::makeComponent);
+ addAction(m_makeAction);
+
+ if (canInspectComponent()) {
+ m_inspectAction = new QAction(tr("Edit Component"), this);
+ m_inspectAction->setShortcut(QKeySequence(
+ Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_G));
+ m_inspectAction->setShortcutVisibleInContextMenu(true);
+ connect(m_inspectAction, &QAction::triggered, this, &RowTreeContextMenu::inspectComponent);
+ addAction(m_inspectAction);
+ }
+
+ if (canMakeAnimatable()) {
+ m_animAction = new QAction(tr("Make Animatable"), this);
+ connect(m_animAction, &QAction::triggered, this, &RowTreeContextMenu::makeAnimatable);
+ addAction(m_animAction);
+ }
+
+ addSeparator();
+
+ m_copyPathAction = new QAction(tr("Copy Object Path"), this);
+ m_copyPathAction->setShortcut(QKeySequence(
+ Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_C));
+ m_copyPathAction->setShortcutVisibleInContextMenu(true);
+ connect(m_copyPathAction, &QAction::triggered, this, &RowTreeContextMenu::copyObjectPath);
+ addAction(m_copyPathAction);
+}
+
+void RowTreeContextMenu::showEvent(QShowEvent *event)
+{
+ if (m_subpMenu)
+ m_subpMenu->setEnabled(canAddSubPresentation());
+ if (m_diMenu)
+ m_diMenu->setEnabled(true);
+ m_renameAction->setEnabled(canRenameObject());
+ m_duplicateAction->setEnabled(canDuplicateObject());
+ m_deleteAction->setEnabled(canDeleteObject());
+ m_canGroupObjects = canGroupObjects();
+ m_canUngroupObjects = canUngroupObjects();
+ m_groupAction->setEnabled(m_canUngroupObjects || m_canGroupObjects);
+ if (m_canUngroupObjects)
+ m_groupAction->setText(tr("Ungroup Objects"));
+
+ m_cutAction->setEnabled(canCutObject());
+ m_copyAction->setEnabled(canCopyObject());
+ m_pasteAction->setEnabled(canPasteObject());
+
+ m_makeAction->setEnabled(canMakeComponent());
+
+ m_addLayerAction->setEnabled(canAddLayer());
+
+ QMenu::showEvent(event);
+}
+
+bool RowTreeContextMenu::canAddSubPresentation() const
+{
+ return !g_StudioApp.m_subpresentations.empty();
+}
+
+bool RowTreeContextMenu::canRenameObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Rename);
+}
+
+void RowTreeContextMenu::addSubPresentation(QAction *action)
+{
+ CDoc &doc(*g_StudioApp.GetCore()->GetDoc());
+ auto &bridge(*doc.GetStudioSystem()->GetClientDataModelBridge());
+
+ qt3dsdm::Qt3DSDMInstanceHandle instance = m_RowTree->instance();
+ Q3DStudio::CString presentationId;
+ if (action->text() != tr("[None]"))
+ presentationId = Q3DStudio::CString::fromQString(action->text());
+
+ if (m_RowTree->objectType() == OBJTYPE_LAYER) {
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = bridge.GetSourcePathProperty();
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set layer sub-presentation"))
+ ->SetInstancePropertyValueAsRenderable(instance, propHandle, presentationId);
+ } else if (m_RowTree->objectType() & OBJTYPE_IS_MATERIAL) {
+ // if this is a ref material, update the material it references
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance = bridge.getMaterialReference(instance);
+
+ ChooseImagePropertyDlg dlg(refInstance ? refInstance : instance, refInstance != 0);
+ if (dlg.exec() == QDialog::Accepted) {
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = dlg.getSelectedPropertyHandle();
+ if (dlg.detachMaterial()) {
+ Q3DStudio::ScopedDocumentEditor editor(Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc,
+ tr("Set material sub-presentation")));
+ editor->BeginAggregateOperation();
+ editor->SetMaterialType(instance, QStringLiteral("Standard Material"));
+ editor->setInstanceImagePropertyValue(instance, propHandle, presentationId);
+ editor->EndAggregateOperation();
+ } else {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set material sub-presentation"))
+ ->setInstanceImagePropertyValue(refInstance ? refInstance : instance, propHandle,
+ presentationId);
+ }
+ }
+ } else if (m_RowTree->objectType() == OBJTYPE_IMAGE) {
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = bridge.getSubpresentationProperty();
+
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set image sub-presentation"))
+ ->SetInstancePropertyValueAsRenderable(instance, propHandle, presentationId);
+ }
+}
+
+void RowTreeContextMenu::addDiController(QAction *action)
+{
+ m_RowTree->showDataInputSelector(action->data().toString(), QCursor::pos());
+}
+
+void RowTreeContextMenu::renameObject()
+{
+ m_RowTree->selectLabel();
+}
+
+bool RowTreeContextMenu::canDuplicateObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Duplicate);
+}
+
+void RowTreeContextMenu::duplicateObject()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_Duplicate);
+}
+
+bool RowTreeContextMenu::canDeleteObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Delete);
+}
+
+bool RowTreeContextMenu::canGroupObjects() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Group);
+}
+
+bool RowTreeContextMenu::canUngroupObjects() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Ungroup);
+}
+
+void RowTreeContextMenu::deleteObject()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_Delete);
+}
+
+void RowTreeContextMenu::groupObjects()
+{
+ if (m_canUngroupObjects)
+ m_TimelineItemBinding->PerformTransaction(ITimelineItemBinding::EUserTransaction_Ungroup);
+ else if (m_canGroupObjects)
+ m_TimelineItemBinding->PerformTransaction(ITimelineItemBinding::EUserTransaction_Group);
+}
+
+bool RowTreeContextMenu::canInspectComponent() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_EditComponent);
+}
+
+/**
+ * Inspect the State (Component).
+ * This will make the component the top level item of the timelineview.
+ */
+void RowTreeContextMenu::inspectComponent()
+{
+ m_TimelineItemBinding->OpenAssociatedEditor();
+}
+
+/**
+ * Checks to see if the object can be wrapped in a component.
+ * @return true if the object is allowed to be wrapped in a component.
+ */
+bool RowTreeContextMenu::canMakeComponent() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_MakeComponent);
+}
+
+/**
+ * Wraps the specified asset hierarchy under a component.
+ */
+void RowTreeContextMenu::makeComponent()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_MakeComponent);
+}
+
+/**
+ * Returns true if the object is a referenced material and thus can be made animatable
+ */
+bool RowTreeContextMenu::canMakeAnimatable() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_MakeAnimatable);
+}
+
+/**
+ * Makes a referenced material animatable aka changing it to a standard or a custom material
+ * with the same properties
+ */
+void RowTreeContextMenu::makeAnimatable()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_MakeAnimatable);
+}
+
+/**
+ * Get the full Scripting path of the object and copy it to the clipboard.
+ * This will figure out the proper way to address the object via scripting
+ * and put that path into the clipboard.
+ */
+void RowTreeContextMenu::copyObjectPath()
+{
+ CStudioClipboard::CopyTextToClipboard(
+ m_TimelineItemBinding->GetObjectPath().toQString());
+}
+
+bool RowTreeContextMenu::canCopyObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Copy);
+}
+
+void RowTreeContextMenu::copyObject()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_Copy);
+}
+
+bool RowTreeContextMenu::canCutObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Cut);
+}
+
+void RowTreeContextMenu::cutObject()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_Cut);
+}
+
+bool RowTreeContextMenu::canAddLayer() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_AddLayer);
+}
+void RowTreeContextMenu::addLayer()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_AddLayer);
+}
+
+bool RowTreeContextMenu::canPasteObject() const
+{
+ return m_TimelineItemBinding->IsValidTransaction(
+ ITimelineItemBinding::EUserTransaction_Paste);
+}
+
+void RowTreeContextMenu::pasteObject()
+{
+ m_TimelineItemBinding->PerformTransaction(
+ ITimelineItemBinding::EUserTransaction_Paste);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h
new file mode 100644
index 00000000..0ef87552
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTREECONTEXTMENU_H
+#define ROWTREECONTEXTMENU_H
+
+#include <QtWidgets/qmenu.h>
+#include <QtWidgets/qaction.h>
+
+class ITimelineItemBinding;
+class RowTree;
+
+class RowTreeContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ RowTreeContextMenu(RowTree *inRowTree,
+ QWidget *parent = nullptr);
+ virtual ~RowTreeContextMenu();
+
+protected:
+ void showEvent(QShowEvent *event) override;
+
+private Q_SLOTS:
+ void addSubPresentation(QAction *action);
+ void addDiController(QAction *action);
+ void renameObject();
+ void duplicateObject();
+ void deleteObject();
+ void groupObjects();
+ void inspectComponent();
+ void makeComponent();
+ void makeAnimatable();
+ void copyObject();
+ void copyObjectPath();
+ void pasteObject();
+ void cutObject();
+ void addLayer();
+
+private:
+ void initialize();
+
+ bool canAddSubPresentation() const;
+ bool canRenameObject() const;
+ bool canDuplicateObject() const;
+ bool canDeleteObject() const;
+ bool canGroupObjects() const;
+ bool canUngroupObjects() const;
+ bool canInspectComponent() const;
+ bool canMakeComponent() const;
+ bool canMakeAnimatable() const;
+ bool canCopyObject() const;
+ bool canPasteObject() const;
+ bool canCutObject() const;
+ bool canAddLayer() const;
+
+ RowTree *m_RowTree;
+ ITimelineItemBinding *m_TimelineItemBinding;
+ QMenu *m_subpMenu = nullptr; // sub-presentation submenu
+ QMenu *m_diMenu = nullptr; // datainput submenu
+ QAction *m_renameAction = nullptr;
+ QAction *m_duplicateAction = nullptr;
+ QAction *m_deleteAction = nullptr;
+ QAction *m_groupAction = nullptr;
+ QAction *m_addLayerAction = nullptr;
+ QAction *m_inspectAction = nullptr;
+ QAction *m_makeAction = nullptr;
+ QAction *m_animAction = nullptr;
+ QAction *m_copyPathAction = nullptr;
+ QAction *m_cutAction = nullptr;
+ QAction *m_copyAction = nullptr;
+ QAction *m_pasteAction = nullptr;
+ bool m_canGroupObjects = false;
+ bool m_canUngroupObjects = false;
+};
+#endif // ROWTREECONTEXTMENU_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp
new file mode 100644
index 00000000..21b57aaa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RowTreeLabelItem.h"
+#include "TimelineConstants.h"
+#include "TimelineItem.h"
+#include "RowTree.h"
+#include "StudioPreferences.h"
+
+#include <QtWidgets/qstyleoption.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qtextcursor.h>
+
+RowTreeLabelItem::RowTreeLabelItem(QGraphicsItem *parent)
+ : QGraphicsTextItem(parent)
+ , m_locked(false)
+ , m_master(false)
+ , m_acceptOnFocusOut(true)
+{
+ setTextInteractionFlags(Qt::TextEditorInteraction);
+ setEnabled(false);
+ updateLabelColor();
+}
+
+QString RowTreeLabelItem::label() const
+{
+ return m_label;
+}
+
+void RowTreeLabelItem::setLabel(const QString &label)
+{
+ setPlainText(label);
+ if (m_label != label) {
+ m_label = label;
+ emit labelChanged(m_label);
+ }
+}
+
+void RowTreeLabelItem::setMaster(bool isMaster) {
+ if (m_master != isMaster) {
+ m_master = isMaster;
+ updateLabelColor();
+ }
+}
+
+void RowTreeLabelItem::setLocked(bool isLocked) {
+ if (m_locked != isLocked) {
+ m_locked = isLocked;
+ updateLabelColor();
+ }
+}
+
+RowTree *RowTreeLabelItem::parentRow() const
+{
+ return m_rowTree;
+}
+
+void RowTreeLabelItem::setParentRow(RowTree *row)
+{
+ m_rowTree = row;
+}
+
+int RowTreeLabelItem::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TimelineItem::TypeRowTreeLabelItem;
+}
+
+void RowTreeLabelItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ if (!m_rowTree->y()) // prevents flickering when the row is just inserted to the layout
+ return;
+
+ // Remove the HasFocus style state, to prevent the dotted line from being drawn.
+ QStyleOptionGraphicsItem *style = const_cast<QStyleOptionGraphicsItem *>(option);
+ style->state &= ~QStyle::State_HasFocus;
+
+ QGraphicsTextItem::paint(painter, option, widget);
+}
+
+void RowTreeLabelItem::focusOutEvent(QFocusEvent *event)
+{
+ if (m_acceptOnFocusOut)
+ validateLabel();
+ else
+ setPlainText(m_label);
+
+ // Remove possible selection and make disabled again
+ QTextCursor cursor = textCursor();
+ cursor.clearSelection();
+ setTextCursor(cursor);
+ setEnabled(false);
+ QGraphicsTextItem::focusOutEvent(event);
+ // Next time default to accepting
+ m_acceptOnFocusOut = true;
+}
+
+void RowTreeLabelItem::keyPressEvent(QKeyEvent *event)
+{
+ int key = event->key();
+ if (key == Qt::Key_Return || key == Qt::Key_Enter) {
+ m_acceptOnFocusOut = true;
+ clearFocus();
+ event->accept();
+ return;
+ } else if (key == Qt::Key_Escape) {
+ m_acceptOnFocusOut = false;
+ clearFocus();
+ event->accept();
+ return;
+ }
+
+ QGraphicsTextItem::keyPressEvent(event);
+}
+
+QRectF RowTreeLabelItem::boundingRect() const
+{
+ if (!m_rowTree)
+ return QGraphicsTextItem::boundingRect();
+
+ double w = m_rowTree->clipX() - x();
+ // Bounding rect width must be at least 1
+ w = std::max(w, 1.0);
+ return QRectF(0, 0, w, TimelineConstants::ROW_H);
+}
+
+void RowTreeLabelItem::validateLabel()
+{
+ QString text = toPlainText().trimmed();
+ if (text.isEmpty()) {
+ // Inform label was empty and return previous label
+ emit labelChanged("");
+ setLabel(m_label);
+ return;
+ }
+
+ setLabel(text);
+}
+
+void RowTreeLabelItem::updateLabelColor()
+{
+ if (m_locked)
+ setDefaultTextColor(CStudioPreferences::GetDisabledTextColor());
+ else if (m_master)
+ setDefaultTextColor(CStudioPreferences::GetMasterColor());
+ else
+ setDefaultTextColor(CStudioPreferences::GetNormalColor());
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h
new file mode 100644
index 00000000..edffbef5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ROWTREELABELITEM_H
+#define ROWTREELABELITEM_H
+
+#include "StudioObjectTypes.h"
+#include <QtWidgets/qgraphicsitem.h>
+#include <QtCore/qstring.h>
+#include <QtWidgets/qgraphicssceneevent.h>
+#include <QtGui/qevent.h>
+
+class RowTree;
+
+class RowTreeLabelItem : public QGraphicsTextItem
+{
+ Q_OBJECT
+public:
+ explicit RowTreeLabelItem(QGraphicsItem *parent = nullptr);
+
+ QString label() const;
+ void setLabel(const QString &label);
+ void setLocked(bool isLocked);
+ void setMaster(bool isMaster);
+ RowTree *parentRow() const;
+ void setParentRow(RowTree *row);
+ int type() const;
+ bool isLocked() const { return m_locked; }
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ QRectF boundingRect() const override;
+
+signals:
+ void labelChanged(const QString label);
+
+private:
+ void validateLabel();
+ void updateLabelColor();
+
+ RowTree *m_rowTree = nullptr;
+ QString m_label;
+ bool m_locked;
+ bool m_master;
+ bool m_acceptOnFocusOut;
+
+};
+
+#endif // ROWTREELABELITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp
new file mode 100644
index 00000000..62c6de01
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Ruler.h"
+#include "TimelineConstants.h"
+#include "StudioPreferences.h"
+
+#include <QtGui/qpainter.h>
+#include <QtWidgets/qwidget.h>
+
+Ruler::Ruler(TimelineItem *parent) : TimelineItem(parent)
+{
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+}
+
+void Ruler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option)
+
+ double xStep = TimelineConstants::RULER_SEC_W / TimelineConstants::RULER_SEC_DIV * m_timeScale;
+ double activeSegmentsWidth = TimelineConstants::RULER_EDGE_OFFSET
+ + m_duration / 1000.0 * xStep * TimelineConstants::RULER_SEC_DIV;
+ double totalSegmentsWidth = TimelineConstants::RULER_EDGE_OFFSET
+ + m_maxDuration / 1000.0 * xStep * TimelineConstants::RULER_SEC_DIV;
+
+ // Ruler painted width to be at least widget width
+ double minRulerWidth = widget->width();
+ double rowXMax = std::max(minRulerWidth, totalSegmentsWidth);
+
+ painter->save();
+ painter->setPen(CStudioPreferences::timelineRulerColorDisabled());
+ painter->drawLine(TimelineConstants::RULER_EDGE_OFFSET,
+ TimelineConstants::RULER_BASE_Y,
+ rowXMax + TimelineConstants::RULER_EDGE_OFFSET,
+ TimelineConstants::RULER_BASE_Y);
+ painter->setPen(CStudioPreferences::timelineRulerColor());
+ painter->drawLine(TimelineConstants::RULER_EDGE_OFFSET,
+ TimelineConstants::RULER_BASE_Y,
+ activeSegmentsWidth,
+ TimelineConstants::RULER_BASE_Y);
+
+ QFont font = painter->font();
+ font.setPointSize(8);
+ painter->setFont(font);
+
+ const int margin = 50;
+ const int secDiv = TimelineConstants::RULER_SEC_DIV;
+ double rowX = 0;
+ bool useDisabledColor = false;
+ for (int i = 0; rowX < rowXMax; i++) {
+ rowX = TimelineConstants::RULER_EDGE_OFFSET + xStep * i;
+
+ // Optimization to skip painting outside the visible area
+ if (rowX < (m_viewportX - margin) || rowX > (m_viewportX + minRulerWidth + margin))
+ continue;
+
+ const int h = i % secDiv == 0 ? TimelineConstants::RULER_DIV_H1
+ : i % secDiv == secDiv * 0.5 ? TimelineConstants::RULER_DIV_H2
+ : TimelineConstants::RULER_DIV_H3;
+
+ if (!useDisabledColor && rowX > activeSegmentsWidth) {
+ painter->setPen(CStudioPreferences::timelineRulerColorDisabled());
+ useDisabledColor = true;
+ }
+ painter->drawLine(QPointF(rowX, TimelineConstants::RULER_BASE_Y - h),
+ QPointF(rowX, TimelineConstants::RULER_BASE_Y - 1));
+
+ // See if label should be shown at this tick at this zoom level
+ bool drawTimestamp = false;
+ if ((i % (secDiv * 4) == 0)
+ || (i % (secDiv * 2) == 0 && m_timeScale >= TimelineConstants::RULER_TICK_SCALE1)
+ || (i % secDiv == 0 && m_timeScale >= TimelineConstants::RULER_TICK_SCALE2)
+ || (i % secDiv == secDiv * 0.5
+ && m_timeScale >= TimelineConstants::RULER_TICK_SCALE3)
+ || (m_timeScale >= TimelineConstants::RULER_TICK_SCALE4)) {
+ drawTimestamp = true;
+ }
+
+ if (drawTimestamp) {
+ QRectF timestampPos = QRectF(TimelineConstants::RULER_EDGE_OFFSET
+ + xStep * i - TimelineConstants::RULER_LABEL_W / 2,
+ 1, TimelineConstants::RULER_LABEL_W,
+ TimelineConstants::RULER_LABEL_H);
+ painter->drawText(timestampPos, Qt::AlignCenter,
+ timestampString(i * 1000 / TimelineConstants::RULER_SEC_DIV));
+ }
+
+ }
+
+ painter->restore();
+}
+
+void Ruler::setTimelineScale(double scl)
+{
+ m_timeScale = scl;
+ update();
+}
+
+// convert distance values to time (milliseconds)
+long Ruler::distanceToTime(double distance) const
+{
+ return distance / (TimelineConstants::RULER_MILLI_W * m_timeScale);
+}
+
+// convert time (milliseconds) values to distance
+double Ruler::timeToDistance(long time) const
+{
+ return time * TimelineConstants::RULER_MILLI_W * m_timeScale;
+}
+
+double Ruler::timelineScale() const
+{
+ return m_timeScale;
+}
+
+// Returns end of right-most layer/component row.
+// Active color of ruler is used up to this point.
+// Slide plays up to this point.
+long Ruler::duration() const
+{
+ return m_duration;
+}
+
+// Returns end of right-most row.
+// Ruler steps & labels are drawn up to this point.
+// Timeline scrollbar allows scrolling up to this point.
+long Ruler::maxDuration() const
+{
+ return m_maxDuration;
+}
+
+void Ruler::setDuration(long duration)
+{
+ if (m_duration != duration) {
+ m_duration = duration;
+ update();
+ emit durationChanged(m_duration);
+ }
+}
+
+void Ruler::setMaxDuration(long maxDuration)
+{
+ if (m_maxDuration != maxDuration) {
+ m_maxDuration = maxDuration;
+ update();
+ emit maxDurationChanged(m_maxDuration);
+ }
+}
+
+void Ruler::setViewportX(int viewportX)
+{
+ if (m_viewportX != viewportX) {
+ m_viewportX = viewportX;
+ emit viewportXChanged(m_viewportX);
+ update();
+ }
+}
+
+int Ruler::viewportX() const
+{
+ return m_viewportX;
+}
+
+// Returns timestamp in mm:ss.ttt or ss.ttt format
+const QString Ruler::timestampString(int timeMs)
+{
+ static const QString zeroString = tr("0");
+ static const QChar fillChar = tr("0").at(0);
+ static const QString noMinutesTemplate = tr("%1.%2");
+ static const QString minutesTemplate = tr("%1:%2.%3");
+
+ int ms = timeMs % 1000;
+ int s = timeMs % 60000 / 1000;
+ int m = timeMs % 3600000 / 60000;
+ const QString msString = QString::number(ms).rightJustified(3, fillChar);
+ const QString sString = QString::number(s);
+
+ if (timeMs == 0) {
+ return zeroString;
+ } else if (m == 0) {
+ if (s < 10)
+ return noMinutesTemplate.arg(sString).arg(msString);
+ else
+ return noMinutesTemplate.arg(sString.rightJustified(2, fillChar)).arg(msString);
+ } else {
+ return minutesTemplate.arg(m).arg(sString.rightJustified(2, fillChar)).arg(msString);
+ }
+}
+
+int Ruler::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeRuler;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h
new file mode 100644
index 00000000..005a23c3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef RULER_H
+#define RULER_H
+
+#include "TimelineItem.h"
+
+class Ruler : public TimelineItem
+{
+ Q_OBJECT
+
+signals:
+ void rulerClicked(const double &pos);
+
+public:
+ explicit Ruler(TimelineItem *parent = nullptr);
+
+ void setTimelineScale(double scl);
+ long distanceToTime(double distance) const;
+ double timeToDistance(long time) const;
+ double timelineScale() const;
+ long duration() const;
+ long maxDuration() const;
+ void setDuration(long duration);
+ void setMaxDuration(long maxDuration);
+ void setViewportX(int viewportX);
+ int viewportX() const;
+ int type() const override;
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+
+signals:
+ void maxDurationChanged(long maxDuration);
+ void durationChanged(long duration);
+ void viewportXChanged(int viewportX);
+
+private:
+ const QString timestampString(int timeMs);
+ double m_timeScale = 2;
+ long m_duration = 0; // milliseconds
+ long m_maxDuration = 0; // milliseconds
+ int m_viewportX = 0;
+};
+
+#endif // RULER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp
new file mode 100644
index 00000000..866d8109
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineItem.h"
+#include "TimelineConstants.h"
+
+#include <QtGui/qpainter.h>
+
+TimelineItem::TimelineItem(TimelineItem *parent) : QGraphicsWidget(parent)
+{
+ setPreferredHeight(TimelineConstants::ROW_H_EXPANDED);
+ setMaximumHeight(TimelineConstants::ROW_H);
+}
+
+int TimelineItem::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeTimelineItem;
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h
new file mode 100644
index 00000000..71d26c0d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINEITEM_H
+#define TIMELINEITEM_H
+
+#include <QtWidgets/qgraphicswidget.h>
+
+class TimelineItem : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineItem(TimelineItem *parent = nullptr);
+
+ enum ItemType {
+ TypeTimelineItem = UserType + 1,
+ TypeInteractiveTimelineItem,
+ TypeTreeHeader,
+ TypeRowTree,
+ TypeRowTreeLabelItem,
+ TypeRowTimeline,
+ TypeRowTimelineCommentItem,
+ TypePlayHead,
+ TypeRuler,
+ TypeRowMover
+ };
+
+ int type() const override;
+};
+
+#endif // TIMELINEITEM_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp
new file mode 100644
index 00000000..05d24fa6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp
@@ -0,0 +1,456 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TimelineToolbar.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Dispatch.h"
+#include "DataInputSelectView.h"
+#include "DataInputDlg.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "StudioPreferences.h"
+#include "ClientDataModelBridge.h"
+#include "IDocumentEditor.h"
+#include "DocumentEditorEnumerations.h"
+#include "Dialogs.h"
+
+#include <QtWidgets/qslider.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qtimer.h>
+#include <QtWidgets/qpushbutton.h>
+#include <QtWidgets/qshortcut.h>
+
+TimelineToolbar::TimelineToolbar() : QToolBar()
+{
+ setContentsMargins(0, 0, 0, 0);
+ setIconSize(QSize(16, 16));
+
+ // create icons
+ static const QIcon iconLayer = QIcon(":/images/Objects-Layer-Normal.png");
+ static const QIcon iconDelete = QIcon(":/images/Action-Trash-Normal.png");
+ static const QIcon iconFirst = QIcon(":/images/playback_tools_first.png");
+ static const QIcon iconLast = QIcon(":/images/playback_tools_last.png");
+ static const QIcon iconZoomIn = QIcon(":/images/zoom_in.png");
+ static const QIcon iconZoomOut = QIcon(":/images/zoom_out.png");
+ m_iconDiActive = QIcon(":/images/Objects-DataInput-Active.png");
+ m_iconDiInactive = QIcon(":/images/Objects-DataInput-Inactive.png");
+ m_iconStop = QIcon(":/images/playback_tools_stop.png");
+ m_iconPlay = QIcon(":/images/playback_tools_play.png");
+
+ // create actions
+ QString ctrlKey(QStringLiteral("Ctrl+"));
+ QString altKey(QStringLiteral("Alt+"));
+#ifdef Q_OS_MACOS
+ ctrlKey = "⌘";
+ altKey = "⌥";
+#endif
+ QString newLayerString = tr("Add New Layer (%1L)").arg(ctrlKey);
+ m_actionNewLayer = new QAction(iconLayer, newLayerString, this);
+ QAction *actionFirst = new QAction(iconFirst, tr("Go to Timeline Start"), this);
+ QAction *actionLast = new QAction(iconLast, tr("Go to Timeline End"), this);
+ m_actionDataInput = new QAction(m_iconDiInactive, tr("No Controller"), this);
+ m_actionDeleteRow = new QAction(iconDelete, tr("Delete Selected Object (Del)"), this);
+ m_actionPlayStop = new QAction(this);
+ m_timeLabel = new QPushButton(this);
+ m_diLabel = new QLabel(this);
+ m_actionZoomIn = new QAction(iconZoomIn, tr("Zoom In"), this);
+ m_actionZoomOut = new QAction(iconZoomOut, tr("Zoom Out"), this);
+
+ m_scaleSlider = new QSlider();
+ m_scaleSlider->setOrientation(Qt::Horizontal);
+ m_scaleSlider->setFixedWidth(100);
+ m_scaleSlider->setMinimum(1);
+ m_scaleSlider->setMaximum(22);
+ m_scaleSlider->setValue(2);
+
+ m_timeLabel->setObjectName(QLatin1String("timelineButton"));
+ m_timeLabel->setFlat(true);
+ m_timeLabel->setMinimumWidth(80);
+ m_timeLabel->setToolTip(tr("Go To Time (%1%2T)").arg(ctrlKey).arg(altKey));
+
+ m_diLabel->setText("");
+ m_diLabel->setMinimumWidth(100);
+ m_diLabel->setAlignment(Qt::AlignCenter);
+ QString styleString = "QLabel { background: transparent; color: "
+ + QString(CStudioPreferences::dataInputColor().name()) + "; }";
+ m_diLabel->setStyleSheet(styleString);
+
+ m_actionShowRowTexts = new QAction(tr("Toggle Timebars Text Visibility"), this);
+ QIcon rowTextIcon { QPixmap(":/images/timeline_text_hidden.png") };
+ rowTextIcon.addPixmap(QPixmap(":/images/timeline_text_shown.png"), QIcon::Normal, QIcon::On);
+ m_actionShowRowTexts->setIcon(rowTextIcon);
+ m_actionShowRowTexts->setCheckable(true);
+ m_actionFilter = new QAction(tr("Filter Timeline Rows Visibility According to Variants Filter"),
+ this);
+ m_actionFilter->setCheckable(true);
+ QIcon filterIcon { QPixmap(":/images/filter.png") };
+ filterIcon.addPixmap(QPixmap(":/images/filter-colored.png"), QIcon::Normal, QIcon::On);
+ m_actionFilter->setIcon(filterIcon);
+
+ updatePlayButtonState(false);
+
+ // connections
+ connect(m_actionNewLayer, &QAction::triggered, this, &TimelineToolbar::newLayerTriggered);
+ connect(m_actionDeleteRow, &QAction::triggered, this, &TimelineToolbar::deleteLayerTriggered);
+ connect(m_timeLabel, &QPushButton::clicked, this, &TimelineToolbar::gotoTimeTriggered);
+ connect(actionFirst, &QAction::triggered, this, &TimelineToolbar::firstFrameTriggered);
+ connect(m_actionPlayStop, &QAction::triggered, this, &TimelineToolbar::onPlayButtonClicked);
+ connect(actionLast, &QAction::triggered, this, &TimelineToolbar::lastFrameTriggered);
+ connect(m_scaleSlider, &QSlider::valueChanged, this, &TimelineToolbar::onZoomLevelChanged);
+ connect(m_actionZoomIn, &QAction::triggered, this, &TimelineToolbar::onZoomInButtonClicked);
+ connect(m_actionZoomOut, &QAction::triggered, this, &TimelineToolbar::onZoomOutButtonClicked);
+ connect(m_actionDataInput, &QAction::triggered, this, &TimelineToolbar::onDiButtonClicked);
+ connect(m_actionShowRowTexts, &QAction::toggled, this, &TimelineToolbar::showRowTextsToggled);
+ connect(m_actionFilter, &QAction::toggled, this, &TimelineToolbar::variantsFilterToggled);
+
+ // add actions
+ addAction(m_actionNewLayer);
+ addAction(m_actionDeleteRow);
+ addAction(m_actionDataInput);
+ addSpacing(2);
+ addAction(m_actionShowRowTexts);
+ addAction(m_actionFilter);
+ addWidget(m_diLabel);
+ addSpacing(20);
+ addWidget(m_timeLabel);
+ addSpacing(20);
+ addAction(actionFirst);
+ addAction(m_actionPlayStop);
+ addAction(actionLast);
+ addSpacing(30);
+ addAction(m_actionZoomOut);
+ addWidget(m_scaleSlider);
+ addAction(m_actionZoomIn);
+
+ // add keyboard shortcuts
+ m_actionZoomOut->setShortcut(Qt::Key_Minus);
+ m_actionZoomOut->setShortcutContext(Qt::ApplicationShortcut);
+ m_actionZoomIn->setShortcut(Qt::Key_Plus);
+ m_actionZoomIn->setShortcutContext(Qt::ApplicationShortcut);
+ m_actionNewLayer->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_L));
+ m_actionNewLayer->setShortcutContext(Qt::ApplicationShortcut);
+
+ QShortcut *gotoTimeShortcut = new QShortcut(this);
+ gotoTimeShortcut->setKey(QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_T));
+ gotoTimeShortcut->setContext(Qt::ApplicationShortcut);
+ connect(gotoTimeShortcut, &QShortcut::activated, this, &TimelineToolbar::gotoTimeTriggered);
+
+ m_connectSelectionChange = g_StudioApp.GetCore()->GetDispatch()->ConnectSelectionChange(
+ std::bind(&TimelineToolbar::onSelectionChange, this, std::placeholders::_1));
+
+ // make datainput indicator listen to selection dialog choice
+ const QVector<EDataType> acceptedTypes = { EDataType::DataTypeRangedNumber };
+ m_dataInputSelector = new DataInputSelectView(acceptedTypes, this);
+ g_StudioApp.GetCore()->GetDispatch()->AddDataModelListener(this);
+ connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged,
+ this, &TimelineToolbar::onDataInputChange);
+}
+
+TimelineToolbar::~TimelineToolbar()
+{
+ delete m_dataInputSelector;
+}
+
+void TimelineToolbar::onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable)
+{
+ qt3dsdm::TInstanceHandleList selectedInstances = inNewSelectable.GetSelectedInstances();
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ CClientDataModelBridge *theClientBridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ bool canDelete = false;
+ for (size_t idx = 0, end = selectedInstances.size(); idx < end; ++idx) {
+ if (theClientBridge->CanDelete(selectedInstances[idx])) {
+ canDelete = true;
+ break;
+ }
+ }
+
+ m_actionDeleteRow->setEnabled(canDelete);
+}
+
+// add a spacer widget
+void TimelineToolbar::addSpacing(int width)
+{
+ auto *widget = new QWidget;
+ widget->setStyleSheet("background:transparent;");
+ widget->setFixedWidth(width);
+ addWidget(widget);
+}
+
+void TimelineToolbar::setTime(long totalMillis)
+{
+ long mins = totalMillis % 3600000 / 60000;
+ long secs = totalMillis % 60000 / 1000;
+ long millis = totalMillis % 1000;
+
+ m_timeLabel->setText(QString::asprintf("%01d:%02d.%03d", mins, secs, millis));
+}
+
+QString TimelineToolbar::getCurrentController() const
+{
+ return m_currController;
+}
+
+QAction *TimelineToolbar::actionShowRowTexts() const
+{
+ return m_actionShowRowTexts;
+}
+
+void TimelineToolbar::setNewLayerEnabled(bool enable)
+{
+ m_actionNewLayer->setEnabled(enable);
+}
+
+void TimelineToolbar::updatePlayButtonState(bool started)
+{
+ if (started) {
+ m_actionPlayStop->setIcon(m_iconStop);
+ m_actionPlayStop->setText(tr("Stop Playing (Enter)"));
+ } else {
+ m_actionPlayStop->setIcon(m_iconPlay);
+ m_actionPlayStop->setText(tr("Start Playing (Enter)"));
+ }
+}
+
+void TimelineToolbar::onPlayButtonClicked()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ if (doc->IsPlaying())
+ emit stopTriggered();
+ else
+ emit playTriggered();
+}
+
+void TimelineToolbar::onZoomLevelChanged(int scale)
+{
+ m_actionZoomIn->setEnabled(scale < m_scaleSlider->maximum());
+ m_actionZoomOut->setEnabled(scale > m_scaleSlider->minimum());
+
+ emit timelineScaleChanged(scale);
+}
+
+void TimelineToolbar::onZoomInButtonClicked()
+{
+ m_scaleSlider->setValue(m_scaleSlider->value() + 1);
+}
+
+void TimelineToolbar::onZoomOutButtonClicked()
+{
+ m_scaleSlider->setValue(m_scaleSlider->value() - 1);
+}
+
+void TimelineToolbar::onDiButtonClicked()
+{
+ QWidget *diButton = widgetForAction(m_actionDataInput);
+ if (diButton) {
+ QPoint chooserPos = diButton->pos() + QPoint(diButton->size().width(),
+ diButton->size().height());
+ showDataInputChooser(mapToGlobal(chooserPos));
+ }
+}
+
+bool TimelineToolbar::isVariantsFilterOn() const
+{
+ return m_actionFilter->isChecked();
+}
+
+// Update datainput button state according to this timecontext control state.
+void TimelineToolbar::updateDataInputStatus()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMPropertyHandle ctrldProp;
+ qt3dsdm::Qt3DSDMInstanceHandle timeCtxRoot = doc->GetActiveRootInstance();
+ CClientDataModelBridge *theClientBridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ if (theClientBridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_SCENE) {
+ ctrldProp = theClientBridge->GetObjectDefinitions().m_Scene.m_ControlledProperty;
+ } else if (theClientBridge->GetObjectType(timeCtxRoot) ==
+ EStudioObjectType::OBJTYPE_COMPONENT) {
+ ctrldProp = theClientBridge->GetObjectDefinitions().m_Component.m_ControlledProperty;
+ } else {
+ Q_ASSERT(false);
+ }
+
+ qt3dsdm::SValue controlledPropertyVal;
+ doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(
+ timeCtxRoot, ctrldProp, controlledPropertyVal);
+ auto existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal);
+
+ QString newController;
+ int timelineStrPos = existingCtrl.indexOf("@timeline");
+ if (timelineStrPos != -1) {
+ int ctrStrPos = existingCtrl.lastIndexOf("$", timelineStrPos - 2);
+ newController = existingCtrl.mid(ctrStrPos + 1, timelineStrPos - ctrStrPos - 2);
+ }
+ if (newController != m_currController) {
+ m_currController = newController;
+ // Toggle if we changed to a controlled time context, or if icon current state
+ // differs from the control state of current time context
+ if (!m_currController.isEmpty()) {
+ m_actionDataInput->setToolTip(
+ tr("Timeline Controller:\n%1").arg(m_currController));
+ m_actionDataInput->setIcon(m_iconDiActive);
+ updateTimelineTitleColor(true);
+ } else {
+ // TODO actually delete the entire property instead of setting it as empty string
+ m_actionDataInput->setIcon(m_iconDiInactive);
+ m_actionDataInput->setToolTip(tr("No Controller"));
+ updateTimelineTitleColor(false);
+ }
+ m_diLabel->setText(m_currController);
+ emit controllerChanged(m_currController);
+ if (m_dataInputSelector && m_dataInputSelector->isVisible())
+ m_dataInputSelector->setCurrentController(m_currController);
+ }
+}
+
+void TimelineToolbar::showDataInputChooser(const QPoint &point)
+{
+ QString currCtr = m_currController.size() ?
+ m_currController : m_dataInputSelector->getNoneString();
+ QVector<QPair<QString, int>> dataInputList;
+
+ for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems))
+ dataInputList.append({it->name, it->type});
+
+ m_dataInputSelector->setData(dataInputList, currCtr);
+
+ CDialogs::showWidgetBrowser(this, m_dataInputSelector, point,
+ CDialogs::WidgetBrowserAlign::ToolButton);
+}
+
+void TimelineToolbar::onDataInputChange(int handle, int instance, const QString &dataInputName)
+{
+ Q_UNUSED(handle)
+ Q_UNUSED(instance)
+
+ if (dataInputName == m_currController)
+ return;
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ QString fullTimeControlStr;
+
+ if (dataInputName != m_dataInputSelector->getNoneString()) {
+ m_actionDataInput->setToolTip(tr("Timeline Controller:\n%1").arg(dataInputName));
+ fullTimeControlStr = "$" + dataInputName + " @timeline";
+ m_actionDataInput->setIcon(m_iconDiActive);
+ m_currController = dataInputName;
+ updateTimelineTitleColor(true);
+ } else {
+ m_actionDataInput->setToolTip(tr("No Controller"));
+ // TODO actually delete the entire property instead of setting it as empty string
+ m_actionDataInput->setIcon(m_iconDiInactive);
+ m_currController.clear();
+ updateTimelineTitleColor(false);
+ }
+
+ emit controllerChanged(m_currController);
+
+ // To indicate that this presentation timeline is controlled by data input,
+ // we set "controlled property" of this time context root (scene or component)
+ // to contain the name of controller followed by special indicator "@timeline".
+ // Either replace existing timeline control indicator string or append new one
+ // but do not touch @slide indicator string as scene can have both
+ qt3dsdm::Qt3DSDMPropertyHandle ctrldPropertyHandle;
+ qt3dsdm::Qt3DSDMInstanceHandle timeCtxRoot = doc->GetActiveRootInstance();
+ // Time context root is either scene or component
+ if (bridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_SCENE)
+ ctrldPropertyHandle = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty;
+ else if (bridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_COMPONENT)
+ ctrldPropertyHandle = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty;
+ else
+ Q_ASSERT(false);
+
+ qt3dsdm::SValue controlledPropertyVal;
+ doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(
+ timeCtxRoot, ctrldPropertyHandle, controlledPropertyVal);
+
+ auto existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal);
+ int slideStrPos = existingCtrl.indexOf("@timeline");
+ if (slideStrPos != -1) {
+ // find the controlling datainput name and build the string to replace
+ int ctrStrPos = existingCtrl.lastIndexOf("$", slideStrPos - 2);
+ QString prevCtrler = existingCtrl.mid(ctrStrPos, slideStrPos - ctrStrPos);
+ existingCtrl.replace(prevCtrler + "@timeline", fullTimeControlStr);
+ } else {
+ if (!existingCtrl.isEmpty() && m_currController.size())
+ existingCtrl.append(" ");
+ existingCtrl.append(fullTimeControlStr);
+ }
+
+ if (existingCtrl.endsWith(" "))
+ existingCtrl.chop(1);
+
+ if (existingCtrl.startsWith(" "))
+ existingCtrl.remove(0, 1);
+
+ m_diLabel->setText(m_currController);
+ qt3dsdm::SValue fullCtrlPropVal
+ = std::make_shared<qt3dsdm::CDataStr>(
+ Q3DStudio::CString::fromQString(existingCtrl));
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Timeline control"))
+ ->SetInstancePropertyValue(timeCtxRoot, ctrldPropertyHandle, fullCtrlPropVal);
+}
+
+void TimelineToolbar::OnBeginDataModelNotifications()
+{
+}
+
+void TimelineToolbar::OnEndDataModelNotifications()
+{
+ updateDataInputStatus();
+}
+
+void TimelineToolbar::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ Q_UNUSED(inInstance)
+}
+
+void TimelineToolbar::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount)
+{
+ Q_UNUSED(inInstance)
+ Q_UNUSED(inInstanceCount)
+}
+
+// Notify the user about control state change also with timeline dock
+// title color change.
+void TimelineToolbar::updateTimelineTitleColor(bool controlled)
+{
+ QString styleString;
+ if (controlled) {
+ styleString = "QDockWidget#timeline { color: "
+ + QString(CStudioPreferences::dataInputColor().name()) + "; }";
+ } else {
+ styleString = "QDockWidget#timeline { color: "
+ + QString(CStudioPreferences::textColor().name()) + "; }";
+ }
+
+ QWidget *timelineDock = parentWidget()->parentWidget()->parentWidget();
+ timelineDock->setStyleSheet(styleString);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h
new file mode 100644
index 00000000..53a87b18
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TIMELINETOOLBAR_H
+#define TIMELINETOOLBAR_H
+
+#include "SelectedValueImpl.h"
+#include "Qt3DSDMSignals.h"
+#include "DispatchListeners.h"
+#include "Dispatch.h"
+#include "DataInputSelectView.h"
+#include <QtWidgets/qtoolbar.h>
+#include <QtWidgets/qlabel.h>
+
+QT_FORWARD_DECLARE_CLASS(QAction)
+QT_FORWARD_DECLARE_CLASS(QSlider)
+
+class TimelineToolbar : public QToolBar,
+ public IDataModelListener
+{
+ Q_OBJECT
+
+signals:
+ void newLayerTriggered();
+ void deleteLayerTriggered();
+ void gotoTimeTriggered();
+ void firstFrameTriggered();
+ void stopTriggered();
+ void playTriggered();
+ void controllerChanged(const QString &controller);
+ void lastFrameTriggered();
+ void timelineScaleChanged(int scale);
+ void setDurationTriggered();
+ void showRowTextsToggled(bool toggled);
+ void variantsFilterToggled(bool toggled);
+
+public:
+ TimelineToolbar();
+ virtual ~TimelineToolbar() override;
+ void setTime(long totalMillis);
+ QString getCurrentController() const;
+ void setNewLayerEnabled(bool enable);
+ QAction *actionShowRowTexts() const;
+ bool isVariantsFilterOn() const;
+
+ // IDataModelListener
+ void OnBeginDataModelNotifications() override;
+ void OnEndDataModelNotifications() override;
+ void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount) override;
+
+public Q_SLOTS:
+ void updatePlayButtonState(bool started);
+ void onZoomInButtonClicked();
+ void onZoomOutButtonClicked();
+
+private Q_SLOTS:
+ void onPlayButtonClicked();
+ void onZoomLevelChanged(int scale);
+ void onDiButtonClicked();
+
+private:
+ void addSpacing(int width);
+ void onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable);
+ void onDataInputChange(int handle, int instance, const QString &dataInputName);
+ void showDataInputChooser(const QPoint &point);
+ void updateDataInputStatus();
+ void updateTimelineTitleColor(bool controlled);
+
+ QPushButton *m_timeLabel = nullptr;
+ QLabel *m_diLabel = nullptr;
+ QAction *m_actionDeleteRow = nullptr;
+ QAction *m_actionPlayStop = nullptr;
+ QAction *m_actionZoomIn = nullptr;
+ QAction *m_actionZoomOut = nullptr;
+ QAction *m_actionDataInput = nullptr;
+ QAction *m_actionNewLayer = nullptr;
+ QAction *m_actionShowRowTexts = nullptr;
+ QAction *m_actionFilter = nullptr;
+ QSlider *m_scaleSlider = nullptr;
+ qt3dsdm::TSignalConnectionPtr m_connectSelectionChange;
+ QIcon m_iconStop;
+ QIcon m_iconPlay;
+ QIcon m_iconDiActive;
+ QIcon m_iconDiInactive;
+
+ QString m_currController;
+
+ DataInputSelectView *m_dataInputSelector;
+};
+#endif // TIMELINETOOLBAR_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp
new file mode 100644
index 00000000..55e0a8fe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp
@@ -0,0 +1,176 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TreeHeader.h"
+#include "StudioPreferences.h"
+#include "StudioUtils.h"
+
+#include <QtGui/qpainter.h>
+
+
+TreeHeader::TreeHeader(TimelineGraphicsScene *timelineScene, TimelineItem *parent)
+ : TimelineItem(parent)
+ , m_scene(timelineScene)
+{
+ setAcceptHoverEvents(true);
+}
+
+void TreeHeader::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0;
+
+ double treeWidth = m_scene->treeWidth() - m_scene->getScrollbarOffsets().x();
+ m_rectShy .setRect(treeWidth - 16 * 3.3, size().height() * .5 - 8, 16, 16);
+ m_rectVisible.setRect(treeWidth - 16 * 2.2, size().height() * .5 - 8, 16, 16);
+ m_rectLock .setRect(treeWidth - 16 * 1.1, size().height() * .5 - 8, 16, 16);
+
+ static const QPixmap pixShy = QPixmap(":/images/Toggle-Shy.png");
+ static const QPixmap pixShy2x = QPixmap(":/images/Toggle-Shy@2x.png");
+ static const QPixmap pixVisible = QPixmap(":/images/Toggle-HideShow.png");
+ static const QPixmap pixVisible2x = QPixmap(":/images/Toggle-HideShow@2x.png");
+ static const QPixmap pixLock = QPixmap(":/images/Toggle-Lock.png");
+ static const QPixmap pixLock2x = QPixmap(":/images/Toggle-Lock@2x.png");
+
+ const QColor selectedColor = CStudioPreferences::timelineFilterButtonSelectedColor();
+ const QColor hoveredColor = CStudioPreferences::timelineFilterButtonHoveredColor();
+
+ if (m_shy)
+ painter->fillRect(m_rectShy, selectedColor);
+
+ if (m_visible)
+ painter->fillRect(m_rectVisible, selectedColor);
+
+ if (m_lock)
+ painter->fillRect(m_rectLock, selectedColor);
+
+ // Paint hovering as semi-transparent overlay
+ if (m_hoveredItem == TreeControlType::Shy)
+ painter->fillRect(m_rectShy, hoveredColor);
+ else if (m_hoveredItem == TreeControlType::Hide)
+ painter->fillRect(m_rectVisible, hoveredColor);
+ else if (m_hoveredItem == TreeControlType::Lock)
+ painter->fillRect(m_rectLock, hoveredColor);
+
+ painter->drawPixmap(m_rectShy , hiResIcons ? pixShy2x : pixShy);
+ painter->drawPixmap(m_rectVisible , hiResIcons ? pixVisible2x : pixVisible);
+ painter->drawPixmap(m_rectLock , hiResIcons ? pixLock2x : pixLock);
+}
+
+TreeControlType TreeHeader::handleButtonsClick(const QPointF &scenePos)
+{
+ QPointF p = mapFromScene(scenePos.x(), scenePos.y());
+
+ if (m_rectShy.contains(p.x(), p.y())) {
+ toggleFilterShy();
+ return TreeControlType::Shy;
+ } else if (m_rectVisible.contains(p.x(), p.y())) {
+ toggleFilterHidden();
+ return TreeControlType::Hide;
+ } else if (m_rectLock.contains(p.x(), p.y())) {
+ toggleFilterLocked();
+ return TreeControlType::Lock;
+ }
+
+ return TreeControlType::None;
+}
+
+bool TreeHeader::filterShy() const
+{
+ return m_shy;
+}
+
+bool TreeHeader::filterHidden() const
+{
+ return m_visible;
+}
+
+bool TreeHeader::filterLocked() const
+{
+ return m_lock;
+}
+
+int TreeHeader::type() const
+{
+ // Enable the use of qgraphicsitem_cast with this item.
+ return TypeTreeHeader;
+}
+
+void TreeHeader::toggleFilterShy()
+{
+ m_shy = !m_shy;
+ update();
+}
+
+void TreeHeader::toggleFilterHidden()
+{
+ m_visible = !m_visible;
+ update();
+}
+
+void TreeHeader::toggleFilterLocked()
+{
+ m_lock = !m_lock;
+ update();
+}
+
+void TreeHeader::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
+{
+ QPointF p = event->scenePos();
+ TreeControlType hoveredItem = TreeControlType::None;
+ if (m_rectShy.contains(p.x(), p.y())) {
+ QString action = m_shy ? tr("Show") : tr("Hide");
+ setToolTip(tr("%1 shy objects").arg(action));
+ hoveredItem = TreeControlType::Shy;
+ } else if (m_rectVisible.contains(p.x(), p.y())) {
+ QString action = m_visible ? tr("Show") : tr("Hide");
+ setToolTip(tr("%1 inactive objects").arg(action));
+ hoveredItem = TreeControlType::Hide;
+ } else if (m_rectLock.contains(p.x(), p.y())) {
+ QString action = m_lock ? tr("Show") : tr("Hide");
+ setToolTip(tr("%1 locked objects").arg(action));
+ hoveredItem = TreeControlType::Lock;
+ } else {
+ setToolTip("");
+ }
+
+ if (m_hoveredItem != hoveredItem) {
+ // Update hover status only if it has changed
+ m_hoveredItem = hoveredItem;
+ update();
+ }
+}
+
+void TreeHeader::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+{
+ m_hoveredItem = TreeControlType::None;
+ update();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h
new file mode 100644
index 00000000..6ebf2bad
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TREEHEADER_H
+#define TREEHEADER_H
+
+#include "TimelineItem.h"
+#include "TimelineConstants.h"
+#include "RowTypes.h"
+#include "TimelineGraphicsScene.h"
+
+class RowTimeline;
+
+class TreeHeader : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ explicit TreeHeader(TimelineGraphicsScene *timelineScene, TimelineItem *parent = nullptr);
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ TreeControlType handleButtonsClick(const QPointF &scenePos);
+ bool filterShy() const;
+ bool filterHidden() const;
+ bool filterLocked() const;
+ int type() const override;
+
+protected:
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
+
+private:
+ void toggleFilterShy();
+ void toggleFilterHidden();
+ void toggleFilterLocked();
+ TimelineGraphicsScene *m_scene;
+ bool m_shy = false;
+ bool m_visible = false;
+ bool m_lock = false;
+ TreeControlType m_hoveredItem = TreeControlType::None;
+ QRect m_rectShy;
+ QRect m_rectVisible;
+ QRect m_rectLock;
+};
+
+#endif // TREEHEADER_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp
new file mode 100644
index 00000000..df784418
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "TreeHeaderView.h"
+
+TreeHeaderView::TreeHeaderView(QWidget *parent)
+ : QGraphicsView(parent)
+{
+}
+
+void TreeHeaderView::scrollContentsBy(int dx, int dy)
+{
+ // Overridden to ignore scrolling after initial show related scrolling has been finished
+ //
+ // Longer explanation: When RowTreeLabelItem (QGraphicsTextItem) gets focus
+ // for text editing, it forces views to scroll themselves so that editable
+ // text item is always visible. But we don't want tree header view to move.
+ // See QGraphicsTextItemPrivate::textControl() and _q_ensureVisible()
+ if (m_allowScrolling)
+ QGraphicsView::scrollContentsBy(dx, dy);
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h
new file mode 100644
index 00000000..7b648660
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TREEHEADERVIEW_H
+#define TREEHEADERVIEW_H
+
+#include <QtWidgets/qgraphicsview.h>
+
+class TreeHeaderView : public QGraphicsView
+{
+ Q_OBJECT
+public:
+ TreeHeaderView(QWidget *parent = nullptr);
+
+ void disableScrolling() { m_allowScrolling = false; }
+
+protected:
+ void scrollContentsBy(int dx, int dy) override;
+
+private:
+ bool m_allowScrolling = true;
+};
+
+#endif // TREEHEADERVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml b/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml
new file mode 100644
index 00000000..727488f4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+MouseArea {
+ id: root
+
+ property alias value: value.text
+ property var activeBrowser
+ property bool blockShow: false
+
+ signal showBrowser
+
+ Layout.minimumHeight: _controlBaseHeight
+ Layout.preferredWidth: _valueWidth
+
+ onPressed: {
+ // Block showBrowser event on the mouse press that makes the browser lose focus
+ if (activeBrowser && activeBrowser.visible) {
+ activeBrowser = null;
+ blockShow = true
+ } else {
+ blockShow = false
+ }
+ }
+
+ onClicked: {
+ if (!blockShow)
+ root.showBrowser()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+
+ color: _studioColor2
+
+ StyledLabel {
+ id: value
+ anchors.fill: parent
+ horizontalAlignment: Text.AlignLeft
+ rightPadding: 6 + img.width
+ leftPadding: 6
+ }
+ Image {
+ id: img
+ // Source image size is 16x16 pixels
+ x: parent.width - 18
+ y: parent.height / 2 - 8
+ source: _resDir + "arrow_down.png"
+ rotation: activeBrowser && activeBrowser.focused ? 180 : 0
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml b/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml
new file mode 100644
index 00000000..95458889
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+/* Use for: Position, Rotation, Scale, Pivot ... */
+
+TextField {
+ id: floatTextFieldId
+ property alias decimalValue: validator.decimals
+ property bool ignoreHotkeys: true
+
+ signal previewValueChanged
+
+ selectByMouse: true
+ text: "0.000"
+ Layout.preferredWidth: _valueWidth / 3
+ Layout.preferredHeight: _controlBaseHeight
+
+ topPadding: 0
+ bottomPadding: 0
+ rightPadding: 6
+
+ onTextEdited: {
+ if (text.search(","))
+ text = text.replace(",",".")
+ }
+
+ activeFocusOnPress: false
+
+ horizontalAlignment: TextInput.AlignRight
+ verticalAlignment: TextInput.AlignVCenter
+ validator: DoubleValidator {
+ id: validator
+ decimals: 3
+ locale: "C"
+ notation: DoubleValidator.StandardNotation
+ }
+
+ selectionColor: _selectionColor
+ selectedTextColor: _textColor
+ font.pixelSize: _fontSize
+ color: _textColor
+ background: Rectangle {
+ color: floatTextFieldId.enabled ? _studioColor2 : "transparent"
+ border.width: floatTextFieldId.activeFocus ? 1 : 0
+ border.color: floatTextFieldId.activeFocus ? _selectionColor : _disabledColor
+ }
+
+ Timer {
+ id: rateLimiter
+ interval: 10
+ onTriggered: {
+ floatTextFieldId.previewValueChanged();
+ }
+ }
+
+ cursorVisible: false
+ onActiveFocusChanged: {
+ if (focusReason === Qt.OtherFocusReason) {
+ select(0, 0);
+ cursorVisible = false;
+ } else if (activeFocus) {
+ selectAll();
+ }
+ }
+
+ Item {
+ id: focusEater
+ // Used to eat keyboard focus after drag-modifying the text is finished
+ }
+
+ MouseArea {
+ id: mouseArea
+ property int clickedPos: 0
+ property int pressedX: 0
+ property bool draggingActive: false
+
+ acceptedButtons: Qt.LeftButton
+ preventStealing: true
+ anchors.fill: parent
+ onPressed: {
+ pressedX = mouse.x;
+ draggingActive = false;
+ if (parent.activeFocus) {
+ clickedPos = parent.positionAt(mouse.x, mouse.y);
+ parent.cursorPosition = clickedPos;
+ } else {
+ parent.forceActiveFocus();
+ }
+ }
+ onClicked: {
+ if (!draggingActive && !parent.cursorVisible) {
+ parent.cursorVisible = true;
+ parent.selectAll();
+ }
+ }
+ onReleased: {
+ if (draggingActive) {
+ _mouseHelper.endUnboundedDrag();
+ rateLimiter.stop();
+ floatTextFieldId.onEditingFinished();
+ focusEater.forceActiveFocus();
+ }
+ }
+
+ onCanceled: {
+ if (draggingActive) {
+ _mouseHelper.endUnboundedDrag();
+ rateLimiter.stop();
+ floatTextFieldId.onEditingFinished();
+ focusEater.forceActiveFocus();
+ }
+ }
+
+ onDoubleClicked: {
+ parent.selectAll();
+ parent.cursorVisible = true;
+ }
+
+ onPositionChanged: {
+ if (parent.cursorVisible) {
+ parent.cursorPosition = parent.positionAt(mouse.x, mouse.y);
+ parent.select(clickedPos, parent.cursorPosition);
+ } else {
+ if (!draggingActive) {
+ var startDelta = (pressedX - mouse.x) / 2.0;
+ if (startDelta > 4.0 || startDelta < -4.0) {
+ _mouseHelper.startUnboundedDrag();
+ draggingActive = true;
+ }
+ }
+ if (draggingActive) {
+ var delta = _mouseHelper.delta().x;
+ if (delta !== 0) {
+ if (mouse.modifiers & Qt.ControlModifier)
+ delta *= 0.1;
+ else if (mouse.modifiers & Qt.ShiftModifier)
+ delta *= 10.0;
+ if (floatTextFieldId.text !== "") {
+ floatTextFieldId.text = Number(parseFloat(floatTextFieldId.text)
+ + delta).toFixed(validator.decimals);
+ } else {
+ floatTextFieldId.text = Number(delta).toFixed(validator.decimals);
+ }
+
+ if (!rateLimiter.running)
+ rateLimiter.start();
+ }
+ }
+ }
+ }
+ }
+
+ Keys.onPressed: {
+ if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
+ event.accepted = true
+ var delta = 1.0;
+ if (event.modifiers & Qt.ControlModifier)
+ delta = 0.1;
+ else if (event.modifiers & Qt.ShiftModifier)
+ delta = 10.0;
+ if (event.key === Qt.Key_Down)
+ delta = -delta;
+ if (floatTextFieldId.text !== "") {
+ floatTextFieldId.text = Number(parseFloat(floatTextFieldId.text)
+ + delta).toFixed(validator.decimals);
+ } else {
+ floatTextFieldId.text = Number(delta).toFixed(validator.decimals);
+ }
+
+ if (!rateLimiter.running)
+ rateLimiter.start();
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml
new file mode 100644
index 00000000..4d32d410
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import QtQuick.Window 2.2
+
+ComboBox {
+ id: control
+
+ Layout.preferredHeight: _controlBaseHeight
+ Layout.preferredWidth: _valueWidth
+ topPadding: 0
+ bottomPadding: 0
+ // hack to fix the color after Qt.Quick.Controls2 "optimization"
+ property alias color: backgroundBox.color
+ property bool showArrow: true
+
+ delegate: ItemDelegate {
+ id: itemDelegate
+
+ property bool hasSeparator: itemDelegate.text.endsWith("|separator")
+
+ width: parent.width
+ height: hasSeparator ? _controlBaseHeight + 6 : _controlBaseHeight
+ padding: 0
+ spacing: 0
+ text: {
+ control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole]
+ : model[control.textRole])
+ : modelData
+ }
+ highlighted: control.highlightedIndex === index
+ hoverEnabled: control.hoverEnabled
+ contentItem: ColumnLayout {
+ anchors.fill: itemDelegate
+ spacing: 0
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: _studioColor3
+ visible: itemDelegate.hasSeparator
+ }
+ StyledLabel {
+ Layout.fillWidth: true
+ rightPadding: control.indicator.width + 6
+ leftPadding: 6
+ text: {
+ hasSeparator ? itemDelegate.text.replace("|separator", "")
+ : itemDelegate.text
+ }
+ visible: itemDelegate.text
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+ background: Rectangle {
+ anchors.fill: itemDelegate
+ color: hovered ? _selectionColor : _backgroundColor
+ }
+ }
+
+ indicator: Image {
+ x: control.width - width - 2
+ y: control.topPadding + (control.availableHeight - height) / 2
+ source: _resDir + "arrow_down.png"
+ rotation: control.popup.visible ? 180 : 0
+ visible: control.showArrow
+ }
+
+ contentItem: StyledTextField {
+ text: {
+ var newText = control.editable ? control.editText : control.displayText;
+ var hasSeparator = newText.endsWith("|separator");
+ hasSeparator ? newText.replace("|separator", "") : newText;
+ }
+
+ enabled: control.editable
+ autoScroll: control.editable
+ readOnly: control.popup.visible
+ inputMethodHints: control.inputMethodHints
+ validator: control.validator
+ opacity: 1
+ leftPadding: 6
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ background: Rectangle {
+ id: backgroundBox
+ color: control.enabled ? _studioColor2 : "transparent"
+ border.width: 0
+ }
+
+ popup: Popup {
+ y: control.height
+ width: control.width
+ height: Math.min(contentItem.implicitHeight,
+ control.Window.height - topMargin - bottomMargin)
+ topMargin: 6
+ bottomMargin: 6
+ padding: 0
+
+ contentItem: ListView {
+ clip: true
+ implicitHeight: contentHeight
+ model: control.popup.visible ? control.delegateModel : null
+ currentIndex: control.highlightedIndex
+ highlightRangeMode: ListView.ApplyRange
+ highlightMoveDuration: 0
+ ScrollIndicator.vertical: ScrollIndicator {
+ id: scrollIndicator
+ contentItem: Rectangle {
+ id: indicator
+
+ implicitWidth: 2
+ implicitHeight: 2
+
+ color: _studioColor3
+ visible: scrollIndicator.size < 1.0
+ opacity: 0.75
+ }
+ }
+ Rectangle {
+ z: 10
+ anchors.fill: parent
+ color: "transparent"
+ border.color: _studioColor3
+ }
+ }
+
+ background: Rectangle {
+ color: _studioColor2
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml
new file mode 100644
index 00000000..5185b2ad
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml
@@ -0,0 +1,41 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+Label {
+ id: styledLabelId
+ font.pixelSize: _fontSize
+ color: _textColor
+ Layout.preferredHeight: _controlBaseHeight
+ Layout.preferredWidth: _idWidth
+ verticalAlignment: Text.AlignVCenter
+ clip: true
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml
new file mode 100644
index 00000000..e44b733f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+MenuItem {
+ id: control
+ hoverEnabled: true
+
+ contentItem: StyledLabel {
+ text: control.text
+ visible: control.text
+ horizontalAlignment: Text.AlignLeft
+ color: control.enabled ? _textColor : _disabledColor
+ }
+ background: Rectangle {
+ implicitWidth: _valueWidth
+ implicitHeight: _controlBaseHeight
+ color: control.hovered ? _selectionColor : _studioColor1
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml
new file mode 100644
index 00000000..0fce5d0d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+
+MenuSeparator {
+ id: control
+ padding: 0
+ topPadding: 4
+ bottomPadding: 4
+ leftPadding: 0
+ rightPadding: 0
+ contentItem: Rectangle {
+ width: control.width
+ implicitWidth: control.parent.width - control.leftPadding - control.rightPadding
+ implicitHeight: 1
+ color: _studioColor3
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml
new file mode 100644
index 00000000..bd123453
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+TextField {
+ id: styledTextFieldId
+ property bool ignoreHotkeys: true
+ property bool textChanged: false
+
+ signal editingFinished
+
+ selectByMouse: true
+ text: ""
+ Layout.preferredWidth: _valueWidth
+ Layout.preferredHeight: _controlBaseHeight
+
+ topPadding: 0
+ bottomPadding: 0
+ rightPadding: 6
+ leftPadding: 6
+
+ activeFocusOnPress: false
+
+ horizontalAlignment: TextInput.AlignRight
+ verticalAlignment: TextInput.AlignVCenter
+
+ selectionColor: _selectionColor
+ selectedTextColor: _textColor
+ font.pixelSize: _fontSize
+ color: _textColor
+ background: Rectangle {
+ color: styledTextFieldId.enabled ? _studioColor2 : "transparent"
+ border.width: styledTextFieldId.activeFocus ? 1 : 0
+ border.color: styledTextFieldId.activeFocus ? _selectionColor : _disabledColor
+ }
+
+ cursorVisible: false
+ onActiveFocusChanged: {
+ if (!activeFocus && textChanged) {
+ styledTextFieldId.editingFinished();
+ textChanged = false;
+ }
+
+ if (focusReason === Qt.OtherFocusReason) {
+ select(0, 0);
+ cursorVisible = false;
+ } else if (activeFocus) {
+ selectAll();
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ property int clickedPos: 0
+
+ acceptedButtons: Qt.LeftButton
+ preventStealing: true
+ anchors.fill: parent
+ onPressed: {
+ if (parent.activeFocus) {
+ clickedPos = parent.positionAt(mouse.x, mouse.y);
+ parent.cursorPosition = clickedPos;
+ } else {
+ parent.forceActiveFocus();
+ }
+ }
+
+ onClicked: {
+ if (!parent.cursorVisible) {
+ parent.cursorVisible = true;
+ parent.selectAll();
+ }
+ }
+
+ onDoubleClicked: {
+ parent.selectAll();
+ parent.cursorVisible = true;
+ }
+ }
+
+ onTextChanged: textChanged = true;
+
+ Keys.onPressed: {
+ if (textChanged && (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) {
+ event.accepted = true
+ styledTextFieldId.editingFinished();
+ textChanged = false;
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml
new file mode 100644
index 00000000..e548acf4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+
+StyledToolButton {
+ id: control
+
+ property string checkedImage
+ property string backgroundColor: _backgroundColor
+ property string downColor: _buttonDownColor
+
+ checkable: true
+
+ background: Rectangle {
+ color: control.checked ? downColor : backgroundColor
+ border.color: backgroundColor
+ }
+
+ contentItem: Image {
+ fillMode: Image.Pad
+ source: control.enabled ? control.checked ? _resDir + checkedImage : _resDir + enabledImage
+ : _resDir + disabledImage
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml
new file mode 100644
index 00000000..d1beea28
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+
+ToolButton {
+ id: control
+
+ property string enabledImage
+ property string disabledImage
+ property alias toolTipText: toolTip.text
+
+ hoverEnabled: true
+
+ StyledTooltip {
+ id: toolTip
+ enabled: control.hovered
+ }
+
+ background: Rectangle {
+ color: control.pressed ? _selectionColor : (hovered ? _studioColor1 : "transparent")
+ border.color: _studioColor1
+ }
+
+ contentItem: Image {
+ source: control.enabled ? _resDir + enabledImage : _resDir + disabledImage
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml
new file mode 100644
index 00000000..34b310a4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+
+ToolTip {
+ id: control
+ delay: 500
+ contentItem: StyledLabel {
+ text: control.text
+ }
+
+ // Handle tooltip visibility based on the trigger event given to the 'enabled' property and
+ // the 'Tooltips' view menu setting. Has to be done this way, as even though the eventFilter
+ // set for MainFrm catches the tooltip events for QML, it doesn't prevent showing them because
+ // we were/are controlling the visibility in code.
+ onEnabledChanged: {
+ visible = enabled && _parentView.toolTipsEnabled();
+ }
+
+ background: Rectangle {
+ border.color: _studioColor3
+ color: _studioColor2
+ radius: 2
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp
new file mode 100644
index 00000000..ce7d8e37
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp
@@ -0,0 +1,208 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "scenecameraglwidget.h"
+#include "StudioApp.h"
+#include "IStudioRenderer.h"
+#include "WGLRenderContext.h"
+#include "StudioPreferences.h"
+
+#include <QtGui/qopenglshaderprogram.h>
+#include <QtGui/qopengltexture.h>
+#include <QtGui/qopenglbuffer.h>
+#include <QtGui/qopenglvertexarrayobject.h>
+
+const QVector4D defaultTextureOffset = QVector4D(0.0f, 0.0f, 1.0f, 1.0f);
+const QVector2D defaultGeometryOffset = QVector2D(1.0f, 1.0f);
+
+SceneCameraGlWidget::SceneCameraGlWidget(QWidget *parent)
+ : QOpenGLWidget(parent)
+ , m_textureOffset(defaultTextureOffset)
+ , m_geometryOffset(defaultGeometryOffset)
+{
+ QSurfaceFormat format = CWGLRenderContext::selectSurfaceFormat(this);
+ format.setSamples(1); // We want pixel perfect view, not aliased one
+ setFormat(format);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+SceneCameraGlWidget::~SceneCameraGlWidget()
+{
+ cleanup();
+}
+
+void SceneCameraGlWidget::initializeGL()
+{
+ initializeOpenGLFunctions();
+ QObject::connect(context(), &QOpenGLContext::aboutToBeDestroyed,
+ this, &SceneCameraGlWidget::cleanup);
+
+ m_program = new QOpenGLShaderProgram();
+ if (!m_program->addShaderFromSourceCode(
+ QOpenGLShader::Vertex,
+ "#version 330 core\n"
+ "in vec2 vertexPos;\n"
+ "in vec2 vertexTexCoord;\n"
+ "uniform vec4 uTexOffset;\n"
+ "uniform vec4 uGeomOffset;\n"
+ "out vec2 texCoord;\n"
+ "void main(void)\n"
+ "{\n"
+ " gl_Position = vec4(uGeomOffset.xy + vertexPos * uGeomOffset.zw, 0.0, 1.0);\n"
+ " texCoord = vec2(uTexOffset.z * vertexTexCoord.x + uTexOffset.x,\n"
+ " uTexOffset.w * vertexTexCoord.y + uTexOffset.y);\n"
+ "}")) {
+ qWarning() << __FUNCTION__ << "Failed to add vertex shader for scene camera preview";
+ return;
+ }
+ if (!m_program->addShaderFromSourceCode(
+ QOpenGLShader::Fragment,
+ "#version 330 core\n"
+ "in vec2 texCoord;\n"
+ "uniform sampler2D uSampler;\n"
+ "out vec4 fragColor;\n"
+ "void main(void) {\n"
+ " vec4 oc = texture(uSampler, texCoord);\n"
+ " fragColor = vec4(oc);\n"
+ "}")) {
+
+ qWarning() << __FUNCTION__ << "Failed to add fragment shader for scene camera preview";
+ return;
+ }
+ if (!m_program->link()) {
+ qWarning() << __FUNCTION__ << "Failed to link program for scene camera preview";
+ return;
+ }
+ if (!m_program->bind()) {
+ qWarning() << __FUNCTION__ << "Failed to bind program for scene camera preview";
+ return;
+ } else {
+ GLint vertexAtt = GLint(m_program->attributeLocation("vertexPos"));
+ GLint uvAtt = GLint(m_program->attributeLocation("vertexTexCoord"));
+ m_uniformTextureOffset = GLint(m_program->uniformLocation("uTexOffset"));
+ m_uniformGeometryOffset = GLint(m_program->uniformLocation("uGeomOffset"));
+ m_program->setUniformValue("uSampler", 0);
+
+ m_vao = new QOpenGLVertexArrayObject;
+ if (m_vao->create()) {
+ m_vao->bind();
+ m_vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
+ if (m_vertexBuffer->create() && m_vertexBuffer->bind()) {
+ GLfloat vertexBuffer[] = {-1.0f, 1.0f,
+ -1.0f, -1.0f,
+ 1.0f, 1.0f,
+ 1.0f, -1.0f};
+ m_vertexBuffer->allocate(vertexBuffer, 8 * sizeof(GLfloat));
+ glEnableVertexAttribArray(vertexAtt);
+ glVertexAttribPointer(vertexAtt, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
+ } else {
+ qWarning() << __FUNCTION__
+ << "Failed to create/bind vertex buffer for scene camera preview";
+ return;
+ }
+ m_uvBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
+ if (m_uvBuffer->create() && m_uvBuffer->bind()) {
+ GLfloat uvBuffer[] = {0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f};
+ m_uvBuffer->allocate(uvBuffer, 8 * sizeof(GLfloat));
+ glEnableVertexAttribArray(uvAtt);
+ glVertexAttribPointer(uvAtt, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
+ } else {
+ qWarning() << __FUNCTION__
+ << "Failed to create/bind UV buffer for scene camera preview";
+ return;
+ }
+
+ m_vao->release();
+ } else {
+ qWarning() << __FUNCTION__ << "Failed to create/bind vertex array object";
+ return;
+ }
+ }
+
+ const QColor matteColor = CStudioPreferences::matteColor();
+ glClearColor(matteColor.redF(), matteColor.greenF(), matteColor.blueF(), 1.0f);
+}
+
+void SceneCameraGlWidget::paintGL()
+{
+ Q3DStudio::IStudioRenderer &renderer(g_StudioApp.getRenderer());
+ if (renderer.IsInitialized()) {
+ m_vao->bind();
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_BLEND);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ QSize fboSize;
+ qt3ds::QT3DSU32 textureId;
+ renderer.getPreviewFbo(fboSize, textureId);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, GLuint(textureId));
+
+ m_program->setUniformValueArray(m_uniformTextureOffset, &m_textureOffset, 1);
+ m_program->setUniformValueArray(m_uniformGeometryOffset, &m_geometryOffset, 1);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ m_vao->release();
+ }
+}
+
+void SceneCameraGlWidget::resizeGL(int, int)
+{
+ // We need to update immediately to avoid flicker
+ update();
+}
+
+void SceneCameraGlWidget::cleanup()
+{
+ makeCurrent();
+
+ delete m_program;
+ delete m_vertexBuffer;
+ delete m_uvBuffer;
+ delete m_vao;
+ m_program = nullptr;
+ m_vertexBuffer = nullptr;
+ m_uvBuffer = nullptr;
+ m_vao = nullptr;
+ m_uniformTextureOffset = 0;
+ m_uniformGeometryOffset = 0;
+ m_textureOffset = defaultTextureOffset;
+ m_geometryOffset = defaultGeometryOffset;
+
+ doneCurrent();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h
new file mode 100644
index 00000000..93becf19
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SCENE_CAMERA_GLWIDGET_H
+#define SCENE_CAMERA_GLWIDGET_H
+
+#include <QtWidgets/qopenglwidget.h>
+#include <QtGui/qopenglfunctions.h>
+#include <QtGui/qvector2d.h>
+#include <QtGui/qvector4d.h>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+QT_FORWARD_DECLARE_CLASS(QOpenGLBuffer)
+QT_FORWARD_DECLARE_CLASS(QOpenGLVertexArrayObject)
+
+class SceneCameraGlWidget : public QOpenGLWidget, QOpenGLFunctions
+{
+ Q_OBJECT
+public:
+ explicit SceneCameraGlWidget(QWidget *parent = nullptr);
+ ~SceneCameraGlWidget();
+
+ void setTextureOffset(const QVector4D &offset) { m_textureOffset = offset; }
+ void setGeometryOffset(const QVector4D &offset) { m_geometryOffset = offset; }
+
+protected:
+ void initializeGL() override;
+ void paintGL() override;
+ void resizeGL(int, int) override;
+
+private:
+ void cleanup();
+
+ QOpenGLShaderProgram *m_program = nullptr;
+ QOpenGLBuffer *m_vertexBuffer = nullptr;
+ QOpenGLBuffer *m_uvBuffer = nullptr;
+ QOpenGLVertexArrayObject *m_vao = nullptr;
+ GLint m_uniformTextureOffset = 0;
+ GLint m_uniformGeometryOffset = 0;
+ QVector4D m_textureOffset;
+ QVector4D m_geometryOffset;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp
new file mode 100644
index 00000000..035e242c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "scenecamerascrollarea.h"
+#include "scenecameraglwidget.h"
+#include "Core.h"
+
+#include <QtWidgets/qscrollbar.h>
+#include <QtGui/qevent.h>
+
+SceneCameraScrollArea::SceneCameraScrollArea(QWidget *parent)
+ : QAbstractScrollArea(parent)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ m_glWidget = new SceneCameraGlWidget(this);
+}
+
+SceneCameraScrollArea::~SceneCameraScrollArea()
+{
+}
+
+void SceneCameraScrollArea::setZoom(qreal zoom, const QPoint &zoomPoint)
+{
+ // Calculate the actual presentation point
+ qreal oldH = (horizontalScrollBar()->value() + zoomPoint.x()) / m_zoom;
+ qreal oldV = (verticalScrollBar()->value() + zoomPoint.y()) / m_zoom;
+
+ m_zoom = zoom;
+
+ recalculateScrollRanges();
+
+ // Move the scrollbars so that the actual presentation point stays in the same location
+ horizontalScrollBar()->setValue(qRound(oldH * m_zoom - zoomPoint.x()));
+ verticalScrollBar()->setValue(qRound(oldV * m_zoom - zoomPoint.y()));
+
+ recalculateOffsets();
+
+ Q_EMIT needUpdate();
+}
+
+void SceneCameraScrollArea::setPresentationSize(const QSize &size)
+{
+ if (m_presentationSize != size) {
+ m_presentationSize = size;
+ recalculateScrollRanges();
+ recalculateOffsets();
+ }
+}
+
+void SceneCameraScrollArea::recalculateScrollRanges()
+{
+ const QSizeF presSize = zoomedPresentationSize();
+
+ const QSize viewSize = viewport()->size();
+ horizontalScrollBar()->setRange(0, int(presSize.width() - viewSize.width()));
+ verticalScrollBar()->setRange(0, int(presSize.height() - viewSize.height()));
+ horizontalScrollBar()->setPageStep(viewSize.width());
+ verticalScrollBar()->setPageStep(viewSize.height());
+}
+
+void SceneCameraScrollArea::recalculateOffsets()
+{
+ // Texture offset vector contains normalized rect of the viewable area of the texture
+ const QSize viewSize = viewport()->size();
+ const qreal fullWidth = qreal(horizontalScrollBar()->maximum() + viewSize.width());
+ const qreal fullHeight = qreal(verticalScrollBar()->maximum() + viewSize.height());
+ QVector4D textureOffset(
+ float(horizontalScrollBar()->value() / fullWidth),
+ float((verticalScrollBar()->maximum() - verticalScrollBar()->value()) / fullHeight),
+ float(viewSize.width() / fullWidth), float(viewSize.height() / fullHeight));
+
+ m_glWidget->setTextureOffset(textureOffset);
+
+ // The geometry offset is adjusted to keep aspect ratio when view area is larger than
+ // zoomed width/height. Since the geometry of the quad is in range [-1, 1], the width/height of
+ // the offset is just a direct multiplier to the coordinate.
+ // XY contain the subpixel offset to ensure we don't get artifacts depending on pixel alignment.
+ const QSizeF presSize = zoomedPresentationSize();
+ float subPixelX = 0.0f;
+ float subPixelY = 0.0f;
+ qreal normWidth = 1.0;
+ qreal normHeight = 1.0;
+ if (presSize.width() < fullWidth) {
+ qreal diffX = (fullWidth - qRound(presSize.width())) / 2.0;
+ subPixelX = float((diffX - qRound(diffX)) / fullWidth);
+ normWidth = presSize.width() / fullWidth;
+ }
+ if (presSize.height() < fullHeight) {
+ qreal diffY = (fullHeight - qRound(presSize.height())) / 2.0;
+ subPixelY = float((diffY - qRound(diffY)) / fullHeight);
+ normHeight = presSize.height() / fullHeight;
+ }
+
+ QVector4D geometryOffset(subPixelX, subPixelY, float(normWidth), float(normHeight));
+ m_glWidget->setGeometryOffset(geometryOffset);
+}
+
+void SceneCameraScrollArea::scrollContentsBy(int, int)
+{
+ recalculateOffsets();
+ Q_EMIT needUpdate();
+}
+
+void SceneCameraScrollArea::showEvent(QShowEvent *event)
+{
+ QAbstractScrollArea::showEvent(event);
+
+ recalculateScrollRanges();
+ recalculateOffsets();
+ resizeGlWidget();
+}
+
+void SceneCameraScrollArea::resizeGlWidget()
+{
+ m_glWidget->resize(viewport()->size());
+}
+
+QSizeF SceneCameraScrollArea::zoomedPresentationSize()
+{
+ // Multiply QSize components separately to avoid rounding to integers
+ QSizeF size = QSizeF(m_presentationSize.width() * m_zoom,
+ m_presentationSize.height() * m_zoom);
+ return size;
+}
+
+void SceneCameraScrollArea::resizeEvent(QResizeEvent *event)
+{
+ QAbstractScrollArea::resizeEvent(event);
+
+ recalculateScrollRanges();
+ recalculateOffsets();
+ resizeGlWidget();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h
new file mode 100644
index 00000000..682dc430
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SCENE_CAMERA_SCROLL_AREA
+#define SCENE_CAMERA_SCROLL_AREA
+
+#include <QtWidgets/qabstractscrollarea.h>
+
+class SceneCameraGlWidget;
+
+class SceneCameraScrollArea : public QAbstractScrollArea
+{
+ Q_OBJECT
+
+public:
+ SceneCameraScrollArea(QWidget *parent = nullptr);
+ virtual ~SceneCameraScrollArea();
+
+ SceneCameraGlWidget *glWidget() const { return m_glWidget; }
+
+ void setZoom(qreal zoom, const QPoint &zoomPoint);
+ void setPresentationSize(const QSize &size);
+ void recalculateScrollRanges();
+ void recalculateOffsets();
+
+Q_SIGNALS:
+ void needUpdate();
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+ void scrollContentsBy(int, int) override;
+ void showEvent(QShowEvent *event) override;
+
+private:
+ void resizeGlWidget();
+ QSizeF zoomedPresentationSize();
+
+protected:
+ SceneCameraGlWidget *m_glWidget = nullptr;
+ qreal m_zoom = 1.0;
+ QSize m_presentationSize;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp
new file mode 100644
index 00000000..e153c0ad
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "scenecameraview.h"
+#include "ui_scenecameraview.h"
+#include "scenecameraglwidget.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "StudioProjectSettings.h"
+#include "MainFrm.h"
+#include "PlayerWnd.h"
+#include "MouseCursor.h"
+#include "ResourceCache.h"
+
+#include <QtGui/qevent.h>
+#include <QtWidgets/qscrollbar.h>
+
+const QPoint invalidMousePoint = QPoint(-999999, -999999);
+
+SceneCameraView::SceneCameraView(CMainFrame *mainFrame, QWidget *parent) :
+ QWidget(parent)
+ , m_ui(new Ui::SceneCameraView)
+ , m_mousePressPointLeft(invalidMousePoint)
+ , m_mousePressPointRight(invalidMousePoint)
+{
+ m_ui->setupUi(this);
+
+ m_cursorPan = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_PAN);
+ m_cursorZoom = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_ZOOM);
+
+ // Limit the preview framerate a bit to limit amount of updates when dragging the slider
+ m_updateTimer.setInterval(0);
+ m_updateTimer.setSingleShot(true);
+
+ connect(m_ui->zoomSlider, &QSlider::valueChanged,
+ this, &SceneCameraView::handleSliderValueChange);
+ connect(mainFrame->GetPlayerWnd(), &CPlayerWnd::newFrame,
+ this, &SceneCameraView::requestUpdate);
+ connect(m_ui->scrollArea, &SceneCameraScrollArea::needUpdate,
+ this, &SceneCameraView::requestUpdate);
+ connect(&m_updateTimer, &QTimer::timeout, this, &SceneCameraView::doUpdate);
+}
+
+SceneCameraView::~SceneCameraView()
+{
+ delete m_ui;
+}
+
+void SceneCameraView::wheelEvent(QWheelEvent *e)
+{
+ m_zoomPoint = m_ui->scrollArea->viewport()->mapFrom(this, e->pos());
+ int currentZoomValue = m_ui->zoomSlider->value();
+ // Adjust amount of change based on zoom level
+ int divider = qMin(120, 1000 / currentZoomValue);
+ m_ui->zoomSlider->setValue(currentZoomValue + (e->angleDelta().y() / divider));
+}
+
+void SceneCameraView::resizeEvent(QResizeEvent *e)
+{
+ m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center();
+
+ QWidget::resizeEvent(e);
+}
+
+void SceneCameraView::mousePressEvent(QMouseEvent *e)
+{
+ // Ignore panning starting outside scrollarea
+ if (!m_ui->scrollArea->rect().contains(e->pos()))
+ return;
+
+ // Panning can be done with left or middle button. Left is more natural and we don't need it
+ // for selection. Alt+middle pans in edit camera mode, so middle button is also supported for
+ // panning.
+ if (m_mousePressPointRight == invalidMousePoint
+ && (e->button() == Qt::LeftButton || e->button() == Qt::MidButton)) {
+ m_mousePressPointLeft = e->pos();
+ m_mousePressScrollValues = QPoint(m_ui->scrollArea->horizontalScrollBar()->value(),
+ m_ui->scrollArea->verticalScrollBar()->value());
+ setCursor(m_cursorPan);
+ } else if (m_mousePressPointLeft == invalidMousePoint && e->button() == Qt::RightButton) {
+ m_mousePressPointRight = e->pos();
+ m_mousePressZoomValue = m_ui->zoomSlider->value();
+ setCursor(m_cursorZoom);
+ }
+}
+
+void SceneCameraView::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_mousePressPointLeft != invalidMousePoint) {
+ const QPoint delta = e->pos() - m_mousePressPointLeft;
+ m_ui->scrollArea->horizontalScrollBar()->setValue(m_mousePressScrollValues.x() - delta.x());
+ m_ui->scrollArea->verticalScrollBar()->setValue(m_mousePressScrollValues.y() - delta.y());
+ }
+ if (m_mousePressPointRight != invalidMousePoint) {
+ const qreal delta = qreal(e->pos().y() - m_mousePressPointRight.y());
+ m_zoomPoint = m_mousePressPointRight;
+ m_ui->zoomSlider->setValue(m_mousePressZoomValue - delta / 2.0);
+ }
+}
+
+void SceneCameraView::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (m_mousePressPointLeft != invalidMousePoint
+ && e->button() == Qt::LeftButton || e->button() == Qt::MidButton) {
+ m_mousePressPointLeft = invalidMousePoint;
+ setCursor(Qt::ArrowCursor);
+ } else if (m_mousePressPointRight != invalidMousePoint) {
+ m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center();
+ m_mousePressPointRight = invalidMousePoint;
+ m_mousePressZoomValue = 0;
+ setCursor(Qt::ArrowCursor);
+ }
+}
+
+void SceneCameraView::handleSliderValueChange()
+{
+ const qreal zoom = qreal(m_ui->zoomSlider->value()) / 10.0;
+ QString valueString = QString::number(zoom, 'f', 1);
+ m_ui->slideValueLabel->setText(tr("%1x").arg(valueString));
+ m_ui->scrollArea->setZoom(zoom, m_zoomPoint);
+ m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center();
+}
+
+void SceneCameraView::doUpdate()
+{
+ // There is no event for presentation size change, so update every frame to catch the change
+ m_ui->scrollArea->setPresentationSize(
+ g_StudioApp.GetCore()->GetStudioProjectSettings()->getPresentationSize());
+ m_ui->scrollArea->glWidget()->update();
+}
+
+void SceneCameraView::requestUpdate()
+{
+ if (!m_updateTimer.isActive())
+ m_updateTimer.start();
+}
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h
new file mode 100644
index 00000000..e2bfb05b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SCENECAMERAVIEW_H
+#define SCENECAMERAVIEW_H
+
+#include <QtWidgets/qwidget.h>
+#include <QtCore/qtimer.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class SceneCameraView;
+}
+QT_END_NAMESPACE
+
+class CMainFrame;
+
+class SceneCameraView : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit SceneCameraView(CMainFrame *mainFrame, QWidget *parent = 0);
+ ~SceneCameraView();
+
+protected:
+ void wheelEvent(QWheelEvent *e) override;
+ void resizeEvent(QResizeEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+
+private:
+ void handleSliderValueChange();
+ void doUpdate();
+ void requestUpdate();
+
+ Ui::SceneCameraView *m_ui = nullptr;
+
+ QTimer m_updateTimer;
+ QPoint m_zoomPoint;
+ QPoint m_mousePressPointLeft;
+ QPoint m_mousePressPointRight;
+ QPoint m_mousePressScrollValues;
+ int m_mousePressZoomValue = 0;
+
+ QCursor m_cursorPan;
+ QCursor m_cursorZoom;
+};
+
+#endif // SCENECAMERAVIEW_H
diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui
new file mode 100644
index 00000000..01aefa72
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SceneCameraView</class>
+ <widget class="QWidget" name="SceneCameraView">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="SceneCameraScrollArea" name="scrollArea" native="true"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSlider" name="zoomSlider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>100</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>300</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="singleStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="slideValueLabel">
+ <property name="text">
+ <string>1.0</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>1</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SceneCameraScrollArea</class>
+ <extends>QWidget</extends>
+ <header>scenecamerascrollarea.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>