summaryrefslogtreecommitdiffstats
path: root/src/Authoring/Qt3DStudio
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2019-06-11 10:55:38 +0300
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2019-06-11 11:03:14 +0300
commit69d7689f29fd5a680884ff688d366ce7080d52a9 (patch)
tree9e9eebe7e9989410e84e7e006bbde062e469f9d7 /src/Authoring/Qt3DStudio
parent32edde39e5b0e4fa15eda28b7b69c1f713b27850 (diff)
parentc2c0a92cb2649d1b6709c08d689bcf68f6d488c2 (diff)
Merge branch '2.4'
Diffstat (limited to 'src/Authoring/Qt3DStudio')
-rw-r--r--src/Authoring/Qt3DStudio/Application/AboutDlg.cpp179
-rw-r--r--src/Authoring/Qt3DStudio/Application/AboutDlg.h70
-rw-r--r--src/Authoring/Qt3DStudio/Application/AboutDlg.ui251
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputDlg.cpp433
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputDlg.h129
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputDlg.ui430
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputListDlg.cpp796
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputListDlg.h120
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputListDlg.ui346
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputSelectModel.cpp95
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputSelectModel.h65
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputSelectView.cpp238
-rw-r--r--src/Authoring/Qt3DStudio/Application/DataInputSelectView.h98
-rw-r--r--src/Authoring/Qt3DStudio/Application/DurationEditDlg.cpp194
-rw-r--r--src/Authoring/Qt3DStudio/Application/DurationEditDlg.h81
-rw-r--r--src/Authoring/Qt3DStudio/Application/DurationEditDlg.ui378
-rw-r--r--src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.cpp110
-rw-r--r--src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.h68
-rw-r--r--src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.qml189
-rw-r--r--src/Authoring/Qt3DStudio/Application/FilterVariantsModel.cpp169
-rw-r--r--src/Authoring/Qt3DStudio/Application/FilterVariantsModel.h77
-rw-r--r--src/Authoring/Qt3DStudio/Application/MsgRouter.cpp129
-rw-r--r--src/Authoring/Qt3DStudio/Application/MsgRouter.h126
-rw-r--r--src/Authoring/Qt3DStudio/Application/PresentationFile.cpp646
-rw-r--r--src/Authoring/Qt3DStudio/Application/PresentationFile.h64
-rw-r--r--src/Authoring/Qt3DStudio/Application/ProjectFile.cpp1549
-rw-r--r--src/Authoring/Qt3DStudio/Application/ProjectFile.h123
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioApp.cpp2239
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioApp.h276
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioConst.h44
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioDefs.h37
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.cpp69
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.h60
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.cpp117
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.h79
-rw-r--r--src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.ui390
-rw-r--r--src/Authoring/Qt3DStudio/Application/TimeEditDlg.cpp203
-rw-r--r--src/Authoring/Qt3DStudio/Application/TimeEditDlg.h79
-rw-r--r--src/Authoring/Qt3DStudio/Application/TimeEditDlg.ui349
-rw-r--r--src/Authoring/Qt3DStudio/Application/TimeEnums.h49
-rw-r--r--src/Authoring/Qt3DStudio/Controls/AppFonts.cpp76
-rw-r--r--src/Authoring/Qt3DStudio/Controls/AppFonts.h61
-rw-r--r--src/Authoring/Qt3DStudio/Controls/BufferedRenderer.cpp214
-rw-r--r--src/Authoring/Qt3DStudio/Controls/BufferedRenderer.h60
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ClickableLabel.cpp51
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ClickableLabel.h48
-rw-r--r--src/Authoring/Qt3DStudio/Controls/Control.cpp1738
-rw-r--r--src/Authoring/Qt3DStudio/Controls/Control.h259
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ControlData.cpp765
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ControlData.h273
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ControlGraph.cpp188
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ControlGraph.h68
-rw-r--r--src/Authoring/Qt3DStudio/Controls/ControlGraphIterators.h156
-rw-r--r--src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.cpp58
-rw-r--r--src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.h61
-rw-r--r--src/Authoring/Qt3DStudio/Controls/Renderer.cpp74
-rw-r--r--src/Authoring/Qt3DStudio/Controls/Renderer.h105
-rw-r--r--src/Authoring/Qt3DStudio/Controls/WidgetControl.cpp243
-rw-r--r--src/Authoring/Qt3DStudio/Controls/WidgetControl.h83
-rw-r--r--src/Authoring/Qt3DStudio/Controls/WinRenderer.cpp518
-rw-r--r--src/Authoring/Qt3DStudio/Controls/WinRenderer.h131
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.cpp261
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.h64
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.cpp96
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.h81
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.cpp324
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.h82
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropSource.cpp90
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropSource.h100
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.cpp53
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.h67
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.cpp111
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.h70
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.cpp322
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.h64
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.cpp285
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.h54
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.cpp158
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.h66
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.cpp223
-rw-r--r--src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.h88
-rw-r--r--src/Authoring/Qt3DStudio/Info.plist79
-rw-r--r--src/Authoring/Qt3DStudio/MainFrm.cpp1968
-rw-r--r--src/Authoring/Qt3DStudio/MainFrm.h269
-rw-r--r--src/Authoring/Qt3DStudio/MainFrm.qrc8
-rw-r--r--src/Authoring/Qt3DStudio/MainFrm.ui1115
-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
-rw-r--r--src/Authoring/Qt3DStudio/PreviewHelper.cpp243
-rw-r--r--src/Authoring/Qt3DStudio/PreviewHelper.h87
-rw-r--r--src/Authoring/Qt3DStudio/Qt3DStudio.pro530
-rw-r--r--src/Authoring/Qt3DStudio/Render/IStudioRenderer.h97
-rw-r--r--src/Authoring/Qt3DStudio/Render/PathWidget.cpp512
-rw-r--r--src/Authoring/Qt3DStudio/Render/PathWidget.h49
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioGradientWidget.cpp200
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioGradientWidget.h89
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioPickValues.h217
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioRenderer.cpp1128
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioRendererImpl.h132
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.cpp4083
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.h714
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioRotationWidget.cpp436
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioScaleWidget.cpp266
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.cpp377
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.h124
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioTranslationWidget.cpp173
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.cpp730
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.h107
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioWidget.cpp321
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioWidget.h139
-rw-r--r--src/Authoring/Qt3DStudio/Render/StudioWidgetImpl.h346
-rw-r--r--src/Authoring/Qt3DStudio/Render/WGLRenderContext.cpp243
-rw-r--r--src/Authoring/Qt3DStudio/Render/WGLRenderContext.h105
-rw-r--r--src/Authoring/Qt3DStudio/UI/EditCameraBar.cpp146
-rw-r--r--src/Authoring/Qt3DStudio/UI/EditCameraBar.h61
-rw-r--r--src/Authoring/Qt3DStudio/UI/EditorPane.cpp83
-rw-r--r--src/Authoring/Qt3DStudio/UI/EditorPane.h65
-rw-r--r--src/Authoring/Qt3DStudio/UI/GLVersionDlg.cpp65
-rw-r--r--src/Authoring/Qt3DStudio/UI/GLVersionDlg.h72
-rw-r--r--src/Authoring/Qt3DStudio/UI/GLVersionDlg.ui100
-rw-r--r--src/Authoring/Qt3DStudio/UI/InterpolationDlg.cpp74
-rw-r--r--src/Authoring/Qt3DStudio/UI/InterpolationDlg.h68
-rw-r--r--src/Authoring/Qt3DStudio/UI/InterpolationDlg.ui311
-rw-r--r--src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.cpp313
-rw-r--r--src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.h91
-rw-r--r--src/Authoring/Qt3DStudio/UI/PlayerWnd.cpp266
-rw-r--r--src/Authoring/Qt3DStudio/UI/PlayerWnd.h88
-rw-r--r--src/Authoring/Qt3DStudio/UI/RecentItems.cpp122
-rw-r--r--src/Authoring/Qt3DStudio/UI/RecentItems.h70
-rw-r--r--src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.cpp54
-rw-r--r--src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.h53
-rw-r--r--src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.ui90
-rw-r--r--src/Authoring/Qt3DStudio/UI/SceneView.cpp362
-rw-r--r--src/Authoring/Qt3DStudio/UI/SceneView.h105
-rw-r--r--src/Authoring/Qt3DStudio/UI/StartupDlg.cpp179
-rw-r--r--src/Authoring/Qt3DStudio/UI/StartupDlg.h115
-rw-r--r--src/Authoring/Qt3DStudio/UI/StartupDlg.ui376
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.cpp512
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.h99
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.ui290
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.cpp137
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.h89
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.ui105
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.cpp252
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.h81
-rw-r--r--src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.ui154
-rw-r--r--src/Authoring/Qt3DStudio/Utils/ITickTock.h82
-rw-r--r--src/Authoring/Qt3DStudio/Utils/ImportUtils.cpp102
-rw-r--r--src/Authoring/Qt3DStudio/Utils/ImportUtils.h72
-rw-r--r--src/Authoring/Qt3DStudio/Utils/MouseCursor.cpp188
-rw-r--r--src/Authoring/Qt3DStudio/Utils/MouseCursor.h91
-rw-r--r--src/Authoring/Qt3DStudio/Utils/QmlUtils.cpp46
-rw-r--r--src/Authoring/Qt3DStudio/Utils/QmlUtils.h46
-rw-r--r--src/Authoring/Qt3DStudio/Utils/ResourceCache.cpp137
-rw-r--r--src/Authoring/Qt3DStudio/Utils/ResourceCache.h64
-rw-r--r--src/Authoring/Qt3DStudio/Utils/StudioUtils.cpp133
-rw-r--r--src/Authoring/Qt3DStudio/Utils/StudioUtils.h53
-rw-r--r--src/Authoring/Qt3DStudio/Utils/TickTock.cpp313
-rw-r--r--src/Authoring/Qt3DStudio/Workspace/Dialogs.cpp1562
-rw-r--r--src/Authoring/Qt3DStudio/Workspace/Dialogs.h202
-rw-r--r--src/Authoring/Qt3DStudio/Workspace/Views.cpp89
-rw-r--r--src/Authoring/Qt3DStudio/Workspace/Views/Views.h59
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/DESCRIPTION.en_us.html22
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/METADATA.pb107
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/OFL.txt93
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Black.ttfbin0 -> 49356 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Bold.ttfbin0 -> 59908 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-BoldItalic.ttfbin0 -> 69796 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLight.ttfbin0 -> 63124 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLightItalic.ttfbin0 -> 67788 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Italic.ttfbin0 -> 72416 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Light.ttfbin0 -> 64032 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-LightItalic.ttfbin0 -> 71720 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Regular.ttfbin0 -> 63752 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBold.ttfbin0 -> 63044 bytes
-rw-r--r--src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBoldItalic.ttfbin0 -> 71812 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images.qrc337
-rw-r--r--src/Authoring/Qt3DStudio/images/3D-studio.icobin0 -> 119716 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/About Icon.bmpbin0 -> 297132 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Action.pngbin0 -> 2927 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Action@2x.pngbin0 -> 599 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ChildAction.pngbin0 -> 2836 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ChildAction@2x.pngbin0 -> 341 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ChildMasterAction.pngbin0 -> 2840 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ChildMasterAction@2x.pngbin0 -> 344 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ComponentAction.pngbin0 -> 2836 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ComponentAction@2x.pngbin0 -> 325 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction.pngbin0 -> 2844 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction@2x.pngbin0 -> 314 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-MasterAction.pngbin0 -> 2911 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-MasterAction@2x.pngbin0 -> 639 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Trash-Disabled.pngbin0 -> 533 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Trash-Disabled@2x.pngbin0 -> 656 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Trash-Normal.pngbin0 -> 541 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Action-Trash-Normal@2x.pngbin0 -> 660 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Alias-Normal.pngbin0 -> 284 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Alias-Normal@2x.pngbin0 -> 545 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Camera-Normal.pngbin0 -> 347 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Camera-Normal@2x.pngbin0 -> 547 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Camera-Pick.pngbin0 -> 657 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Component-Normal.pngbin0 -> 402 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Component-Normal@2x.pngbin0 -> 797 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cone-Normal.pngbin0 -> 517 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cone-Normal@2x.pngbin0 -> 1001 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cube-Normal.pngbin0 -> 550 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cube-Normal@2x.pngbin0 -> 885 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal.pngbin0 -> 508 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal@2x.pngbin0 -> 938 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Group-Normal.pngbin0 -> 606 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Group-Normal@2x.pngbin0 -> 1206 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Layer-Normal.pngbin0 -> 181 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Layer-Normal@2x.pngbin0 -> 280 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Light-Normal.pngbin0 -> 517 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Light-Normal@2x.pngbin0 -> 923 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Light-Pick.pngbin0 -> 977 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal.pngbin0 -> 248 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal@2x.pngbin0 -> 345 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal.pngbin0 -> 421 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal@2x.pngbin0 -> 672 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Text-Normal.pngbin0 -> 151 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-Text-Normal@2x.pngbin0 -> 188 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-import-Normal.pngbin0 -> 551 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Asset-import-Normal@2x.pngbin0 -> 664 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Assets-Model.pngbin0 -> 299 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Assets-Model@2x.pngbin0 -> 461 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Left.pngbin0 -> 986 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Left@2x.pngbin0 -> 314 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Rearrange-Left.pngbin0 -> 196 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Rearrange-Right.pngbin0 -> 196 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Right.pngbin0 -> 990 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Insert-Right@2x.pngbin0 -> 324 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active.pngbin0 -> 641 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active@2x.pngbin0 -> 972 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal.pngbin0 -> 637 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal@2x.pngbin0 -> 978 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled.pngbin0 -> 2946 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled@2x.pngbin0 -> 606 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal.pngbin0 -> 2937 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal@2x.pngbin0 -> 578 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected.pngbin0 -> 2947 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected@2x.pngbin0 -> 498 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled.pngbin0 -> 263 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled@2x.pngbin0 -> 580 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal.pngbin0 -> 2959 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal@2x.pngbin0 -> 578 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected.pngbin0 -> 2949 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected@2x.pngbin0 -> 561 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled.pngbin0 -> 2949 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled@2x.pngbin0 -> 435 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal.pngbin0 -> 2949 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal@2x.pngbin0 -> 428 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected.pngbin0 -> 2951 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected@2x.pngbin0 -> 424 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled.pngbin0 -> 291 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled@2x.pngbin0 -> 428 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal.pngbin0 -> 2949 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal@2x.pngbin0 -> 427 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected.pngbin0 -> 2947 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected@2x.pngbin0 -> 424 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled.pngbin0 -> 555 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled@2x.pngbin0 -> 621 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Alias-Normal.pngbin0 -> 241 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Alias-Normal@2x.pngbin0 -> 391 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled.pngbin0 -> 466 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled@2x.pngbin0 -> 507 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal.pngbin0 -> 152 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal@2x.pngbin0 -> 179 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled.pngbin0 -> 542 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled@2x.pngbin0 -> 653 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Camera-Normal.pngbin0 -> 259 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Camera-Normal@2x.pngbin0 -> 418 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Component-Disabled.pngbin0 -> 583 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Component-Disabled@2x.pngbin0 -> 726 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Component-Normal.pngbin0 -> 266 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Component-Normal@2x.pngbin0 -> 460 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-Active.pngbin0 -> 610 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-Active@2x.pngbin0 -> 817 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive.pngbin0 -> 626 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive@2x.pngbin0 -> 796 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-White.pngbin0 -> 590 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-DataInput-White@2x.pngbin0 -> 740 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled.pngbin0 -> 631 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled@2x.pngbin0 -> 869 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Effect-Normal.pngbin0 -> 381 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Effect-Normal@2x.pngbin0 -> 709 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled.pngbin0 -> 468 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled@2x.pngbin0 -> 499 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Folder-Normal.pngbin0 -> 469 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Folder-Normal@2x.pngbin0 -> 498 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Group-Disabled.pngbin0 -> 569 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Group-Disabled@2x.pngbin0 -> 836 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Group-Normal.pngbin0 -> 288 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Group-Normal@2x.pngbin0 -> 595 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Image-Disabled.pngbin0 -> 589 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Image-Disabled@2x.pngbin0 -> 704 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Image-Normal.pngbin0 -> 281 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Image-Normal@2x.pngbin0 -> 457 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled.pngbin0 -> 507 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled@2x.pngbin0 -> 541 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Layer-Normal.pngbin0 -> 151 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Layer-Normal@2x.pngbin0 -> 203 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Light-Disabled.pngbin0 -> 606 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Light-Disabled@2x.pngbin0 -> 803 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Light-Normal.pngbin0 -> 325 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Light-Normal@2x.pngbin0 -> 640 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Material-Disabled.pngbin0 -> 610 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Material-Disabled@2x.pngbin0 -> 768 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Material-Normal.pngbin0 -> 287 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Material-Normal@2x.pngbin0 -> 511 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Model-Disabled.pngbin0 -> 636 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Model-Disabled@2x.pngbin0 -> 832 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Model-Normal.pngbin0 -> 336 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Model-Normal@2x.pngbin0 -> 545 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Property-Disabled.pngbin0 -> 480 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Property-Disabled@2x.pngbin0 -> 518 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Property-Normal.pngbin0 -> 481 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Property-Normal@2x.pngbin0 -> 515 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled.pngbin0 -> 489 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled@2x.pngbin0 -> 533 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Scene-Normal.pngbin0 -> 199 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Scene-Normal@2x.pngbin0 -> 282 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Text-Disabled.pngbin0 -> 470 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Text-Disabled@2x.pngbin0 -> 495 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Text-Normal.pngbin0 -> 151 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-Text-Normal@2x.pngbin0 -> 188 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-edit-disabled.pngbin0 -> 577 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-edit-disabled@2x.pngbin0 -> 760 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-edit-normal.pngbin0 -> 604 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-edit-normal@2x.pngbin0 -> 803 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal.pngbin0 -> 620 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal@2x.pngbin0 -> 936 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/PlaybackHead.pngbin0 -> 233 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/PlaybackHead@2x.pngbin0 -> 318 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Active.pngbin0 -> 1562 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Active@2x.pngbin0 -> 2663 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Master-Active.pngbin0 -> 1471 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Master-Active@2x.pngbin0 -> 4190 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Normal.pngbin0 -> 1461 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Slide-Normal@2x.pngbin0 -> 2534 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Empty.pngbin0 -> 2855 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Empty@2x.pngbin0 -> 286 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled.pngbin0 -> 238 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled@2x.pngbin0 -> 371 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShow.pngbin0 -> 248 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShow@2x.pngbin0 -> 454 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled.pngbin0 -> 489 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled@2x.pngbin0 -> 523 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Lock.pngbin0 -> 224 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Lock@2x.pngbin0 -> 356 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Shy.pngbin0 -> 289 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toggle-Shy@2x.pngbin0 -> 485 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-00.pngbin0 -> 488 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-00@2x.pngbin0 -> 522 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-01.pngbin0 -> 583 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-01@2x.pngbin0 -> 678 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-02.pngbin0 -> 543 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-02@2x.pngbin0 -> 586 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-03.pngbin0 -> 746 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-03@2x.pngbin0 -> 1231 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-04.pngbin0 -> 669 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-04@2x.pngbin0 -> 874 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-05.pngbin0 -> 617 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-05@2x.pngbin0 -> 782 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-06.pngbin0 -> 642 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-06@2x.pngbin0 -> 929 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-07.pngbin0 -> 676 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/Toolbar-07@2x.pngbin0 -> 916 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/add-disabled.pngbin0 -> 470 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/add-disabled@2x.pngbin0 -> 528 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/add.pngbin0 -> 454 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/add@2x.pngbin0 -> 515 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/anim_progress.pngbin0 -> 1939 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/anim_progress@2x.pngbin0 -> 3674 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow.pngbin0 -> 523 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow@2x.pngbin0 -> 600 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow_down.pngbin0 -> 533 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow_down@2x.pngbin0 -> 610 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow_up.pngbin0 -> 275 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/arrow_up@2x.pngbin0 -> 367 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_button.pngbin0 -> 539 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_button@2x.pngbin0 -> 697 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button.pngbin0 -> 467 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button@2x.pngbin0 -> 554 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button.pngbin0 -> 546 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button@2x.pngbin0 -> 684 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_scene.pngbin0 -> 495 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/breadcrumb_component_scene@2x.pngbin0 -> 538 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/checkbox-checked.pngbin0 -> 617 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/checkbox-checked@2x.pngbin0 -> 779 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/checkbox-unchecked.pngbin0 -> 476 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/checkbox-unchecked@2x.pngbin0 -> 507 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-00.pngbin0 -> 302 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-00@2x.pngbin0 -> 465 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-01.pngbin0 -> 231 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-01@2x.pngbin0 -> 324 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-02.pngbin0 -> 311 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-02@2x.pngbin0 -> 554 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-03.pngbin0 -> 149 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-03@2x.pngbin0 -> 190 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-04.pngbin0 -> 349 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-04@2x.pngbin0 -> 848 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-05.pngbin0 -> 359 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-05@2x.pngbin0 -> 671 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-06.pngbin0 -> 162 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/client_tools_hi_color-06@2x.pngbin0 -> 230 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/edit_camera_pan.pngbin0 -> 556 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/edit_camera_rot.pngbin0 -> 567 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/edit_camera_zoom.pngbin0 -> 567 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00.pngbin0 -> 324 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00@2x.pngbin0 -> 623 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled.pngbin0 -> 339 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled@2x.pngbin0 -> 487 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01.pngbin0 -> 395 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01@2x.pngbin0 -> 730 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled.pngbin0 -> 298 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled@2x.pngbin0 -> 468 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02.pngbin0 -> 670 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02@2x.pngbin0 -> 972 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled.pngbin0 -> 672 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled@2x.pngbin0 -> 979 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03.pngbin0 -> 494 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03@2x.pngbin0 -> 966 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled.pngbin0 -> 361 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled@2x.pngbin0 -> 624 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04.pngbin0 -> 341 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04@2x.pngbin0 -> 774 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled.pngbin0 -> 353 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled@2x.pngbin0 -> 605 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05.pngbin0 -> 328 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05@2x.pngbin0 -> 720 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled.pngbin0 -> 367 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled@2x.pngbin0 -> 582 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/empty-pixel.pngbin0 -> 154 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-colored.pngbin0 -> 611 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-colored@2x.pngbin0 -> 791 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-disabled.pngbin0 -> 250 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-disabled@2x.pngbin0 -> 402 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-shy-down.pngbin0 -> 289 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-shy-down@2x.pngbin0 -> 485 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-toggle-eye-down.pngbin0 -> 238 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-toggle-eye-down@2x.pngbin0 -> 371 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-toggle-eye-up.pngbin0 -> 248 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter-toggle-eye-up@2x.pngbin0 -> 454 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter.pngbin0 -> 236 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/filter@2x.pngbin0 -> 369 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/group_move.pngbin0 -> 560 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/group_rotate.pngbin0 -> 570 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/group_scale.pngbin0 -> 540 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/icon_256x256.pngbin0 -> 9054 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/icon_256x256@2x.pngbin0 -> 21603 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/initial_notUsed.pngbin0 -> 670 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/initial_notUsed@2x.pngbin0 -> 857 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/initial_used.pngbin0 -> 655 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/initial_used@2x.pngbin0 -> 858 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/item_move.pngbin0 -> 562 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/item_rotate.pngbin0 -> 573 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/item_scale.pngbin0 -> 536 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/keyframe-hidden-normal.pngbin0 -> 501 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/keyframe-hidden-normal@2x.pngbin0 -> 226 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/matdef-active.pngbin0 -> 579 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/matdef-active@2x.pngbin0 -> 774 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/matdef-disabled.pngbin0 -> 613 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/matdef-disabled@2x.pngbin0 -> 788 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/obsolete_placeholder.pngbin0 -> 158 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/open_dialog.pngbin0 -> 9204 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/open_dialog@2x.pngbin0 -> 19238 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_first.pngbin0 -> 547 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_first@2x.pngbin0 -> 651 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_last.pngbin0 -> 257 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_last@2x.pngbin0 -> 315 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_play.pngbin0 -> 519 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_play@2x.pngbin0 -> 567 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_stop.pngbin0 -> 437 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/playback_tools_stop@2x.pngbin0 -> 457 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/prefstab-00.pngbin0 -> 449 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/prefstab-00@2x.pngbin0 -> 711 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/prefstab-01.pngbin0 -> 597 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/prefstab-01@2x.pngbin0 -> 832 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_edit.pngbin0 -> 632 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_edit@2x.pngbin0 -> 841 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_notUsed.pngbin0 -> 566 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_notUsed@2x.pngbin0 -> 624 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_used.pngbin0 -> 562 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/presentation_used@2x.pngbin0 -> 624 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-disabled.pngbin0 -> 239 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-disabled@2x.pngbin0 -> 366 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-remote-disabled.pngbin0 -> 316 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-remote-disabled@2x.pngbin0 -> 578 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-remote.pngbin0 -> 331 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-remote@2x.pngbin0 -> 559 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-variants.pngbin0 -> 369 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview-variants@2x.pngbin0 -> 712 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview.pngbin0 -> 243 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/preview@2x.pngbin0 -> 369 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/qml_notUsed.pngbin0 -> 642 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/qml_notUsed@2x.pngbin0 -> 735 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/qml_used.pngbin0 -> 643 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/qml_used@2x.pngbin0 -> 734 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/separator-vertical.pngbin0 -> 214 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/separator-vertical@2x.pngbin0 -> 241 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/separator.pngbin0 -> 522 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/separator@2x.pngbin0 -> 526 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/studio.icnsbin0 -> 133666 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timebarhandle-disabled-left.pngbin0 -> 276 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timebarhandle-disabled-right.pngbin0 -> 283 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timebarhandle-left.pngbin0 -> 298 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timebarhandle-right.pngbin0 -> 297 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timeline_text_hidden.pngbin0 -> 183 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timeline_text_hidden@2x.pngbin0 -> 260 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timeline_text_shown.pngbin0 -> 186 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/timeline_text_shown@2x.pngbin0 -> 262 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/toolbutton-arrow.pngbin0 -> 280 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/warning.pngbin0 -> 809 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/warning2x.pngbin0 -> 1423 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/welcomedialog/laptop.pngbin0 -> 207370 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/zoom_in.pngbin0 -> 454 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/zoom_in@2x.pngbin0 -> 515 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/zoom_out.pngbin0 -> 162 bytes
-rw-r--r--src/Authoring/Qt3DStudio/images/zoom_out@2x.pngbin0 -> 214 bytes
-rw-r--r--src/Authoring/Qt3DStudio/qml.qrc47
-rw-r--r--src/Authoring/Qt3DStudio/remotedeploymentsender.cpp260
-rw-r--r--src/Authoring/Qt3DStudio/remotedeploymentsender.h70
-rw-r--r--src/Authoring/Qt3DStudio/style.qss465
-rw-r--r--src/Authoring/Qt3DStudio/version.h35
754 files changed, 85206 insertions, 0 deletions
diff --git a/src/Authoring/Qt3DStudio/Application/AboutDlg.cpp b/src/Authoring/Qt3DStudio/Application/AboutDlg.cpp
new file mode 100644
index 00000000..818c3556
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/AboutDlg.cpp
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** 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
+//==============================================================================
+#include "StudioDefs.h"
+#include "qtAuthoring-config.h"
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "AboutDlg.h"
+#include "ui_AboutDlg.h"
+#include "StudioPreferences.h"
+
+#include <QtGui/qpalette.h>
+
+#ifdef QT3DSTUDIO_REVISION
+#define STRINGIFY_INTERNAL(x) #x
+#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
+const char *const QT3DSTUDIO_REVISION_STR = STRINGIFY(QT3DSTUDIO_REVISION);
+#endif
+
+//==============================================================================
+/**
+ * Constructor: Initializes the object.
+ */
+CAboutDlg::CAboutDlg(QWidget* parent)
+ : QDialog(parent, Qt::MSWindowsFixedSizeDialogHint)
+ , m_ui(new Ui::AboutDlg)
+ , m_palette(nullptr)
+{
+ m_ui->setupUi(this);
+ OnInitDialog();
+}
+
+//==============================================================================
+/**
+ * Destructor: Releases the object.
+ */
+CAboutDlg::~CAboutDlg()
+{
+ delete m_palette;
+}
+
+void CAboutDlg::paintEvent(QPaintEvent* event)
+{
+ Q_UNUSED(event)
+ if (m_palette)
+ return;
+
+ m_palette = new QPalette;
+ QPixmap pic = QPixmap(":/startup/open_dialog.png");
+ m_palette->setBrush(QPalette::Window, pic);
+ setPalette(*m_palette);
+ resize(pic.size());
+ setFixedSize(size());
+}
+
+static QString compilerString()
+{
+#if defined(Q_CC_CLANG) // must be before GNU, because clang claims to be GNU too
+ QString isAppleString;
+#if defined(__apple_build_version__) // Apple clang has other version numbers
+ isAppleString = QStringLiteral(" (Apple)");
+#endif
+ return QStringLiteral("Clang " ) + QString::number(__clang_major__) + QStringLiteral(".")
+ + QString::number(__clang_minor__) + isAppleString;
+#elif defined(Q_CC_GNU)
+ return QStringLiteral("GCC " ) + QStringLiteral(__VERSION__);
+#elif defined(Q_CC_MSVC)
+ if (_MSC_VER > 1999)
+ return QStringLiteral("MSVC <unknown>");
+ if (_MSC_VER >= 1910)
+ return QStringLiteral("MSVC 2017");
+ if (_MSC_VER >= 1900)
+ return QStringLiteral("MSVC 2015");
+#endif
+ return QStringLiteral("<unknown compiler>");
+}
+
+void CAboutDlg::OnInitDialog()
+{
+ // Set the Studio version
+ m_ProductVersionStr = QStringLiteral("Qt 3D Studio v") + CStudioPreferences::GetVersionString();
+
+ // Set the copyright string
+ m_CopyrightStr = QObject::tr("Copyright (C) %1 The Qt Company. All rights reserved.").arg(
+ QString(STUDIO_COPYRIGHT_YEAR));
+
+ // Set the credit strings
+#ifdef QT_3DSTUDIO_FBX
+ m_Credit1Str = QObject::tr("This software contains Autodesk(R) FBX(R) code developed by "
+ "Autodesk, Inc. Copyright 2014 Autodesk, Inc. All rights, reserved. "
+ "Such code is provided 'as is' and Autodesk, Inc. disclaims any "
+ "and all warranties, whether express or implied, including without "
+ "limitation the implied warranties of merchantability, fitness for "
+ "a particular purpose or non-infringement of third party rights. "
+ "In no event shall Autodesk, Inc. be liable for any direct, "
+ "indirect, incidental, special, exemplary, or consequential "
+ "damages (including, but not limited to, procurement of "
+ "substitute goods or services; loss of use, data, or profits; or "
+ "business interruption) however caused and on any theory of "
+ "liability, whether in contract, strict liability, or tort "
+ "(including negligence or otherwise) arising in any way out of "
+ "such code.");
+#endif
+
+ // Add link to Web site
+ QString theURL(QStringLiteral("https://www.qt.io/3d-studio"));
+
+ m_ui->m_WebSite->setText(QString("<a href=\"%1\"><font color=\"#%2\">%3</font></a>").arg(
+ theURL,
+ CStudioPreferences::GetMasterColor().GetString().toQString(),
+ theURL));
+ m_ui->m_WebSite->setToolTip(tr("Click to visit Qt web site"));
+ m_ui->m_WebSite->setOpenExternalLinks(true);
+
+ // Add link to support address
+ const QString theSupport = QStringLiteral("https://account.qt.io/support");
+
+ m_ui->m_Email->setText(QString("<a href=\"%1\"><font color=\"#%2\">%3</font></a>").arg(
+ theSupport,
+ CStudioPreferences::GetMasterColor().GetString().toQString(),
+ theSupport));
+ m_ui->m_Email->setToolTip(tr("Send a Studio support request to the Qt Company"));
+ m_ui->m_Email->setOpenExternalLinks(true);
+
+ // Make the font bold for version number
+ m_ui->m_ProductVersion->setStyleSheet("font-weight: bold;");
+
+ m_ui->m_ProductVersion->setText(m_ProductVersionStr);
+ m_ui->m_Copyright->setText(m_CopyrightStr);
+ m_ui->m_Credit1->setText(m_Credit1Str);
+
+ // Information about build
+ m_ui->m_buildTimestamp->setText(
+ tr("Built on %1 %2").arg(QStringLiteral(__DATE__), QStringLiteral(__TIME__)));
+ m_ui->m_qtVersion->setText(
+ tr("Based on Qt %1 (%2, %3 bit)").arg(
+ QString::fromLatin1(qVersion()),
+ compilerString(),
+ QString::number(QSysInfo::WordSize)));
+#ifdef QT3DSTUDIO_REVISION
+ m_ui->m_revisionSHA->setText(
+ tr("From revision %1").arg(
+ QString::fromLatin1(QT3DSTUDIO_REVISION_STR).left(10)));
+#else
+ m_ui->m_revisionSHA->setText(QString());
+ m_ui->m_revisionSHA->setMaximumHeight(0);
+#endif
+}
diff --git a/src/Authoring/Qt3DStudio/Application/AboutDlg.h b/src/Authoring/Qt3DStudio/Application/AboutDlg.h
new file mode 100644
index 00000000..f89ff41a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/AboutDlg.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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_ABOUT_DLG_H
+#define INCLUDED_ABOUT_DLG_H 1
+
+#pragma once
+
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class AboutDlg;
+}
+QT_END_NAMESPACE
+
+class CAboutDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ CAboutDlg(QWidget* parent = nullptr);
+ ~CAboutDlg();
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+
+private:
+ void OnInitDialog();
+
+private:
+ QScopedPointer<Ui::AboutDlg> m_ui;
+
+ QPalette *m_palette;
+
+ QString m_ProductVersionStr;
+ QString m_CopyrightStr;
+ QString m_Credit1Str;
+};
+
+#endif // INCLUDED_ABOUT_DLG_H
diff --git a/src/Authoring/Qt3DStudio/Application/AboutDlg.ui b/src/Authoring/Qt3DStudio/Application/AboutDlg.ui
new file mode 100644
index 00000000..c835d61c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/AboutDlg.ui
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutDlg</class>
+ <widget class="QDialog" name="AboutDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>442</width>
+ <height>650</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>About Qt 3D Studio</string>
+ </property>
+ <property name="accessibleName">
+ <string>AboutDlg</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>120</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="m_ProductVersion">
+ <property name="text">
+ <string>&lt;studio version goes here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_7">
+ <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>12</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_qtVersion">
+ <property name="text">
+ <string>&lt;Based on Qt version goes here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_buildTimestamp">
+ <property name="text">
+ <string>&lt;Built on timestamp goes here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_revisionSHA">
+ <property name="text">
+ <string>&lt;From revision SHA goes here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>12</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_Copyright">
+ <property name="text">
+ <string>&lt;Copyright goes here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>6</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_Credit1">
+ <property name="text">
+ <string>&lt;Credit 1 goes here&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>6</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_WebSiteLabel">
+ <property name="text">
+ <string>Web site:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_WebSite">
+ <property name="text">
+ <string>&lt;Link to support site here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>12</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_EmailLabel">
+ <property name="text">
+ <string>Support Center:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_Email">
+ <property name="text">
+ <string>&lt;Link to support email here&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AboutDlg</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>AboutDlg</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/Application/DataInputDlg.cpp b/src/Authoring/Qt3DStudio/Application/DataInputDlg.cpp
new file mode 100644
index 00000000..f6255395
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputDlg.cpp
@@ -0,0 +1,433 @@
+/****************************************************************************
+**
+** 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 "DataInputDlg.h"
+#include "ui_DataInputDlg.h"
+#include "Qt3DSMessageBox.h"
+#include "StudioApp.h"
+#include "Dialogs.h"
+
+#include <QtWidgets/qabstractbutton.h>
+#include <QtWidgets/qpushbutton.h>
+#include <QtGui/qstandarditemmodel.h>
+#include <QtWidgets/qstyleditemdelegate.h>
+
+CDataInputDlg::CDataInputDlg(CDataInputDialogItem **datainput, QStandardItemModel *data,
+ QWidget *parent, const QVector<EDataType> acceptedTypes)
+ : QDialog(parent)
+ , m_ui(new Ui::DataInputDlg)
+ , m_data(data)
+ , m_dataInput(*datainput)
+ , m_name(m_dataInput->name)
+ , m_type(0)
+ , m_min(0.0)
+ , m_max(10.0)
+ , m_acceptedTypes(acceptedTypes)
+{
+ m_ui->setupUi(this);
+
+ // For enabling stylesheet for drop-down items
+ QStyledItemDelegate *itemDelegate = new QStyledItemDelegate();
+ m_ui->comboBoxTypeList->setItemDelegate(itemDelegate);
+
+ m_ui->comboBoxTypeList->addItem(tr("Boolean"), QVariant(DataTypeBoolean));
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ m_ui->comboBoxTypeList->addItem(tr("Evaluator"), QVariant(DataTypeEvaluator));
+#endif
+ m_ui->comboBoxTypeList->addItem(tr("Float"), QVariant(DataTypeFloat));
+ m_ui->comboBoxTypeList->addItem(tr("Ranged Number"), QVariant(DataTypeRangedNumber));
+ m_ui->comboBoxTypeList->addItem(tr("String"), QVariant(DataTypeString));
+ m_ui->comboBoxTypeList->addItem(tr("Variant"), QVariant(DataTypeVariant));
+ m_ui->comboBoxTypeList->addItem(tr("Vector2"), QVariant(DataTypeVector2));
+ m_ui->comboBoxTypeList->addItem(tr("Vector3"), QVariant(DataTypeVector3));
+ m_ui->comboBoxTypeList->addItem(tr("Vector4"), QVariant(DataTypeVector4));
+
+ QStandardItemModel *model
+ = qobject_cast<QStandardItemModel *>(m_ui->comboBoxTypeList->model());
+
+ for (int i = 0; i < m_ui->comboBoxTypeList->model()->rowCount(); ++i)
+ {
+ QStandardItem *item = model->item(i, 0);
+ if (!acceptedTypes.contains((EDataType)i))
+ item->setEnabled(false);
+ }
+
+ const auto keys = m_dataInput->metadata.keys();
+ for (auto &k : keys)
+ m_orderedMetadata.append({k, m_dataInput->metadata.value(k)});
+
+ initDialog();
+
+ window()->setFixedSize(size());
+
+ connect(m_ui->comboBoxTypeList,
+ static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ this, &CDataInputDlg::onTypeChanged);
+ connect(m_ui->doubleSpinBoxMin,
+ static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
+ this, &CDataInputDlg::onMinChanged);
+ connect(m_ui->doubleSpinBoxMax,
+ static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
+ this, &CDataInputDlg::onMaxChanged);
+ connect(m_ui->lineEditInputName, &QLineEdit::textChanged, this, &CDataInputDlg::onNameChanged);
+ // Clunky way of making TAB jump over the delete button column, as setting focusPolicy
+ // does not seem to work with cellWidget.
+ connect(m_ui->tableEditMetadata, &QTableWidget::currentCellChanged,
+ this, [this](int currentRow, int currentColumn, int previousRow, int previousColumn) {
+ Q_UNUSED(previousRow);
+ Q_UNUSED(previousColumn);
+ if (currentColumn == 2) {
+ if (currentRow < (m_ui->tableEditMetadata->rowCount() - 1))
+ m_ui->tableEditMetadata->setCurrentCell(currentRow + 1, 0);
+ else
+ m_ui->tableEditMetadata->setCurrentCell(0, 0);
+ }
+ });
+}
+
+CDataInputDlg::~CDataInputDlg()
+{
+ delete m_ui;
+}
+
+void CDataInputDlg::initDialog()
+{
+ // Disallow special characters and whitespaces
+ QRegExpValidator *rxp = new QRegExpValidator(QRegExp("[A-Za-z0-9_]+"), this);
+ m_ui->lineEditInputName->setValidator(rxp);
+
+ if (!m_dataInput->name.isEmpty()) {
+ m_name = m_dataInput->name;
+ if (m_dataInput->type == DataTypeRangedNumber) {
+ m_min = m_dataInput->minValue;
+ m_max = m_dataInput->maxValue;
+ }
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ else if (m_type == DataTypeEvaluator) {
+ m_ui->lineEditEvaluation->setText(m_dataInput->valueString);
+ }
+#endif
+ } else {
+ m_name = getUniqueId(tr("newDataInput"));
+ if (m_dataInput->type == DataTypeRangedNumber) {
+ m_dataInput->minValue = m_min;
+ m_dataInput->maxValue = m_max;
+ }
+ }
+
+ m_type = m_dataInput->type;
+ m_ui->comboBoxTypeList->setCurrentIndex(m_type);
+ m_ui->lineEditInputName->setText(m_name);
+ if (m_type == DataTypeRangedNumber) {
+ m_ui->doubleSpinBoxMin->setValue(m_dataInput->minValue);
+ m_ui->doubleSpinBoxMax->setValue(m_dataInput->maxValue);
+ }
+
+ m_ui->tableEditMetadata->setColumnCount(3);
+ m_ui->tableEditMetadata->setColumnWidth(0, 150);
+ m_ui->tableEditMetadata->setColumnWidth(1, 280);
+ m_ui->tableEditMetadata->setColumnWidth(2, 10);
+ m_ui->tableEditMetadata->setHorizontalHeaderLabels({tr("Key"), tr("Metadata"), {}});
+
+ populateMetadata(m_orderedMetadata.size() + 1);
+
+ m_ui->tableEditMetadata->installEventFilter(this);
+ updateVisibility(m_dataInput->type);
+}
+
+void CDataInputDlg::accept()
+{
+ if (checkDuplicateKeys()) {
+ QString title(QObject::tr("Warning"));
+ QString text(QObject::tr("Metadata keys must be unique."));
+ g_StudioApp.GetDialogs()->DisplayMessageBox(title, text,
+ Qt3DSMessageBox::ICON_WARNING, false,
+ this);
+ return;
+ }
+
+ for (const auto &it : qAsConst(m_orderedMetadata)) {
+ if (it.first.trimmed().isEmpty()) {
+ QString title(QObject::tr("Warning"));
+ QString text(QObject::tr("Metadata keys cannot be empty."));
+ g_StudioApp.GetDialogs()->DisplayMessageBox(title, text,
+ Qt3DSMessageBox::ICON_WARNING, false,
+ this);
+ return;
+ }
+ }
+
+ if (m_dataInput->name != m_name)
+ m_dataInput->name = getUniqueId(m_name);
+
+ m_dataInput->type = m_type;
+ if (m_type == DataTypeRangedNumber) {
+ m_dataInput->minValue = m_min;
+ m_dataInput->maxValue = m_max;
+ }
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ else if (m_type == DataTypeEvaluator) {
+ m_dataInput->valueString = m_text;
+ }
+#endif
+
+ m_dataInput->metadata.clear();
+ for (auto const &it : qAsConst(m_orderedMetadata))
+ m_dataInput->metadata.insert(it.first, it.second);
+
+ QDialog::accept();
+}
+
+void CDataInputDlg::onTypeChanged(int type)
+{
+ updateVisibility(type);
+ m_type = type;
+}
+
+void CDataInputDlg::onMinChanged(float min)
+{
+ if (m_ui->doubleSpinBoxMax->value() < min)
+ m_ui->doubleSpinBoxMax->setValue(min);
+ m_min = min;
+}
+
+void CDataInputDlg::onMaxChanged(float max)
+{
+ if (m_ui->doubleSpinBoxMin->value() > max)
+ m_ui->doubleSpinBoxMin->setValue(max);
+ m_max = max;
+}
+
+void CDataInputDlg::onNameChanged(const QString &name)
+{
+ int cursorPos = m_ui->lineEditInputName->cursorPosition();
+ m_name = name;
+ m_ui->lineEditInputName->setText(m_name);
+ m_ui->lineEditInputName->setCursorPosition(cursorPos);
+}
+
+QString CDataInputDlg::getUniqueId(const QString &id)
+{
+ QString retval = QStringLiteral("%1").arg(id);
+ int idx = 1;
+ while (m_data->findItems(retval, Qt::MatchExactly, 0).size() && idx < 1000) {
+ retval = QStringLiteral("%1_%2").arg(id).arg(idx, 3, 10, QLatin1Char('0'));
+ ++idx;
+ }
+ return retval;
+}
+
+void CDataInputDlg::updateVisibility(int type)
+{
+ if (type == DataTypeRangedNumber) {
+ m_ui->labelMin->setVisible(true);
+ m_ui->labelMax->setVisible(true);
+ m_ui->doubleSpinBoxMin->setVisible(true);
+ m_ui->doubleSpinBoxMax->setVisible(true);
+ } else {
+ m_ui->labelMin->setVisible(false);
+ m_ui->labelMax->setVisible(false);
+ m_ui->doubleSpinBoxMin->setVisible(false);
+ m_ui->doubleSpinBoxMax->setVisible(false);
+ }
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ if (type == DataTypeEvaluator) {
+ m_ui->lineEditEvaluation->setVisible(true);
+ m_ui->labelEvaluation->setVisible(true);
+ } else {
+ m_ui->lineEditEvaluation->setVisible(false);
+ m_ui->labelEvaluation->setVisible(false);
+ }
+#endif
+ // Adjust text label positioning according to the
+ // visibility of info text warning about allowed datatypes.
+ if (m_dataInput->ctrldElems.size()) {
+ m_ui->labelInfoText->setVisible(true);
+ m_ui->infoTxtSpacer->changeSize(20, 18);
+ } else {
+ m_ui->labelInfoText->setVisible(false);
+ m_ui->infoTxtSpacer->changeSize(20, 0);
+ }
+}
+
+// static
+bool CDataInputDlg::isEquivalentDataType(int dlgType, qt3dsdm::DataModelDataType::Value dmType,
+ bool strict)
+{
+ using namespace qt3dsdm;
+
+ if ((dlgType == EDataType::DataTypeString && dmType == DataModelDataType::String)
+ || (dlgType == EDataType::DataTypeRangedNumber && dmType == DataModelDataType::RangedNumber)
+ || (dlgType == EDataType::DataTypeFloat
+ && (dmType == DataModelDataType::Float
+ || (dmType == DataModelDataType::String && !strict)))
+ || (dlgType == EDataType::DataTypeBoolean && dmType == DataModelDataType::Bool)
+ || (dlgType == EDataType::DataTypeVector4 && dmType == DataModelDataType::Float4)
+ || (dlgType == EDataType::DataTypeVector3 && dmType == DataModelDataType::Float3)
+ || (dlgType == EDataType::DataTypeVector2 && dmType == DataModelDataType::Float2)
+ // Variant can be bound to any property type except timeline controller because only
+ // datainput of type Ranged Number has additional min/max information. For slide control,
+ // we can allow variant type in addition to String type.
+ || (dlgType == EDataType::DataTypeVariant && dmType != DataModelDataType::RangedNumber)
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ || dlgType == EDataType::DataTypeEvaluator
+#endif
+ ) {
+ return true;
+ }
+
+ return false;
+}
+
+QVector<EDataType> CDataInputDlg::getAcceptedTypes(qt3dsdm::DataModelDataType::Value dmType,
+ bool strict)
+{
+ QVector<EDataType> acceptedTypes;
+ for (auto candidate : allDataTypes) {
+ if (isEquivalentDataType(candidate, dmType, strict))
+ acceptedTypes.append(candidate);
+ }
+ return acceptedTypes;
+}
+
+bool CDataInputDlg::eventFilter(QObject *obj, QEvent *ev)
+{
+ // Eat Enter if the user is editing metadata, to avoid inadvertent accept of
+ // the entire dialog.
+ if (obj == m_ui->tableEditMetadata && ev->type() == QEvent::KeyPress) {
+ if (static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Return
+ || static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Enter) {
+ QKeyEvent *tabEv = new QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
+ QApplication::sendEvent(m_ui->tableEditMetadata, tabEv);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CDataInputDlg::populateMetadata(int rows)
+{
+ m_ui->tableEditMetadata->clearContents();
+ m_ui->tableEditMetadata->setRowCount(rows);
+
+ QList<QPair<QString, QString>>::iterator it = m_orderedMetadata.begin();
+ for (int i = 0; i < rows; ++i) {
+ if (it != m_orderedMetadata.end()) {
+ addMetadataRow(it->first, it->second, i);
+ it++;
+ } else {
+ addMetadataRow({}, {}, i);
+ }
+ }
+}
+
+void CDataInputDlg::addMetadataRow(const QString &key, const QString &metadata, int row)
+{
+ // Grow table if required
+ if (row > m_ui->tableEditMetadata->rowCount() - 1)
+ m_ui->tableEditMetadata->setRowCount(row + 1);
+
+ static const QIcon trashIcon = QIcon(":/images/Action-Trash-Normal.png");
+ QLineEdit *keyEdit = new QLineEdit();
+ QLineEdit *metadataEdit = new QLineEdit();
+
+ keyEdit->installEventFilter(this);
+ metadataEdit->installEventFilter(this);
+
+ keyEdit->setText(key);
+ metadataEdit->setText(metadata);
+ // '$' is used as a delimiter character in .UIP
+ const QRegExpValidator *rxpNoDollar = new QRegExpValidator(QRegExp("[^\\$]+"), this);
+ keyEdit->setValidator(rxpNoDollar);
+ metadataEdit->setValidator(rxpNoDollar);
+
+ QPushButton *delButton = new QPushButton(trashIcon, {});
+
+ m_ui->tableEditMetadata->setCellWidget(row, 0, keyEdit);
+ m_ui->tableEditMetadata->setCellWidget(row, 1, metadataEdit);
+ m_ui->tableEditMetadata->setCellWidget(row, 2, delButton);
+
+ // Check whether the user is editing an existing pair or inserting a new one.
+ // In the latter case append a new blank row.
+ connect(keyEdit, &QLineEdit::editingFinished, this, [&, keyEdit, metadataEdit] {
+ auto currRow = m_ui->tableEditMetadata->currentRow();
+
+ if (currRow < m_orderedMetadata.size())
+ m_orderedMetadata.replace(currRow, {keyEdit->text(), metadataEdit->text()});
+ else
+ m_orderedMetadata.append({keyEdit->text(), metadataEdit->text()});
+
+ // Require both key and metadata to be non-empty before appending a new
+ // blank row.
+ if (currRow == (m_ui->tableEditMetadata->rowCount() - 1)
+ && !static_cast<QLineEdit *>(m_ui->tableEditMetadata->cellWidget(currRow, 1))
+ ->text().isEmpty()) {
+ addMetadataRow({}, {}, currRow + 1);
+ m_ui->tableEditMetadata->scrollToBottom();
+ }
+ });
+
+ connect(metadataEdit, &QLineEdit::editingFinished, this, [&, keyEdit, metadataEdit] {
+ auto currRow = m_ui->tableEditMetadata->currentRow();
+ if (currRow < m_orderedMetadata.size())
+ m_orderedMetadata.replace(currRow, {keyEdit->text(), metadataEdit->text()});
+ else
+ m_orderedMetadata.append({keyEdit->text(), metadataEdit->text()});
+
+ // Require both key and metadata to be non-empty before appending a new
+ // blank row.
+ if (currRow == (m_ui->tableEditMetadata->rowCount() - 1)
+ && !static_cast<QLineEdit *>(m_ui->tableEditMetadata->cellWidget(currRow, 0))
+ ->text().isEmpty()) {
+ addMetadataRow({}, {}, currRow + 1);
+ m_ui->tableEditMetadata->scrollToBottom();
+ }
+ });
+
+ connect(delButton, &QPushButton::clicked, this, [&, delButton] {
+ auto currRow = m_ui->tableEditMetadata->indexAt(delButton->pos()).row();
+ // Never delete last row as it is a placeholder for new items.
+ if (currRow < m_ui->tableEditMetadata->rowCount() - 1) {
+ m_orderedMetadata.removeAt(currRow);
+ m_ui->tableEditMetadata->removeRow(currRow);
+ }
+ });
+}
+
+bool CDataInputDlg::checkDuplicateKeys() const
+{
+ for (int i = 0; i < m_orderedMetadata.size() - 1; ++i) {
+ auto key = m_orderedMetadata[i].first;
+ for (int j = i + 1; j < m_orderedMetadata.size(); ++j) {
+ if (key == m_orderedMetadata[j].first)
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputDlg.h b/src/Authoring/Qt3DStudio/Application/DataInputDlg.h
new file mode 100644
index 00000000..9ae6bc6f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputDlg.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** 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 DATAINPUTDLG_H
+#define DATAINPUTDLG_H
+
+#include <QtWidgets/qdialog.h>
+#include <QtWidgets/QTableWidgetItem>
+#include <QtWidgets/QTableWidget>
+#include <QtCore/qhash.h>
+#include <QKeyEvent>
+
+#include "Doc.h"
+#include "Qt3DSDMDataTypes.h"
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class DataInputDlg;
+}
+QT_END_NAMESPACE
+
+QT_FORWARD_DECLARE_CLASS(QStandardItemModel)
+
+enum EDataType {
+ DataTypeBoolean = 0,
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ DataTypeEvaluator,
+#endif
+ DataTypeFloat,
+ DataTypeRangedNumber,
+ DataTypeString,
+ DataTypeVariant,
+ DataTypeVector2,
+ DataTypeVector3,
+ DataTypeVector4
+};
+
+// The order also specifies the priority for default type in case of multiple accepted types
+static const QVector<EDataType> allDataTypes {
+ EDataType::DataTypeString,
+ EDataType::DataTypeFloat,
+ EDataType::DataTypeVector4,
+ EDataType::DataTypeVector3,
+ EDataType::DataTypeVector2,
+ EDataType::DataTypeRangedNumber,
+ EDataType::DataTypeBoolean,
+ #ifdef DATAINPUT_EVALUATOR_ENABLED
+ EDataType::DataTypeEvaluator,
+ #endif
+ EDataType::DataTypeVariant
+};
+
+class CDataInputDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ CDataInputDlg(CDataInputDialogItem **datainput, QStandardItemModel *data,
+ QWidget* parent = nullptr, const QVector<EDataType> acceptedTypes = allDataTypes);
+ ~CDataInputDlg() override;
+
+ // Maps between DataModel datatypes and datainput dialog types
+ static bool isEquivalentDataType(int dlgType, qt3dsdm::DataModelDataType::Value dmType,
+ bool strict = false);
+ static QVector<EDataType> getAcceptedTypes(qt3dsdm::DataModelDataType::Value dmType,
+ bool strict = false);
+
+ bool eventFilter(QObject *obj, QEvent *ev) override;
+
+protected:
+ void initDialog();
+ QString getUniqueId(const QString &id);
+ void updateVisibility(int type);
+
+private Q_SLOTS:
+ void accept() override;
+ void onTypeChanged(int type);
+ void onMinChanged(float min);
+ void onMaxChanged(float max);
+ void onNameChanged(const QString &name);
+
+private:
+ void populateMetadata(int rows);
+ void updateMetadataFromTable();
+ void addMetadataRow(const QString &key, const QString &metadata, int row);
+ bool checkDuplicateKeys() const;
+
+ Ui::DataInputDlg *m_ui;
+ CDataInputDialogItem *m_dataInput;
+ QStandardItemModel *m_data;
+ QString m_name;
+ float m_max;
+ float m_min;
+ int m_type;
+ QList<QPair<QString, QString>> m_orderedMetadata;
+ QVector<EDataType> m_acceptedTypes;
+
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputDlg.ui b/src/Authoring/Qt3DStudio/Application/DataInputDlg.ui
new file mode 100644
index 00000000..584068c5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputDlg.ui
@@ -0,0 +1,430 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DataInputDlg</class>
+ <widget class="QDialog" name="DataInputDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>533</width>
+ <height>340</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Modify Data Input</string>
+ </property>
+ <property name="accessibleName">
+ <string>DataInputDlg</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <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_4">
+ <item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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_2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </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="QLabel" name="labelNodeName">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="infoTxtSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>9</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelNodeType">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>90</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Input Type</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelMin">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Minimum Range</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelMax">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Maximum Range</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_5" native="true">
+ <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="QLineEdit" name="lineEditInputName">
+ <property name="toolTip">
+ <string>The name of the data input</string>
+ </property>
+ <property name="text">
+ <string>newDataInput</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelInfoText">
+ <property name="font">
+ <font>
+ <pointsize>7</pointsize>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Only datatypes compatible with existing controlled properties are allowed</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxTypeList">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>The type of the data input</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="doubleSpinBoxMin">
+ <property name="toolTip">
+ <string>The minimum value for the number</string>
+ </property>
+ <property name="minimum">
+ <double>-999999999.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>999999999.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="doubleSpinBoxMax">
+ <property name="toolTip">
+ <string>The maximum value for the number</string>
+ </property>
+ <property name="minimum">
+ <double>-999999999.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>999999999.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>10.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <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>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMaximumSize</enum>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="QTableWidget" name="tableEditMetadata">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>5</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>78</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Metadata key - value pairs.</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustIgnored</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="horizontalHeaderDefaultSectionSize">
+ <number>75</number>
+ </attribute>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_3" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_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>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>252</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <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>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>lineEditInputName</tabstop>
+ <tabstop>comboBoxTypeList</tabstop>
+ <tabstop>doubleSpinBoxMin</tabstop>
+ <tabstop>doubleSpinBoxMax</tabstop>
+ <tabstop>lineEditMetadataKey</tabstop>
+ <tabstop>lineEditMetadata</tabstop>
+ <tabstop>pushButtonAddMeta</tabstop>
+ <tabstop>tableEditMetadata</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DataInputDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>396</x>
+ <y>223</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>266</x>
+ <y>121</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DataInputDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>396</x>
+ <y>223</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>266</x>
+ <y>121</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputListDlg.cpp b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.cpp
new file mode 100644
index 00000000..2f8bc29b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.cpp
@@ -0,0 +1,796 @@
+/****************************************************************************
+**
+** 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 "DataInputListDlg.h"
+#include "ui_DataInputListDlg.h"
+#include "DataInputDlg.h"
+#include "StudioPreferences.h"
+#include "StudioApp.h"
+#include "Dialogs.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "DataModelObjectReferenceHelper.h"
+
+#include <QtWidgets/qpushbutton.h>
+#include <QtGui/qstandarditemmodel.h>
+#include <QtGui/qevent.h>
+#include <QtWidgets/qaction.h>
+#include <algorithm>
+#include <QtCore/qtimer.h>
+
+const int columnCount = 3;
+
+CDataInputListDlg::CDataInputListDlg(QMap<QString, CDataInputDialogItem *> *datainputs,
+ bool goToAdd, QWidget *parent, EDataType defaultType,
+ const QVector<EDataType> &acceptedTypes)
+ : QDialog(parent)
+ , m_ui(new Ui::DataInputListDlg)
+ , m_actualDataInputs(datainputs)
+ , m_currentDataInputIndex(-1)
+ , m_tableContents(new QStandardItemModel(0, columnCount, this))
+ , m_infoContents(new QStandardItemModel(0, columnCount, this))
+ , m_goToAdd(goToAdd)
+ , m_sortColumn(-1)
+ , m_defaultType(defaultType)
+ , m_acceptedTypes(acceptedTypes)
+{
+ m_ui->setupUi(this);
+
+ // Create icon buttons. Give them object and accessible names so their style can be modified
+ // via stylesheet.
+ QPushButton *addButton = new QPushButton(this);
+ addButton->setIcon(QIcon(":/images/add.png"));
+ addButton->setAccessibleName(QStringLiteral("DataInputListButton"));
+ addButton->setObjectName(QStringLiteral("DataInputListButton"));
+ QPushButton *removeButton = new QPushButton(this);
+ removeButton->setIcon(QIcon(":/images/Action-Trash-Disabled.png"));
+ removeButton->setAccessibleName(QStringLiteral("DataInputListButton"));
+ removeButton->setObjectName(QStringLiteral("DataInputListButton"));
+
+ m_ui->buttonBoxAddEditRemove->addButton(addButton, QDialogButtonBox::ActionRole);
+ m_ui->buttonBoxAddEditRemove->addButton(removeButton, QDialogButtonBox::ActionRole);
+ QList<QAbstractButton *>buttons = m_ui->buttonBoxAddEditRemove->buttons();
+ connect(buttons.at(0), &QAbstractButton::clicked, this, &CDataInputListDlg::onAddDataInput);
+ connect(buttons.at(1), &QAbstractButton::clicked, this, &CDataInputListDlg::onRemoveDataInput);
+
+ buttons[0]->setToolTip(tr("Add New Data Input..."));
+ buttons[0]->setText(tr("Add Data Input"));
+ buttons[1]->setToolTip(tr("Remove Data Input"));
+ buttons[1]->setText(tr("Remove existing Data Input"));
+
+ m_ui->typeFilterCombo->addItems({tr("[All types]"), tr("Boolean"),
+ tr("Float"), tr("Ranged Number"), tr("String"), tr("Variant"),
+ tr("Vector2"), tr("Vector3"), tr("Vector4")});
+ m_ui->typeFilterCombo->setToolTip(tr("Filter the list by Data Input type"));
+
+ m_ui->searchField->setToolTip(tr("Search for Data Input"));
+
+ connect(m_ui->typeFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &CDataInputListDlg::onFilterTypeChanged);
+ connect(m_ui->searchField, &QLineEdit::textChanged, this,
+ &CDataInputListDlg::onSearchTextChanged);
+
+ setTabOrder(m_ui->searchField, addButton);
+ setTabOrder(addButton, removeButton);
+
+ initDialog();
+
+ window()->setFixedSize(size());
+}
+
+CDataInputListDlg::~CDataInputListDlg()
+{
+ delete m_ui;
+}
+
+void CDataInputListDlg::initDialog()
+{
+ // Copy given list to our internal one. We want to commit to the changes only after "Ok"
+ // has been pressed.
+ const auto keys = m_actualDataInputs->keys();
+ for (auto name : keys)
+ m_dataInputs.insert(name, m_actualDataInputs->value(name));
+
+ // Check available list. If there are none, disable "Remove" and "Edit" buttons
+ updateButtons();
+
+ // Update table contents
+ updateContents();
+ updateInfo();
+
+ // Make the expression column wider than name and type
+ m_ui->tableView->horizontalHeader()->setStretchLastSection(true);
+ m_ui->tableView->horizontalHeader()->setMinimumSectionSize(125);
+ m_ui->tableView->setFocusPolicy(Qt::NoFocus);
+
+ // Align columns left and prevent selecting the whole column
+ m_ui->tableView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
+
+ m_replaceSelectedAction = new QAction(QObject::tr("Replace selected"), m_ui->elementInfo);
+ m_replaceAllAction = new QAction(QObject::tr("Replace all"), m_ui->elementInfo);
+ m_replaceSelectedAction->setDisabled(true);
+ m_replaceAllAction->setDisabled(true);
+
+ m_ui->elementInfo->addAction(m_replaceSelectedAction);
+ m_ui->elementInfo->addAction(m_replaceAllAction);
+ m_ui->elementInfo->setContextMenuPolicy(Qt::ActionsContextMenu);
+ m_ui->elementInfo->setFocusPolicy(Qt::NoFocus);
+ m_ui->elementInfo->resizeColumnsToContents();
+ m_ui->elementInfo->horizontalHeader()->setStretchLastSection(true);
+ m_ui->elementInfo->horizontalHeader()->setMinimumSectionSize(140);
+ m_ui->elementInfo->setModel(m_infoContents);
+ m_ui->elementInfo->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
+
+ connect(m_replaceSelectedAction, &QAction::triggered, this,
+ &CDataInputListDlg::onReplaceSelected);
+ connect(m_replaceAllAction, &QAction::triggered, this, &CDataInputListDlg::onReplaceAll);
+
+ connect(m_ui->elementInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &CDataInputListDlg::onElementSelectionChanged);
+
+ connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &CDataInputListDlg::onSelectionChanged);
+ connect(m_ui->tableView, &QTableView::activated, this, &CDataInputListDlg::onActivated);
+ connect(m_ui->tableView->horizontalHeader(), &QHeaderView::sortIndicatorChanged,
+ this, &CDataInputListDlg::onSortOrderChanged);
+
+ // Directly show data input modification dialog
+ if (m_goToAdd)
+ QTimer::singleShot(0, this, &CDataInputListDlg::onAddDataInput);
+}
+
+void CDataInputListDlg::updateButtons()
+{
+ if (m_ui->tableView->selectionModel()
+ && m_ui->tableView->selectionModel()->selectedIndexes().size() > 0) {
+ m_ui->buttonBoxAddEditRemove->buttons()[1]->setEnabled(true);
+ m_ui->buttonBoxAddEditRemove->buttons()[1]->setIcon(
+ QIcon(":/images/Action-Trash-Normal.png"));
+ } else {
+ m_ui->buttonBoxAddEditRemove->buttons()[1]->setEnabled(false);
+ m_ui->buttonBoxAddEditRemove->buttons()[1]->setIcon(
+ QIcon(":/images/Action-Trash-Disabled.png"));
+ }
+}
+
+void CDataInputListDlg::updateContents()
+{
+ m_tableContents->clear();
+
+ QStringList labels;
+ labels << tr("Name") << tr("Input Type") << tr("Expression");
+ m_tableContents->setHorizontalHeaderLabels(labels);
+
+ QList<QStandardItem *> dataInput;
+
+ for (auto &it : qAsConst(m_dataInputs)) {
+ dataInput.clear();
+
+ EDataType dataInputType = (EDataType)it->type;
+
+ if ((dataInputType == m_typeFilter || m_typeFilter == -1)
+ && it->name.contains(m_searchString)){
+
+ dataInput.append(new QStandardItem(it->name));
+
+ if (dataInputType == DataTypeRangedNumber
+ && (m_typeFilter == (int)DataTypeRangedNumber || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Ranged Number")));
+ QString expression = QStringLiteral("[ ")
+ + QString::number(it->minValue)
+ + QStringLiteral(" ... ")
+ + QString::number(it->maxValue)
+ + QStringLiteral(" ]");
+ dataInput.append(new QStandardItem(expression));
+ } else if (dataInputType == DataTypeString
+ && (m_typeFilter == (int)DataTypeString || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("String")));
+ } else if (dataInputType == DataTypeFloat
+ && (m_typeFilter == (int)DataTypeFloat || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Float")));
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ } else if (dataInputType == DataTypeEvaluator
+ && (m_typeFilter == (int)DataTypeEvaluator || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Evaluator")));
+ dataInput.append(new QStandardItem(m_dataInputs.at(i)->valueString));
+#endif
+ } else if (dataInputType == DataTypeBoolean
+ && (m_typeFilter == (int)DataTypeBoolean || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Boolean")));
+ } else if (dataInputType == DataTypeVector4
+ && (m_typeFilter == (int)DataTypeVector4 || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Vector4")));
+ } else if (dataInputType == DataTypeVector3
+ && (m_typeFilter == (int)DataTypeVector3 || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Vector3")));
+ } else if (dataInputType == DataTypeVector2
+ && (m_typeFilter == (int)DataTypeVector2 || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Vector2")));
+ } else if (dataInputType == DataTypeVariant
+ && (m_typeFilter == (int)DataTypeVariant || m_typeFilter == -1)) {
+ dataInput.append(new QStandardItem(tr("Variant")));
+ }
+
+ // highlight datainputs that are in use
+ if (it->ctrldElems.size() || it->externalPresBoundTypes.size())
+ dataInput.first()->setForeground(QBrush(CStudioPreferences::dataInputColor()));
+
+ // warn if any datainputs have mismatching datatype with an icon after datatype
+ // indicator
+ static QString warning(tr("Data Input type is not matching with one "
+ "or several bound properties"));
+ for (const auto &ctrlElem : qAsConst(it->ctrldElems)) {
+ if (!CDataInputDlg::getAcceptedTypes(ctrlElem.dataType.first)
+ .contains(dataInputType)) {
+ dataInput[1]->setIcon(QIcon(":/images/warning.png"));
+ dataInput[1]->setToolTip(warning);
+ }
+ }
+
+ for (const auto &extBoundType : qAsConst(it->externalPresBoundTypes)) {
+ if (!CDataInputDlg::getAcceptedTypes(extBoundType.first).contains(dataInputType)) {
+ dataInput[1]->setIcon(QIcon(":/images/warning.png"));
+ dataInput[1]->setToolTip(warning);
+ }
+ }
+
+ m_tableContents->appendRow(dataInput);
+ }
+ }
+
+ m_ui->tableView->setModel(m_tableContents);
+
+ if (m_sortColumn >= 0)
+ m_ui->tableView->sortByColumn(m_sortColumn, m_sortOrder);
+}
+
+void CDataInputListDlg::updateInfo()
+{
+ auto doc = g_StudioApp.GetCore()->GetDoc();
+ auto refHelper = doc->GetDataModelObjectReferenceHelper();
+
+ m_infoContents->clear();
+
+ QStringList labels;
+ labels << tr("Object Name") << tr("Location") << tr("Properties");
+ m_infoContents->setHorizontalHeaderLabels(labels);
+
+ // Only show controlled instances if we have a single datainput selected.
+ if (m_ui->tableView->selectionModel()->selectedRows(0).size() == 1) {
+ for (auto allCtrldElemsIt = m_dataInputs[m_currentDataInputName]->ctrldElems.begin();
+ allCtrldElemsIt != m_dataInputs[m_currentDataInputName]->ctrldElems.end();) {
+ bool typeNotMatching = false;
+ QStandardItem *item = new QStandardItem(
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge()->GetName(
+ allCtrldElemsIt->instHandle.GetHandleValue()).toQString());
+ // Store actual handle value to Qt::Userdata+1
+ item->setData(allCtrldElemsIt->instHandle.GetHandleValue());
+ auto path = refHelper->GetObjectReferenceString(
+ doc->GetSceneInstance(), CRelativePathTools::EPATHTYPE_GUID,
+ allCtrldElemsIt->instHandle).toQString();
+ // One element can have several properties controlled by this datainput,
+ // do not show element several times. Show the number of properties after
+ // the elementpath and the list of property names in a separate column.
+ if (m_infoContents->findItems(path, Qt::MatchContains, 1).isEmpty()) {
+ item->setToolTip(path);
+ item->setEditable(false);
+
+ QString propNames;
+ int count = 0;
+ CDataInputDialogItem *di = m_dataInputs[m_currentDataInputName];
+
+ QVector<CDataInputDialogItem::ControlledItem> thisInstCtrldItems;
+ di->getInstCtrldItems(
+ allCtrldElemsIt->instHandle.GetHandleValue(), thisInstCtrldItems);
+ for (auto thisInstCtrldItemsIt : qAsConst(thisInstCtrldItems)) {
+ if (propNames.size() != 0)
+ propNames.append(QLatin1String(", "));
+
+ if (thisInstCtrldItemsIt.propHandle.Valid()) {
+ propNames.append(
+ QString::fromStdWString(
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->
+ GetPropertySystem()->GetFormalName(
+ thisInstCtrldItemsIt.instHandle,
+ thisInstCtrldItemsIt.propHandle).wide_str()));
+ } else {
+ propNames.append(QObject::tr("timeline/slide"));
+ }
+
+ count++;
+
+ // Check if there is a non-matching datatype binding with one or several
+ // properties for this element.
+ if (!CDataInputDlg::getAcceptedTypes(allCtrldElemsIt->dataType.first).contains(
+ (EDataType)(m_dataInputs[m_currentDataInputName]->type))) {
+ typeNotMatching = true;
+ }
+ // Advance main iterator so that after the inner loop we end up
+ // at the start of next instance's batch of controlleditems.
+ allCtrldElemsIt++;
+ }
+
+ QStandardItem *item2 = new QStandardItem(path + QStringLiteral("(")
+ + QString::number(count)
+ + QStringLiteral(")"));
+ item2->setToolTip(path);
+ item2->setEditable(false);
+ QStandardItem *item3 = new QStandardItem(propNames);
+ item3->setToolTip(propNames);
+ item3->setEditable(false);
+
+ // Highlight the entire property name item if a non-match was found.
+ if (typeNotMatching) {
+ item3->setForeground(
+ QBrush(CStudioPreferences::invalidDataInputIndicatorColor()));
+ static QString warning(tr("\n\nData Input type is not matching with one or "
+ "several bound properties"));
+ item3->setToolTip(propNames + warning);
+ }
+ m_infoContents->appendRow(QList<QStandardItem *>({item, item2, item3}));
+ }
+ }
+ // Show this datainput uses in subpresentations but leave property name list
+ // empty because we do not have that info.
+ const auto uniqueKeys
+ = m_dataInputs[m_currentDataInputName]->externalPresBoundTypes.uniqueKeys();
+ for (auto &k : uniqueKeys) {
+ m_infoContents->appendRow(QList<QStandardItem *>(
+ {new QStandardItem(QObject::tr("<another presentation>")),
+ new QStandardItem(k), new QStandardItem()}));
+ }
+ }
+
+ m_ui->elementInfo->setModel(m_infoContents);
+}
+bool CDataInputListDlg::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride) {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (m_currentDataInputIndex >= 0 && (ke->key() == Qt::Key_Delete)) {
+ onRemoveDataInput();
+ event->accept();
+ return true;
+ } else {
+ return QDialog::event(event);
+ }
+ } else {
+ return QDialog::event(event);
+ }
+}
+
+void CDataInputListDlg::keyPressEvent(QKeyEvent *event)
+{
+ if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) {
+ // Eat enter if we have selections
+ const QModelIndexList indexes = m_ui->tableView->selectionModel()->selectedIndexes();
+ if (indexes.size() > 0)
+ event->accept();
+ else
+ QDialog::keyPressEvent(event);
+ } else {
+ QDialog::keyPressEvent(event);
+ }
+}
+
+void CDataInputListDlg::accept()
+{
+ m_actualDataInputs->clear();
+
+ const auto keys = m_dataInputs.keys();
+ for (auto name : keys)
+ m_actualDataInputs->insert(name, m_dataInputs.value(name));
+
+ // Only show automatic binding removal choice if user has deleted
+ // in-use datainput during this activation of the dialog, to avoid constant
+ // nagging at closing of this dialog in case we have invalid datainputs.
+ if (m_deletedDiInUse)
+ QTimer::singleShot(0, &g_StudioApp, &CStudioApp::checkDeletedDatainputs);
+
+ if (g_StudioApp.GetCore()->GetDoc()->isTransactionOpened()) {
+ g_StudioApp.GetCore()->GetDoc()->closeTransaction();
+ // If a datainput has been edited (and datainput controller names
+ // updated in element "controlledproperty" properties), we need to make all changes
+ // non-undoable. This is because datainput definitions in UIA file (and in global
+ // datainput map) are not participating in the command stack. Changes there cannot be
+ // reversed, leading to datainput map and element properties being out of sync after Undo.
+ if (m_diHasBeenEdited)
+ g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo();
+ }
+
+ QDialog::accept();
+}
+
+void CDataInputListDlg::reject()
+{
+ // If the user cancels, also roll back any possible changes to data input bindings.
+ if (g_StudioApp.GetCore()->GetDoc()->isTransactionOpened())
+ g_StudioApp.GetCore()->GetDoc()->rollbackTransaction();
+
+ QDialog::reject();
+}
+
+void CDataInputListDlg::onAddDataInput()
+{
+ // Create a new data input dialog item and give it to dialog
+ CDataInputDialogItem *dataInput = new CDataInputDialogItem();
+ dataInput->type = m_defaultType;
+ CDataInputDlg datainputdialog(&dataInput, m_tableContents, this, m_acceptedTypes);
+ datainputdialog.setWindowTitle("Add Data Input");
+ if (datainputdialog.exec() == QDialog::Accepted) {
+ m_dataInputs.insert(dataInput->name, dataInput);
+ m_mostRecentlyAdded = dataInput->name;
+ } else {
+ m_mostRecentlyAdded.clear();
+ }
+
+ updateButtons();
+ updateContents();
+
+ // If we went straight to adding a new datainput, close
+ // dialog automatically
+ if (m_goToAdd)
+ accept();
+
+ // Otherwise find the new position of added DI and select it.
+ auto idxList = m_ui->tableView->selectionModel()->model()->match(
+ m_ui->tableView->selectionModel()->model()->index(
+ 0,0), Qt::EditRole, m_mostRecentlyAdded);
+
+ if (!idxList.empty())
+ m_ui->tableView->selectRow(idxList.first().row());
+}
+
+void CDataInputListDlg::onRemoveDataInput()
+{
+ QVector<int> removedRows;
+ bool anyDiInUse = false;
+
+ if (m_ui->tableView->selectionModel()) {
+ const QModelIndexList indexes = m_ui->tableView->selectionModel()->selectedIndexes();
+ for (const auto &index : indexes) {
+ if (!removedRows.contains(index.row())) {
+ removedRows.append(index.row());
+ QString diName = m_tableContents
+ ->itemFromIndex(index)->data(Qt::EditRole).toString();
+ if (m_dataInputs[diName]->ctrldElems.size()
+ || m_dataInputs[diName]->externalPresBoundTypes.size() ) {
+ anyDiInUse = true;
+ }
+ }
+ }
+ }
+
+
+ QString title(QObject::tr("Warning"));
+ QString text;
+
+ if (anyDiInUse)
+ text.append(QObject::tr("One or more datainputs are currently in use. "));
+ text.append(QObject::tr("This operation cannot be undone.\n\nAre you sure?"));
+
+ auto ret = g_StudioApp.GetDialogs()->DisplayMessageBox(title, text,
+ Qt3DSMessageBox::ICON_WARNING, true,
+ this);
+ if (ret != Qt3DSMessageBox::MSGBX_OK)
+ return;
+
+ m_deletedDiInUse = anyDiInUse;
+
+ if (removedRows.size() > 0) {
+ std::sort(removedRows.begin(), removedRows.end());
+ for (int i = removedRows.size() - 1; i >= 0; --i) {
+ m_dataInputs.remove(
+ m_tableContents->item(removedRows[i])->data(Qt::EditRole).toString());
+ }
+ m_ui->tableView->clearSelection();
+ m_currentDataInputIndex = -1;
+
+ updateButtons();
+ updateContents();
+ }
+}
+
+void CDataInputListDlg::onEditDataInput()
+{
+ if (m_currentDataInputIndex >= 0) {
+ CDataInputDialogItem *di = m_dataInputs.value(m_currentDataInputName);
+
+ // Only show types that are ok for _all_ currently controlled properties.
+ // If datainput is not controlling any elements, all types are ok.
+
+ // Datainput binding types
+ QVector<EDataType> allowedTypes;
+ bool strictFound = false;
+ // Datatype and strictness requirement
+ QVector<QPair<qt3dsdm::DataModelDataType::Value, bool>> types;
+ di->getBoundTypes(types);
+ if (types.size()) {
+ for (auto type : qAsConst(types)) {
+ // If we hit strict type requirement for a certain bound datatype, set allowed types
+ // to only this data type (and Variant) and exit after appending it to
+ // allowedTypes vector.
+ // We should never have strict requirement for two different datatypes, obviously.
+ if (type.second)
+ allowedTypes.clear();
+
+ auto acceptedTypes = CDataInputDlg::getAcceptedTypes(type.first, type.second);
+
+ for (auto t : qAsConst(acceptedTypes)) {
+ if (!allowedTypes.contains(t))
+ allowedTypes.append(t);
+ }
+ // if we just hit a strict type requirement we are finished
+ if (type.second) {
+ strictFound = true;
+ break;
+ }
+ }
+ }
+
+ // Datainput bindings for all other presentations, unless we already have a
+ // strict type requirement
+ if (di->externalPresBoundTypes.size() && !strictFound) {
+ for (auto type : qAsConst(di->externalPresBoundTypes)) {
+ if (type.second)
+ allowedTypes.clear();
+
+ const auto acceptedTypes = CDataInputDlg::getAcceptedTypes(type.first, type.second);
+
+ for (auto t : acceptedTypes) {
+ if (!allowedTypes.contains(t))
+ allowedTypes.append(t);
+ }
+ // if we just hit a strict type requirement we are finished
+ if (type.second)
+ break;
+ }
+ }
+
+ // no bindings in this or other presentations, all datatypes are ok
+ if (!allowedTypes.size())
+ allowedTypes = allDataTypes;
+
+ CDataInputDlg datainputdialog(&di, m_tableContents, this, allowedTypes);
+ datainputdialog.exec();
+
+ // if we are renaming a datainput, remove the old key - value and
+ // add it again as new entry with new name
+ if (m_currentDataInputName != di->name) {
+ m_ui->elementInfo->selectAll();
+ const QModelIndexList indexes = m_ui->elementInfo->selectionModel()->selectedRows();
+ QList<qt3dsdm::Qt3DSDMInstanceHandle> elementHandles;
+ for (auto it : indexes) {
+ elementHandles.append(
+ m_ui->elementInfo->model()->data(it, Qt::UserRole + 1).toInt());
+ }
+ // Opens up a transaction if one is not existing; we will close it at
+ // dialog exit to batch ok/cancel all changes done in this dialog.
+ g_StudioApp.GetCore()->GetDoc()->ReplaceDatainput(m_currentDataInputName,
+ di->name, elementHandles);
+ m_dataInputs.remove(m_currentDataInputName);
+ m_currentDataInputName = di->name;
+ m_diHasBeenEdited = true;
+ }
+ // Insert replaces the previous key - value pair if existing.
+ m_dataInputs.insert(m_currentDataInputName, di);
+
+ updateButtons();
+ updateContents();
+
+ // Find the new position of renamed DI and select it.
+ auto idxList = m_ui->tableView->selectionModel()->model()->match(
+ m_ui->tableView->selectionModel()->model()->index(
+ 0,0), Qt::EditRole, m_currentDataInputName);
+
+ if (!idxList.empty())
+ m_ui->tableView->selectRow(idxList.first().row());
+ }
+}
+
+void CDataInputListDlg::onActivated(const QModelIndex &index)
+{
+ const QModelIndexList indexes = m_ui->tableView->selectionModel()->selectedRows(0);
+ m_currentDataInputIndex = indexes.size() ? index.row() : -1;
+ if (m_currentDataInputIndex >= 0) {
+ m_currentDataInputName
+ = m_tableContents->itemFromIndex(indexes.at(0))->data(Qt::EditRole).toString();
+ }
+ onEditDataInput();
+}
+
+void CDataInputListDlg::onSelectionChanged()
+{
+ const QModelIndexList indexes = m_ui->tableView->selectionModel()->selectedRows(0);
+ m_currentDataInputIndex = indexes.size() ? indexes.at(0).row() : -1;
+ if (m_currentDataInputIndex >= 0) {
+ m_currentDataInputName
+ = m_tableContents->itemFromIndex(indexes.at(0))->data(Qt::EditRole).toString();
+ }
+
+ updateButtons();
+ updateInfo();
+ onElementSelectionChanged();
+}
+
+void CDataInputListDlg::onSortOrderChanged(int column, Qt::SortOrder order)
+{
+ m_sortColumn = column;
+ m_sortOrder = order;
+}
+
+void CDataInputListDlg::onReplaceSelected()
+{
+ if (!m_dataInputChooserView) {
+ m_dataInputChooserView = new DataInputSelectView(m_acceptedTypes, this);
+ // Do not show "Add new" and "None" choices.
+ m_dataInputChooserView->getModel()->showFixedItems(false);
+ } else {
+ disconnect(m_dataInputChooserView, nullptr, nullptr, nullptr);
+ }
+
+ connect(m_dataInputChooserView, &DataInputSelectView::dataInputChanged, this,
+ [this](int handle, int instance, const QString &controllerName) {
+ Q_UNUSED(handle)
+ Q_UNUSED(instance)
+
+ const QModelIndexList indexes = m_ui->elementInfo->selectionModel()->selectedRows();
+
+ replaceDatainputs(indexes, controllerName);
+ refreshDIs();
+ });
+
+ QVector<QPair<qt3dsdm::DataModelDataType::Value, bool>> selBoundTypes;
+
+ const auto selRows = m_ui->elementInfo->selectionModel()->selectedRows(0);
+ for (auto it : selRows)
+ selBoundTypes.append(m_dataInputs[m_currentDataInputName]
+ ->ctrldElems[it.row()].dataType);
+
+ setUniqueAcceptedDITypes(selBoundTypes);
+
+ CDialogs::showWidgetBrowser(this, m_dataInputChooserView, geometry().center(),
+ CDialogs::WidgetBrowserAlign::Center);
+}
+
+void CDataInputListDlg::onReplaceAll()
+{
+ if (!m_dataInputChooserView) {
+ m_dataInputChooserView = new DataInputSelectView(m_acceptedTypes, this);
+ // Do not show "Add new" and "None" choices.
+ m_dataInputChooserView->getModel()->showFixedItems(false);
+ } else {
+ disconnect(m_dataInputChooserView, nullptr, nullptr, nullptr);
+ }
+
+ connect(m_dataInputChooserView, &DataInputSelectView::dataInputChanged, this,
+ [this](int handle, int instance, const QString &controllerName) {
+ Q_UNUSED(handle)
+ Q_UNUSED(instance)
+
+ m_ui->elementInfo->selectAll();
+ const QModelIndexList indexes = m_ui->elementInfo->selectionModel()->selectedRows();
+
+ replaceDatainputs(indexes, controllerName);
+ refreshDIs();
+ });
+ QVector<QPair<qt3dsdm::DataModelDataType::Value, bool>> types;
+ m_dataInputs[m_currentDataInputName]->getBoundTypes(types);
+ setUniqueAcceptedDITypes(types);
+
+ CDialogs::showWidgetBrowser(this, m_dataInputChooserView, geometry().center(),
+ CDialogs::WidgetBrowserAlign::Center);
+}
+
+void CDataInputListDlg::onElementSelectionChanged()
+{
+ bool disable = true;
+ QModelIndexList selected = m_ui->elementInfo->selectionModel()->selectedRows(2);
+ // We only can change bindings in the currently open project. Disable replace
+ // actions if we have selected a row denoting control in a subpresentation
+ // (fastest way to check is to see if property list is empty).
+ if (selected.isEmpty())
+ disable = true;
+ else if (m_ui->elementInfo->model()->data(selected.at(0)).isNull())
+ disable = true;
+ else
+ disable = false;
+
+ m_replaceSelectedAction->setDisabled(disable);
+ m_replaceAllAction->setDisabled(disable);
+}
+
+void CDataInputListDlg::onFilterTypeChanged(int index)
+{
+ m_typeFilter = index - 1;
+ updateContents();
+}
+
+void CDataInputListDlg::onSearchTextChanged()
+{
+ m_searchString = m_ui->searchField->text();
+ updateContents();
+}
+
+void CDataInputListDlg::refreshDIs()
+{
+ updateContents();
+ updateInfo();
+}
+
+void CDataInputListDlg::setUniqueAcceptedDITypes(
+ const QVector<QPair<qt3dsdm::DataModelDataType::Value, bool>> &boundTypes)
+{
+ QVector<EDataType> okDiTypes(allDataTypes);
+
+ for (auto it : qAsConst(allDataTypes)) {
+ for (auto it2 : boundTypes) {
+ if (!CDataInputDlg::isEquivalentDataType(it, it2.first, it2.second)) {
+ auto idx = okDiTypes.indexOf(it);
+ if (idx != -1)
+ okDiTypes.remove(idx);
+ }
+ }
+
+ QVector<QPair<QString, int>> dataInputList;
+ for (auto &it : qAsConst(m_dataInputs))
+ dataInputList.append({it->name, it->type});
+
+ m_dataInputChooserView->setMatchingTypes(okDiTypes);
+ m_dataInputChooserView->setData(dataInputList, m_currentDataInputName);
+ }
+}
+
+void CDataInputListDlg::replaceDatainputs(const QModelIndexList &selectedBindings,
+ const QString &newDIName)
+{
+ QList<qt3dsdm::Qt3DSDMInstanceHandle> elementHandles;
+ for (auto it : selectedBindings)
+ elementHandles.append(m_ui->elementInfo->model()->data(it, Qt::UserRole + 1).toInt());
+
+ // Update bindings for the internal list held by this dialog.
+ for (auto it : qAsConst(elementHandles)) {
+ CDataInputDialogItem *oldDI = *m_dataInputs.find(m_currentDataInputName);
+ CDataInputDialogItem *newDI = *m_dataInputs.find(newDIName);
+
+ QVector<CDataInputDialogItem::ControlledItem> ctrlItems;
+ oldDI->getInstCtrldItems(it.GetHandleValue(), ctrlItems);
+
+ for (auto &it2 : qAsConst(ctrlItems)) {
+ oldDI->ctrldElems.removeAll(it2);
+ newDI->ctrldElems.append(it2);
+ }
+ }
+ // Make direct changes to object properties. Transaction that is opened will be
+ // closed when the dialog ultimately exits.
+ g_StudioApp.GetCore()->GetDoc()->ReplaceDatainput(m_currentDataInputName,
+ newDIName, elementHandles);
+}
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputListDlg.h b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.h
new file mode 100644
index 00000000..ff59c5b4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.h
@@ -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$
+**
+****************************************************************************/
+
+#ifndef DATAINPUTLISTDLG_H
+#define DATAINPUTLISTDLG_H
+
+#include <QtWidgets/qdialog.h>
+#include <QtCore/qmap.h>
+#include "DataInputDlg.h"
+#include "DataInputSelectView.h"
+#include <QtCore/qpointer.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class DataInputListDlg;
+}
+QT_END_NAMESPACE
+
+class CDataInputDialogItem;
+
+QT_FORWARD_DECLARE_CLASS(QStandardItemModel)
+QT_FORWARD_DECLARE_CLASS(QKeyEvent)
+
+class CDataInputListDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ CDataInputListDlg(QMap<QString, CDataInputDialogItem *> *datainputs,
+ bool goToAdd = false, QWidget *parent = nullptr,
+ EDataType defaultType = EDataType::DataTypeFloat,
+ const QVector<EDataType> &acceptedTypes = allDataTypes);
+ ~CDataInputListDlg();
+
+ QString getAddedDataInput() { return m_mostRecentlyAdded; }
+
+protected:
+ void initDialog();
+ void updateButtons();
+ void updateContents();
+ void updateInfo();
+ QVector<CDataInputDialogItem *> dataInputs() const;
+ void keyPressEvent(QKeyEvent *event) override;
+ bool event(QEvent *event) override;
+ void refreshDIs();
+ void replaceDatainputs(const QModelIndexList &selectedBindings, const QString &newDIName);
+ // Given a list of data model datatypes, returns the type(s) of datainputs that can
+ // work as controller for all of listed datatypes.
+ void setUniqueAcceptedDITypes(
+ const QVector<QPair<qt3dsdm::DataModelDataType::Value, bool>> &boundTypes);
+
+private Q_SLOTS:
+ void accept() override;
+ void reject() override;
+ void onAddDataInput();
+ void onRemoveDataInput();
+ void onEditDataInput();
+ void onActivated(const QModelIndex &index);
+ void onSelectionChanged();
+ void onSortOrderChanged(int column, Qt::SortOrder order);
+ void onReplaceSelected();
+ void onReplaceAll();
+ void onElementSelectionChanged();
+ void onFilterTypeChanged(int index);
+ void onSearchTextChanged();
+
+private:
+ Ui::DataInputListDlg *m_ui;
+ QMap<QString, CDataInputDialogItem *> m_dataInputs;
+ QMap<QString, CDataInputDialogItem *> *m_actualDataInputs;
+ int m_currentDataInputIndex;
+ QString m_currentDataInputName;
+ QStandardItemModel *m_tableContents;
+ QStandardItemModel *m_infoContents;
+ bool m_goToAdd;
+ int m_sortColumn;
+ Qt::SortOrder m_sortOrder;
+ QString m_mostRecentlyAdded;
+ EDataType m_defaultType;
+ QVector<EDataType> m_acceptedTypes;
+ QPointer<DataInputSelectView> m_dataInputChooserView;
+ // -1 all types, 0... matches EDataType enum
+ int m_typeFilter = -1;
+ QString m_searchString;
+ bool m_deletedDiInUse = false;
+ bool m_diHasBeenEdited = false;
+
+ QAction *m_replaceSelectedAction;
+ QAction *m_replaceAllAction;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputListDlg.ui b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.ui
new file mode 100644
index 00000000..3693032e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputListDlg.ui
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DataInputListDlg</class>
+ <widget class="QDialog" name="DataInputListDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1024</width>
+ <height>512</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Data Inputs</string>
+ </property>
+ <property name="accessibleName">
+ <string>DataInputListDlg</string>
+ </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">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget_2" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>24</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_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="QComboBox" name="typeFilterCombo">
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContentsOnFirstShow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="searchField">
+ <property name="placeholderText">
+ <string>[search]</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>200</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBoxAddEditRemove">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::NoButton</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>8</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <widget class="QTableView" name="tableView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>5</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>1</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::NoContextMenu</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">background-color: white;</string>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTableView" name="elementInfo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>3</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_3" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>24</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>800</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <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>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DataInputListDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>380</x>
+ <y>490</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>255</x>
+ <y>255</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DataInputListDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>380</x>
+ <y>490</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>255</x>
+ <y>255</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.cpp b/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.cpp
new file mode 100644
index 00000000..baacb744
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.cpp
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** 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 "DataInputSelectModel.h"
+#include "StudioUtils.h"
+#include "StudioObjectTypes.h"
+
+const int DataInputSelectModel::TypeRole = Qt::UserRole + 1;
+
+DataInputSelectModel::DataInputSelectModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+DataInputSelectModel::~DataInputSelectModel()
+{
+}
+
+QString DataInputSelectModel::getActiveIconPath() const
+{
+ return StudioUtils::resourceImageUrl()
+ + CStudioObjectTypes::GetNormalIconName(OBJTYPE_DATAINPUT);
+}
+
+QString DataInputSelectModel::getInactiveIconPath() const
+{
+ return StudioUtils::resourceImageUrl()
+ + CStudioObjectTypes::GetDisabledIconName(OBJTYPE_DATAINPUT);
+}
+
+QVariant DataInputSelectModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() >= m_diTypePairList.size() || index.row() < 0)
+ return QVariant();
+
+ QPair<QString, QString> pair = m_diTypePairList.at(index.row());
+ if (role == Qt::DisplayRole)
+ return QVariant::fromValue(QString(pair.first));
+ else if (role == TypeRole)
+ return QVariant::fromValue(QString(pair.second));
+
+ return QVariant();
+}
+
+void DataInputSelectModel::setData(const QVector<QPair<QString, QString>> &data)
+{
+ // need explicit begin/end in order to make model content update
+ // in UI also when datainput chooser widget parent is (QWidget-based)
+ // timeline toolbar or slideview
+ beginResetModel();
+ m_diTypePairList = data;
+ endResetModel();
+}
+
+void DataInputSelectModel::clear()
+{
+ beginResetModel();
+ m_diTypePairList.clear();
+ endResetModel();
+}
+
+QHash<int, QByteArray> DataInputSelectModel::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
+ roles.insert(TypeRole, QByteArray("datatype"));
+ return roles;
+}
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.h b/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.h
new file mode 100644
index 00000000..6e0ef085
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputSelectModel.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** 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 DATAINPUTSELECTMODEL_H
+#define DATAINPUTSELECTMODEL_H
+
+#include <QtCore/qstringlistmodel.h>
+
+class DataInputSelectModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int fixedItemCount MEMBER m_fixCount NOTIFY fixCountChanged)
+public:
+ Q_INVOKABLE QString getActiveIconPath() const;
+ Q_INVOKABLE QString getInactiveIconPath() const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ Q_UNUSED(parent);
+ return m_diTypePairList.size();
+ }
+ void setData(const QVector<QPair<QString, QString>> &data);
+ void clear();
+ QHash<int, QByteArray> roleNames() const;
+ explicit DataInputSelectModel(QObject *parent = nullptr);
+ virtual ~DataInputSelectModel();
+ void setFixedItemCount(int count) { m_fixCount = count; }
+ int getFixedItemCount() const { return m_fixCount; }
+ void showFixedItems(bool show) { m_showFixedItems = show; }
+ bool getShowFixedItems() const { return m_showFixedItems; }
+Q_SIGNALS:
+ void fixCountChanged();
+
+private:
+ int m_fixCount = 0;
+ bool m_showFixedItems = true;
+ QVector<QPair<QString, QString>> m_diTypePairList;
+ static const int TypeRole;
+
+};
+#endif // DATAINPUTSELECTMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputSelectView.cpp b/src/Authoring/Qt3DStudio/Application/DataInputSelectView.cpp
new file mode 100644
index 00000000..5d37b0af
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputSelectView.cpp
@@ -0,0 +1,238 @@
+/****************************************************************************
+**
+** 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 <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+
+#include "DataInputSelectView.h"
+#include "StudioPreferences.h"
+#include "StudioUtils.h"
+#include "Literals.h"
+#include "MainFrm.h"
+#include "DataInputListDlg.h"
+#include "DataInputDlg.h"
+#include "StudioApp.h"
+
+// Empty acceptedTypes vector means all types are accepted
+DataInputSelectView::DataInputSelectView(const QVector<EDataType> &acceptedTypes, QWidget *parent)
+ : QQuickWidget(parent)
+ , m_model(new DataInputSelectModel(this))
+ , m_defaultType(EDataType::DataTypeFloat)
+ , m_matchingTypes(acceptedTypes)
+ , m_dataInputList(QVector<QPair<QString, int>>())
+{
+ if (!m_matchingTypes.isEmpty())
+ m_defaultType = m_matchingTypes[0];
+
+ setWindowTitle(tr("Datainputs"));
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
+ setResizeMode(QQuickWidget::SizeRootObjectToView);
+ QTimer::singleShot(0, this, &DataInputSelectView::initialize);
+}
+
+DataInputSelectView::~DataInputSelectView()
+{
+}
+
+void DataInputSelectView::setData(const QVector<QPair<QString, int>> &dataInputList,
+ const QString &currentController, int handle, int instance)
+{
+ m_handle = handle;
+ m_instance = instance;
+ m_currController = currentController;
+ m_dataInputList = dataInputList;
+ updateData();
+}
+
+void DataInputSelectView::setMatchingTypes(const QVector<EDataType> &matchingTypes)
+{
+ m_matchingTypes = matchingTypes;
+ if (!m_matchingTypes.isEmpty())
+ m_defaultType = m_matchingTypes[0];
+ updateData();
+}
+
+void DataInputSelectView::updateData()
+{
+ m_selection = -1;
+
+ m_model->setFixedItemCount(0);
+ QVector<QPair<QString, QString>> dataInputs;
+ m_model->clear();
+
+ if (m_model->getShowFixedItems()) {
+ dataInputs.append({getAddNewDataInputString(), {}});
+ m_model->setFixedItemCount(m_model->getFixedItemCount() + 1);
+
+ dataInputs.append({getNoneString(), {}});
+ m_model->setFixedItemCount(m_model->getFixedItemCount() + 1);
+ }
+
+ for (auto &i : qAsConst(m_dataInputList)) {
+ bool isCurrentCtrlr = i.first == m_currController;
+ if (i.first.contains(m_searchString) || !m_searchString.size() || isCurrentCtrlr) {
+ if (m_typeFilter == DataInputTypeFilter::AllTypes
+ || (m_typeFilter == DataInputTypeFilter::MatchingTypes
+ && m_matchingTypes.contains((EDataType)i.second))
+ || m_typeFilter == (EDataType)i.second || isCurrentCtrlr) {
+ dataInputs.append({i.first, getDiTypeStr(i.second)});
+ if (isCurrentCtrlr) {
+ m_selection = dataInputs.size() - 1;
+ dataInputs.last().first.append(tr(" (Current)"));
+ }
+ }
+ }
+ }
+
+ m_model->setData(dataInputs);
+}
+
+QString DataInputSelectView::getDiTypeStr(int type)
+{
+ switch (static_cast<EDataType>(type)) {
+ case EDataType::DataTypeBoolean:
+ return tr("Boolean");
+ break;
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ case EDataType::DataTypeEvaluator:
+ return tr("Evaluator");
+ break;
+#endif
+ case EDataType::DataTypeFloat:
+ return tr("Float");
+ break;
+ case EDataType::DataTypeRangedNumber:
+ return tr("Ranged Number");
+ break;
+ case EDataType::DataTypeString:
+ return tr("String");
+ break;
+ case EDataType::DataTypeVariant:
+ return tr("Variant");
+ break;
+ case EDataType::DataTypeVector2:
+ return tr("Vector2");
+ break;
+ case EDataType::DataTypeVector3:
+ return tr("Vector3");
+ break;
+ default:
+ return {};
+ Q_ASSERT(false);
+ break;
+ }
+}
+
+void DataInputSelectView::showEvent(QShowEvent *event)
+{
+ QQuickWidget::showEvent(event);
+}
+
+void DataInputSelectView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape)
+ QTimer::singleShot(0, this, &DataInputSelectView::close);
+
+ QQuickWidget::keyPressEvent(event);
+}
+
+void DataInputSelectView::setSelection(int index)
+{
+ if (m_selection != index) {
+ m_selection = index;
+ QString sel = m_model->data(m_model->index(index), Qt::DisplayRole).toString();
+ if (sel != getAddNewDataInputString()) {
+ // do not set the value if it has not changed
+ if (sel != m_currController && !(sel == getNoneString() && !m_currController.size())) {
+ Q_EMIT dataInputChanged(m_handle, m_instance, sel);
+ Q_EMIT selectedChanged();
+ }
+ } else {
+ CDataInputListDlg dataInputDlg(&g_StudioApp.m_dataInputDialogItems, true, nullptr,
+ m_defaultType, m_matchingTypes);
+ dataInputDlg.exec();
+
+ if (dataInputDlg.result() == QDialog::Accepted) {
+ m_mostRecentlyAdded = dataInputDlg.getAddedDataInput();
+ if (m_mostRecentlyAdded.size()) {
+ CDataInputDialogItem *diItem = g_StudioApp.m_dataInputDialogItems.value(
+ m_mostRecentlyAdded);
+ if (m_matchingTypes.isEmpty()
+ || (diItem && m_matchingTypes.contains(
+ static_cast<EDataType>(diItem->type)))) {
+ Q_EMIT dataInputChanged(m_handle, m_instance, m_mostRecentlyAdded);
+ }
+ }
+ g_StudioApp.saveDataInputsToProjectFile();
+ }
+ }
+ QTimer::singleShot(0, this, &DataInputSelectView::close);
+ }
+}
+
+void DataInputSelectView::setSearchString(const QString &string)
+{
+ m_searchString = string;
+ updateData();
+}
+
+void DataInputSelectView::setTypeFilter(const int index)
+{
+ m_typeFilter = index;
+ updateData();
+ Q_EMIT filterChanged();
+}
+
+void DataInputSelectView::focusOutEvent(QFocusEvent *event)
+{
+ QQuickWidget::focusOutEvent(event);
+ QTimer::singleShot(0, this, &DataInputSelectView::close);
+}
+
+bool DataInputSelectView::toolTipsEnabled() const
+{
+ return CStudioPreferences::ShouldShowTooltips();
+}
+
+void DataInputSelectView::setCurrentController(const QString &currentController)
+{
+ m_currController = currentController;
+ // Need to update the entire data as being current controller affects if the item is visible
+ updateData();
+}
+
+void DataInputSelectView::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_resDir"),
+ StudioUtils::resourceImageUrl());
+ rootContext()->setContextProperty(QStringLiteral("_parentView"), this);
+ rootContext()->setContextProperty(QStringLiteral("_dataInputSelectModel"), m_model);
+ engine()->addImportPath(StudioUtils::qmlImportPath());
+ setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/DataInputChooser.qml")));
+}
diff --git a/src/Authoring/Qt3DStudio/Application/DataInputSelectView.h b/src/Authoring/Qt3DStudio/Application/DataInputSelectView.h
new file mode 100644
index 00000000..c73388aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DataInputSelectView.h
@@ -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$
+**
+****************************************************************************/
+#ifndef DATAINPUTSELECTDLG_H
+#define DATAINPUTSELECTDLG_H
+
+#include <QtQuickWidgets/qquickwidget.h>
+#include <QtCore/qstringlistmodel.h>
+#include "DataInputSelectModel.h"
+#include "DataInputDlg.h"
+
+class DataInputSelectModel;
+
+enum DataInputTypeFilter {
+ MatchingTypes = -2,
+ AllTypes = -1,
+};
+
+class DataInputSelectView : public QQuickWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(int selected MEMBER m_selection NOTIFY selectedChanged)
+ Q_PROPERTY(int typeFilter MEMBER m_typeFilter NOTIFY filterChanged)
+public:
+ explicit DataInputSelectView(const QVector<EDataType> &acceptedTypes,
+ QWidget *parent = nullptr);
+ ~DataInputSelectView();
+ void setData(const QVector<QPair<QString, int>> &dataInputList,
+ const QString &currentController,
+ int handle = 0, int instance = 0);
+ void setMatchingTypes(const QVector<EDataType> &matchingTypes);
+ QString getAddNewDataInputString() { return tr("[Add New Data Input]"); }
+ QString getNoneString() { return tr("[None]"); }
+ DataInputSelectModel *getModel() const { return m_model; }
+ int instance() const { return m_instance; }
+ int handle() const { return m_handle; }
+
+ Q_INVOKABLE void setSelection(int index);
+ Q_INVOKABLE int selection() const { return m_selection; }
+ Q_INVOKABLE void setSearchString(const QString &string);
+ Q_INVOKABLE void setTypeFilter(const int index);
+ Q_INVOKABLE bool toolTipsEnabled() const;
+
+ void setCurrentController(const QString &currentController);
+
+Q_SIGNALS:
+ void dataInputChanged(int handle, int instance, const QString &selected);
+ void selectedChanged();
+ void filterChanged();
+
+protected:
+ void focusOutEvent(QFocusEvent *event) override;
+ void showEvent(QShowEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void initialize();
+ void updateData();
+ QString getDiTypeStr(int type);
+ int m_handle = 0;
+ int m_instance = 0;
+ DataInputSelectModel *m_model = nullptr;
+ int m_selection = -1;
+ QString m_currController;
+ QString m_mostRecentlyAdded;
+ EDataType m_defaultType;
+ QVector<EDataType> m_matchingTypes;
+ // 0... matches EDataType enum
+ int m_typeFilter = DataInputTypeFilter::MatchingTypes;
+ QVector<QPair<QString, int>> m_dataInputList;
+ QString m_searchString;
+};
+
+#endif // DATAINPUTSELECTDLG_H
diff --git a/src/Authoring/Qt3DStudio/Application/DurationEditDlg.cpp b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.cpp
new file mode 100644
index 00000000..f0707a56
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.cpp
@@ -0,0 +1,194 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** 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 "ui_DurationEditDlg.h"
+#include "DurationEditDlg.h"
+#include "TimeEnums.h"
+#include <QtGui/qvalidator.h>
+
+CDurationEditDlg::CDurationEditDlg(QWidget *parent)
+ : QDialog(parent)
+ , m_ui(new Ui::DurationEditDlg)
+{
+ m_ui->setupUi(this);
+ setWindowFlag(Qt::WindowContextHelpButtonHint, false); // remove '?' from the dialog title bar
+
+ QIntValidator *minValidator = new QIntValidator(this);
+ minValidator->setRange(0, 9999);
+ m_ui->lineEditMinutes->setValidator(minValidator);
+ m_ui->lineEditEndMinutes->setValidator(minValidator);
+ QIntValidator *secValidator = new QIntValidator(this);
+ secValidator->setRange(0, 59);
+ m_ui->lineEditSeconds->setValidator(secValidator);
+ m_ui->lineEditEndSeconds->setValidator(secValidator);
+ QIntValidator *msecValidator = new QIntValidator(this);
+ msecValidator->setRange(0, 999);
+ m_ui->lineEditMilliseconds->setValidator(msecValidator);
+ m_ui->lineEditEndMilliseconds->setValidator(msecValidator);
+
+ connect(m_ui->lineEditMinutes, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onStartTimeChanged);
+ connect(m_ui->lineEditSeconds, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onStartTimeChanged);
+ connect(m_ui->lineEditMilliseconds, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onStartTimeChanged);
+
+ connect(m_ui->lineEditEndMinutes, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onEndTimeChanged);
+ connect(m_ui->lineEditEndSeconds, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onEndTimeChanged);
+ connect(m_ui->lineEditEndMilliseconds, &QLineEdit::textEdited,
+ this, &CDurationEditDlg::onEndTimeChanged);
+}
+
+CDurationEditDlg::~CDurationEditDlg()
+{
+ delete m_ui;
+}
+
+/**
+ * Initializes and shows the Duration Edit Dialog Box.
+ * @param startTime is the initial start time, which will be shown when the time edit
+ * dialog box pops up
+ * @param endTime is the initial end time, which will be shown when the time edit
+ * dialog box pops up
+ * @param inCallback is the target object for the callbacks
+ */
+void CDurationEditDlg::showDialog(long startTime, long endTime, ITimeChangeCallback *inCallback)
+{
+ m_Callback = inCallback;
+
+ // Set initial values to dialog
+ formatTime(startTime, true);
+ formatTime(endTime, false);
+
+ exec();
+}
+
+void CDurationEditDlg::formatTime(long inTime, bool startTime)
+{
+ long mins = 0;
+ long secs = 0;
+ long mils = 0;
+
+ if (inTime != 0) {
+ mins = inTime % 3600000 / 60000;
+ secs = inTime % 60000 / 1000;
+ mils = inTime % 1000;
+ }
+
+ // display milliseconds in 3 digits (5 -> 005)
+ QString milsStr = QString("%1").arg(mils, 3, 10, QChar('0'));
+
+ if (startTime) {
+ m_ui->lineEditMinutes->setText(QString::number(mins));
+ m_ui->lineEditSeconds->setText(QString::number(secs));
+ m_ui->lineEditMilliseconds->setText(milsStr);
+
+ // Select the biggest non-zero unit
+ if (mins > 0) {
+ m_ui->lineEditMinutes->setFocus();
+ m_ui->lineEditMinutes->selectAll();
+ } else if (secs > 0) {
+ m_ui->lineEditSeconds->setFocus();
+ m_ui->lineEditSeconds->selectAll();
+ } else {
+ m_ui->lineEditMilliseconds->setFocus();
+ m_ui->lineEditMilliseconds->selectAll();
+ }
+ } else {
+ m_ui->lineEditEndMinutes->setText(QString::number(mins));
+ m_ui->lineEditEndSeconds->setText(QString::number(secs));
+ m_ui->lineEditEndMilliseconds->setText(milsStr);
+ }
+}
+
+void CDurationEditDlg::accept()
+{
+ m_Callback->Commit();
+ QDialog::accept();
+}
+
+void CDurationEditDlg::reject()
+{
+ m_Callback->Rollback();
+ QDialog::reject();
+}
+
+void CDurationEditDlg::updateObjectTime(long inTime, bool startTime)
+{
+ if (m_Callback) {
+ if (startTime)
+ m_Callback->ChangeStartTime(inTime); // Update Start Time
+ else
+ m_Callback->ChangeEndTime(inTime); // Update End Time
+ }
+}
+
+void CDurationEditDlg::onStartTimeChanged()
+{
+ long min = m_ui->lineEditMinutes->text().toInt();
+ long sec = m_ui->lineEditSeconds->text().toInt();
+ long msec = m_ui->lineEditMilliseconds->text().toInt();
+
+ long theGoToTime = min * 60000 + sec * 1000 + msec;
+
+ // Go to the time specified in the start time edit display
+ updateObjectTime(theGoToTime, true);
+
+ // If max number of digits reached in a number field, select the next
+ if (m_ui->lineEditMinutes->hasFocus() && min > 999) {
+ m_ui->lineEditSeconds->setFocus();
+ m_ui->lineEditSeconds->selectAll();
+ } else if (m_ui->lineEditSeconds->hasFocus() && sec > 9) {
+ m_ui->lineEditMilliseconds->setFocus();
+ m_ui->lineEditMilliseconds->selectAll();
+ }
+}
+
+void CDurationEditDlg::onEndTimeChanged()
+{
+ long min = m_ui->lineEditEndMinutes->text().toInt();
+ long sec = m_ui->lineEditEndSeconds->text().toInt();
+ long msec = m_ui->lineEditEndMilliseconds->text().toInt();
+
+ long theGoToTime = min * 60000 + sec * 1000 + msec;
+
+ // Go to the time specified in the end time edit display
+ updateObjectTime(theGoToTime, false);
+
+ // If max number of digits reached in a number field, select the next
+ if (m_ui->lineEditEndMinutes->hasFocus() && min > 999) {
+ m_ui->lineEditEndSeconds->setFocus();
+ m_ui->lineEditEndSeconds->selectAll();
+ } else if (m_ui->lineEditEndSeconds->hasFocus() && sec > 9) {
+ m_ui->lineEditEndMilliseconds->setFocus();
+ m_ui->lineEditEndMilliseconds->selectAll();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Application/DurationEditDlg.h b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.h
new file mode 100644
index 00000000..ddb42ec0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** 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 DURATION_EDIT_DIALOG_H
+#define DURATION_EDIT_DIALOG_H
+
+#include <QtWidgets/qdialog.h>
+
+class IDoc;
+
+class ITimeChangeCallback
+{
+public:
+ virtual ~ITimeChangeCallback() {}
+ virtual void ChangeStartTime(long) = 0;
+ virtual void ChangeEndTime(long) = 0;
+ virtual void Commit() = 0;
+ virtual void Rollback() = 0;
+};
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class DurationEditDlg;
+}
+QT_END_NAMESPACE
+
+class CDurationEditDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ CDurationEditDlg(QWidget *parent = nullptr);
+ ~CDurationEditDlg() override;
+
+ void showDialog(long startTime, long endTime, ITimeChangeCallback *inCallback = nullptr);
+
+public Q_SLOTS:
+ void accept() override;
+ void reject() override;
+
+private:
+ void onStartTimeChanged();
+ void onEndTimeChanged();
+
+ void formatTime(long inTime, bool startTime);
+ void updateObjectTime(long inTime, bool startTime);
+
+ Ui::DurationEditDlg *m_ui;
+ ITimeChangeCallback *m_Callback = nullptr;
+};
+#endif // DURATION_EDIT_DIALOG_H
diff --git a/src/Authoring/Qt3DStudio/Application/DurationEditDlg.ui b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.ui
new file mode 100644
index 00000000..4f6bb7de
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/DurationEditDlg.ui
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DurationEditDlg</class>
+ <widget class="QDialog" name="DurationEditDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>343</width>
+ <height>152</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Set Timebar Start / End Time</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <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">
+ <item>
+ <widget class="QWidget" name="startTimeLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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="labelStartTime">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Start time</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditMinutes">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditSeconds">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditMilliseconds">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="endTimeLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <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="labelEndTime">
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>End time</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditEndMinutes">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditEndSeconds">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditEndMilliseconds">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="labelsLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <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>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>120</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="LabelM">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>min</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelS">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>sec</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelMs">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>ms</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonBoxLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_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>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </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>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DurationEditDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>207</x>
+ <y>128</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>149</x>
+ <y>75</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DurationEditDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>207</x>
+ <y>128</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>149</x>
+ <y>75</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.cpp b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.cpp
new file mode 100644
index 00000000..8a451e9a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.cpp
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** 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 "FilterVariantsDlg.h"
+#include "StudioPreferences.h"
+#include "FilterVariantsModel.h"
+
+#include <QtWidgets/qaction.h>
+#include <QtCore/qtimer.h>
+#include <QtQml/qqmlcontext.h>
+
+FilterVariantsDlg::FilterVariantsDlg(QWidget *parent, QAction *action, int actionSize,
+ QWidget *actionWidget)
+ : QQuickWidget(parent)
+ , m_model(new FilterVariantsModel(m_variantsFilter, this))
+ , m_action(action)
+ , m_actionSize(actionSize)
+ , m_actionWidget(actionWidget)
+{
+ setWindowTitle(tr("Filter variants"));
+ QTimer::singleShot(0, this, &FilterVariantsDlg::initialize);
+}
+
+void FilterVariantsDlg::initialize()
+{
+ CStudioPreferences::setQmlContextProperties(rootContext());
+ rootContext()->setContextProperty(QStringLiteral("_view"), this);
+ rootContext()->setContextProperty(QStringLiteral("_model"), m_model);
+ rootContext()->setContextProperty(QStringLiteral("_utils"), &m_qmlUtils);
+ setSource(QUrl(QStringLiteral("qrc:/Application/FilterVariantsDlg.qml")));
+}
+
+QString FilterVariantsDlg::filterStr() const
+{
+ QString ret;
+ if (!m_variantsFilter.isEmpty()) {
+ const auto groups = m_variantsFilter.keys();
+ for (auto &g : groups) {
+ const auto group = m_variantsFilter[g];
+ for (auto &tag : group)
+ ret.append(g + QLatin1Char(':') + tag + QLatin1Char(','));
+ }
+
+ if (!m_variantsFilter.isEmpty())
+ ret.chop(1);
+ }
+
+ return ret;
+}
+
+void FilterVariantsDlg::clearFilter()
+{
+ m_variantsFilter.clear();
+}
+
+int FilterVariantsDlg::actionSize() const
+{
+ return m_actionSize;
+}
+
+void FilterVariantsDlg::showEvent(QShowEvent *event)
+{
+ m_model->refresh();
+ QQuickWidget::showEvent(event);
+}
+
+void FilterVariantsDlg::focusOutEvent(QFocusEvent *e)
+{
+ QQuickWidget::focusOutEvent(e);
+
+ if (!m_actionWidget->underMouse()) {
+ m_action->setChecked(false);
+ QTimer::singleShot(0, this, &QQuickWidget::close);
+ }
+}
+
+void FilterVariantsDlg::keyPressEvent(QKeyEvent *e)
+{
+ if (e->key() == Qt::Key_Escape) {
+ m_action->setChecked(false);
+ QTimer::singleShot(0, this, &QQuickWidget::close);
+ }
+
+ QQuickWidget::keyPressEvent(e);
+}
diff --git a/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.h b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.h
new file mode 100644
index 00000000..cc7eab1e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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 FILTER_VARIANTS_DLG_H
+#define FILTER_VARIANTS_DLG_H
+
+#include "QmlUtils.h"
+
+#include <QtQuickWidgets/qquickwidget.h>
+
+class FilterVariantsModel;
+
+class FilterVariantsDlg : public QQuickWidget
+{
+ Q_OBJECT
+
+public:
+ explicit FilterVariantsDlg(QWidget *parent, QAction *action, int actionSize,
+ QWidget *actionWidget);
+
+ Q_INVOKABLE int actionSize() const;
+
+ QString filterStr() const;
+ void clearFilter();
+
+protected:
+ void focusOutEvent(QFocusEvent *e) override;
+ void keyPressEvent(QKeyEvent *e) override;
+
+private:
+ void showEvent(QShowEvent *event) override;
+
+ void initialize();
+
+ QmlUtils m_qmlUtils;
+ QHash<QString, QSet<QString> > m_variantsFilter; // key: group, value: tags
+ FilterVariantsModel *m_model = nullptr;
+ QAction *m_action = nullptr;
+ int m_actionSize = 0; // width/height of the action icon
+ QWidget *m_actionWidget = nullptr;
+};
+
+#endif // FILTER_VARIANTS_DLG_H
diff --git a/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.qml b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.qml
new file mode 100644
index 00000000..ec626c4b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/FilterVariantsDlg.qml
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+Rectangle {
+ id: root
+ width: 400
+ height: 280
+ color: _backgroundColor
+ border.color: _studioColor3
+
+ // hider for the border segment between the icon and the dialog
+ Rectangle {
+ color: _backgroundColor
+ x: 1
+ width: _view.actionSize() - 2 // -1px from each side
+ height: 1
+ }
+
+ Item {
+ height: 25
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 10
+
+ Text {
+ text: qsTr("Select variant filtering")
+ color: _studioColor4
+ font.pixelSize: _fontSize
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ ToolButton { // clear button
+ width: 60
+ height: 25
+ anchors.right: parent.right
+ text: qsTr("Clear")
+ font.pixelSize: _fontSize
+
+ onClicked: _model.clearAll();
+ }
+ }
+
+ Flickable {
+ anchors.top: parent.top
+ anchors.topMargin: 35
+ anchors.fill: parent
+ contentWidth: variantsColumn.width
+ contentHeight: variantsColumn.height
+ flickableDirection: Flickable.VerticalFlick
+ clip: true
+
+ ScrollBar.vertical: ScrollBar {}
+
+ Column {
+ id: variantsColumn
+ spacing: 10
+ padding: 10
+
+ Repeater { // groups
+ id: tagsRepeater
+ model: _model
+ 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 { // a row of a group and its tags
+ spacing: 5
+
+ readonly property var tagsModel: model.tags
+ readonly property var groupModel: model
+ readonly property int groupLabelWidth: tLabel.implicitWidth + 10
+
+ Rectangle { // group button
+ width: tagsRepeater.maxGroupLabelWidth;
+ height: 25
+ color: groupMouseArea.pressed ? model.color : _backgroundColor
+ border.color: model.color
+
+ Text {
+ id: tLabel
+ text: model.group
+ font.pixelSize: _fontSize
+ color: groupMouseArea.pressed ? _backgroundColor : model.color
+ elide: Text.ElideRight
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ id: groupMouseArea
+ anchors.fill: parent;
+ onClicked: _model.toggleGroupState(model.group);
+ enabled: tagsModel && tagsModel.rowCount() > 0
+ }
+ }
+
+ Flow { // group tags
+ width: root.width - tagsRepeater.maxGroupLabelWidth - 15
+ spacing: 5
+
+ Repeater {
+ model: tagsModel
+
+ Loader {
+ readonly property var tagsModel: model
+ readonly property var grpModel: groupModel
+ sourceComponent: tagComponent
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: tagComponent
+
+ Rectangle {
+ property bool toggled: tagsModel ? tagsModel.selected : false
+ property color grpColor: grpModel ? grpModel.color : ""
+ property bool isBright: grpModel ? _utils.isBright(grpModel.color) : false
+
+ width: Math.max(tLabel.width + 10, 60)
+ height: 25
+ color: tagMouseArea.pressed ? Qt.lighter(grpColor, 1.2)
+ : toggled ? grpColor : _backgroundColor
+ border.color: toggled ? _studioColor4 : grpColor;
+
+ Text {
+ id: tLabel
+ anchors.centerIn: parent
+ text: tagsModel ? tagsModel.tag : ""
+ font.pixelSize: _fontSize
+ color: (toggled || tagMouseArea.pressed)
+ ? (isBright ? _studioColor1 : _textColor) : _studioColor4
+ }
+
+ MouseArea {
+ id: tagMouseArea
+ anchors.fill: parent
+ onClicked: {
+ toggled = !toggled;
+ _model.setTagState(grpModel.group, tagsModel.tag, toggled);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.cpp b/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.cpp
new file mode 100644
index 00000000..ad3efac7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** 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 "FilterVariantsModel.h"
+#include "VariantsTagModel.h"
+#include "StudioApp.h"
+#include "Views.h"
+#include "MainFrm.h"
+#include "Core.h"
+#include "VariantTagDialog.h"
+#include "TimelineWidget.h"
+
+FilterVariantsModel::FilterVariantsModel(VariantsFilterT &variantsFilter, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_variantsFilter(variantsFilter)
+{
+
+}
+
+void FilterVariantsModel::refresh()
+{
+ beginResetModel();
+
+ // delete tag models
+ for (auto &g : qAsConst(m_data))
+ delete g.m_tagsModel;
+
+ m_data.clear();
+
+ // 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) {
+ VariantsGroupData 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) {
+ QString tag = variantsDef[group].m_tags[i];
+ bool state = m_variantsFilter.contains(group) && m_variantsFilter[group].contains(tag);
+ tags.append({tag, state});
+ }
+ g.m_tagsModel = new VariantsTagModel(tags);
+ m_data.push_back(g);
+ }
+
+ endResetModel();
+}
+
+int FilterVariantsModel::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 FilterVariantsModel::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 == TagsRole)
+ return QVariant::fromValue(m_data.at(index.row()).m_tagsModel);
+
+ return QVariant();
+}
+
+void FilterVariantsModel::setTagState(const QString &group, const QString &tag, bool selected)
+{
+ if (selected)
+ m_variantsFilter[group].insert(tag);
+ else
+ m_variantsFilter[group].remove(tag);
+
+ bool filtered = false;
+ for (auto &g : qAsConst(m_variantsFilter)) {
+ if (!g.isEmpty()) {
+ filtered = true;
+ break;
+ }
+ }
+
+ g_StudioApp.GetViews()->getMainFrame()->getTimelineWidget()->updateVariantsFiltering();
+ g_StudioApp.GetViews()->getMainFrame()->updateToolbarVariantsIcons(filtered);
+}
+
+void FilterVariantsModel::toggleGroupState(const QString &group)
+{
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+
+ if (m_variantsFilter[group].size() < variantsDef[group].m_tags.size()) {
+ // not all tags selected, select all
+ const auto tags = variantsDef[group].m_tags;
+ for (auto &t : tags)
+ m_variantsFilter[group].insert(t);
+ } else {
+ // all tags selected, select none
+ m_variantsFilter[group].clear();
+ }
+ refresh();
+
+ bool filtered = false;
+ for (auto &g : qAsConst(m_variantsFilter)) {
+ if (!g.isEmpty()) {
+ filtered = true;
+ break;
+ }
+ }
+
+ g_StudioApp.GetViews()->getMainFrame()->getTimelineWidget()->updateVariantsFiltering();
+ g_StudioApp.GetViews()->getMainFrame()->updateToolbarVariantsIcons(filtered);
+}
+
+void FilterVariantsModel::clearAll()
+{
+ m_variantsFilter.clear();
+ refresh();
+ g_StudioApp.GetViews()->getMainFrame()->getTimelineWidget()->updateVariantsFiltering();
+ g_StudioApp.GetViews()->getMainFrame()->updateToolbarVariantsIcons(false);
+}
+
+QHash<int, QByteArray> FilterVariantsModel::roleNames() const
+{
+ auto names = QAbstractListModel::roleNames();
+ names.insert(GroupTitleRole, "group");
+ names.insert(GroupColorRole, "color");
+ names.insert(TagsRole, "tags");
+ return names;
+}
+
+Qt::ItemFlags FilterVariantsModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ return Qt::ItemIsEditable;
+}
diff --git a/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.h b/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.h
new file mode 100644
index 00000000..86df5377
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/FilterVariantsModel.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** 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;
+
+using VariantsFilterT = QHash<QString, QSet<QString> >;
+
+class FilterVariantsModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit FilterVariantsModel(VariantsFilterT &variantsFilter, QObject *parent = nullptr);
+
+ enum Roles {
+ GroupTitleRole = Qt::UserRole + 1,
+ GroupColorRole,
+ TagsRole
+ };
+
+ struct VariantsGroupData
+ {
+ 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 toggleGroupState(const QString &group);
+ Q_INVOKABLE void clearAll();
+
+protected:
+ QHash<int, QByteArray> roleNames() const override;
+
+private:
+ VariantsFilterT &m_variantsFilter;
+ QVector<VariantsGroupData> m_data;
+};
+
+#endif // VARIANTSGROUPMODEL_H
diff --git a/src/Authoring/Qt3DStudio/Application/MsgRouter.cpp b/src/Authoring/Qt3DStudio/Application/MsgRouter.cpp
new file mode 100644
index 00000000..fab4bc19
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/MsgRouter.cpp
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "MsgRouter.h"
+#include "StudioConst.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Views.h"
+
+#include <QtCore/qcoreapplication.h>
+
+static std::unique_ptr<CMsgRouter> static_theInstance;
+
+//==============================================================================
+/**
+ * Constructor
+ */
+CMsgRouter::CMsgRouter()
+{
+ qApp->installEventFilter(this);
+ m_eventType = QEvent::registerEventType();
+}
+
+//==============================================================================
+/**
+ * Destructor
+ */
+CMsgRouter::~CMsgRouter()
+{
+}
+
+CMsgRouter *CMsgRouter::GetInstance()
+{
+ if (!static_theInstance) {
+ static_theInstance.reset(new CMsgRouter);
+ }
+
+ return static_theInstance.get();
+}
+
+void CMsgRouter::blockMessages()
+{
+ qApp->removeEventFilter(this);
+}
+
+//==============================================================================
+/**
+ * Send a command to be executed asynchronously.
+ */
+void CMsgRouter::SendCommand(CCmd *inCommand, CCore *inCore)
+{
+ SMessageData *theMsgData = new SMessageData(m_eventType);
+ theMsgData->Method = &CMsgRouter::OnCommand;
+ theMsgData->Data = inCommand;
+ theMsgData->Data2 = inCore;
+
+ qApp->postEvent(qApp, theMsgData);
+}
+
+bool CMsgRouter::eventFilter(QObject *watched, QEvent *event)
+{
+ if (event->type() == m_eventType) {
+ SMessageData *theMsgData = reinterpret_cast<SMessageData *>(event);
+ (this->*(theMsgData->Method))(theMsgData);
+ return true;
+ }
+
+ return false;
+}
+
+//==============================================================================
+/**
+ * Windows entry point for processing generic messages.
+ */
+void CMsgRouter::OnAsyncNotification(SMessageData *inMessageData)
+{
+ try {
+ CRoutedMessageBase *theRoutedMsg = static_cast<CRoutedMessageBase *>(inMessageData->Data);
+ if (theRoutedMsg)
+ theRoutedMsg->Notify();
+ } catch (...) {
+ // Catch crashes in case the object is gone
+ }
+}
+
+//==============================================================================
+/**
+ * Main thread processing for processing command execution messages.
+ */
+void CMsgRouter::OnCommand(SMessageData *inMsgData)
+{
+ CCmd *theCommand = static_cast<CCmd *>(inMsgData->Data);
+ CCore *theCore = static_cast<CCore *>(inMsgData->Data2);
+ theCore->ExecuteCommand(theCommand, true);
+}
+
+
+
+CMsgRouter::SMessageData::SMessageData(int eventType) :
+ QEvent(static_cast<QEvent::Type>(eventType))
+{
+}
diff --git a/src/Authoring/Qt3DStudio/Application/MsgRouter.h b/src/Authoring/Qt3DStudio/Application/MsgRouter.h
new file mode 100644
index 00000000..efd7e6be
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/MsgRouter.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** 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_MSG_ROUTER_H
+#define INCLUDED_MSG_ROUTER_H 1
+#pragma once
+
+//==============================================================================
+// Defines
+//==============================================================================
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "StudioConst.h"
+
+#include <QEvent>
+#include <QObject>
+
+//==============================================================================
+// Forwards
+//==============================================================================
+class CCmd;
+class CCore;
+
+class CRoutedMessageBase
+{
+public:
+ CRoutedMessageBase() {}
+ virtual ~CRoutedMessageBase() {}
+
+ virtual void Notify() = 0;
+};
+
+template <typename TObject, typename TData>
+class CRoutedMessageImpl : public CRoutedMessageBase
+{
+protected:
+ typedef void (TObject::*TMethod)(const TData &);
+
+public:
+ TObject *m_Object;
+ TMethod m_Method;
+ TData m_Data;
+
+ CRoutedMessageImpl(TObject *inObject, TMethod inMethod, const TData &inData)
+ : m_Object(inObject)
+ , m_Method(inMethod)
+ , m_Data(inData)
+ {
+ }
+
+ virtual void Notify() { (m_Object->*m_Method)(m_Data); }
+};
+
+//==============================================================================
+/**
+ * Routes user-defined messages between different threads of an application.
+ */
+class CMsgRouter : public QObject
+{
+protected:
+ class SMessageData;
+
+public:
+ typedef void (CMsgRouter::*TMainThreadMethod)(SMessageData *inMessageData);
+
+public:
+ static CMsgRouter *GetInstance();
+ virtual ~CMsgRouter();
+
+ void SendCommand(CCmd *inCommand, CCore *inCore);
+ void blockMessages();
+
+protected:
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+ CMsgRouter(); ///< This is a singleton so the constructor is not public (call GetInstance)
+
+ class SMessageData : public QEvent
+ {
+ public:
+ SMessageData(int eventType);
+
+ TMainThreadMethod Method;
+ void *Data;
+ void *Data2;
+ };
+
+ void OnAsyncNotification(SMessageData *inMessageData);
+ void OnCommand(SMessageData *inMessageData);
+
+private:
+ int m_eventType;
+};
+
+#endif // INCLUDED_MSG_ROUTER_H
diff --git a/src/Authoring/Qt3DStudio/Application/PresentationFile.cpp b/src/Authoring/Qt3DStudio/Application/PresentationFile.cpp
new file mode 100644
index 00000000..cbc7f417
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/PresentationFile.cpp
@@ -0,0 +1,646 @@
+/****************************************************************************
+**
+** 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 "PresentationFile.h"
+#include "ProjectFile.h"
+#include "Dialogs.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "StudioUtils.h"
+#include "IDocumentReader.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include <QtCore/qfile.h>
+#include <QtCore/qsavefile.h>
+#include <QtXml/qdom.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qxmlstream.h>
+#include <QtCore/qlist.h>
+
+// This class provides utility static methods for working with presentation files (.uip). Old uip
+// functionality should be gradually moved here whenever feasible.
+
+// static
+QSize PresentationFile::readSize(const QString &uipPath)
+{
+ QFile file(uipPath);
+ file.open(QFile::Text | QFile::ReadOnly);
+ if (!file.isOpen()) {
+ qWarning() << file.errorString();
+ return QSize();
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ while (reader.readNextStartElement()) {
+ if (reader.name() == QLatin1String("ProjectSettings")) {
+ const auto attrs = reader.attributes();
+ return QSize(attrs.value(QLatin1String("presentationWidth")).toInt(),
+ attrs.value(QLatin1String("presentationHeight")).toInt());
+ }
+ }
+
+ return QSize();
+}
+
+/**
+ * Find all occurrences of a presentation Id in a .uip file and replace them with a new value
+ *
+ * @param uipPath presentation file path
+ * @param oldId the presentation Id to find
+ * @param newId the presentation Id to replace
+ */
+// static
+void PresentationFile::updatePresentationId(const QString &uipPath, const QString &oldId,
+ const QString &newId)
+{
+ QDomDocument domDoc;
+ QSaveFile file(uipPath);
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement rootElem = domDoc.documentElement();
+ QDomNodeList addNodes = rootElem.elementsByTagName(QStringLiteral("Add"));
+ QDomNodeList setNodes = rootElem.elementsByTagName(QStringLiteral("Set"));
+ bool updated = false;
+
+ const auto updateNodes = [&](const QDomNodeList &nodes) {
+ if (!nodes.isEmpty()) {
+ for (int i = 0; i < nodes.length(); ++i) {
+ QDomElement elem = nodes.at(i).toElement();
+ if (elem.attribute(QStringLiteral("sourcepath")) == oldId) {
+ elem.setAttribute(QStringLiteral("sourcepath"), newId);
+ updated = true;
+ }
+ if (elem.attribute(QStringLiteral("subpresentation")) == oldId) {
+ elem.setAttribute(QStringLiteral("subpresentation"), newId);
+ updated = true;
+ }
+ }
+ }
+ };
+
+ updateNodes(addNodes);
+ updateNodes(setNodes);
+
+ if (updated)
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+}
+
+/**
+ * Find all occurrences of a material name in a .uip file and replace them with a new value
+ *
+ * @param uipPath presentation file path
+ * @param oldName the material name to find
+ * @param newName the material name to replace
+ */
+// static
+void PresentationFile::renameMaterial(const QString &uipPath, const QString &oldName,
+ const QString &newName)
+{
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const auto sceneEditor = doc->getSceneEditor();
+
+ const auto absOldPath = sceneEditor->getFilePathFromMaterialName(oldName);
+ const auto absNewPath = sceneEditor->getFilePathFromMaterialName(newName);
+
+ const auto dir = QFileInfo(uipPath).dir();
+ const auto relOldPath = dir.relativeFilePath(absOldPath);
+ const auto relNewPath = dir.relativeFilePath(absNewPath);
+
+ auto refNewName = newName;
+ int slashIndex = newName.lastIndexOf(QLatin1Char('/'));
+ if (slashIndex != -1)
+ refNewName = newName.mid(slashIndex + 1);
+
+ QDomDocument domDoc;
+ QSaveFile file(uipPath);
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement rootElem = domDoc.documentElement();
+ QDomNodeList addNodes = rootElem.elementsByTagName(QStringLiteral("Add"));
+ QDomNodeList setNodes = rootElem.elementsByTagName(QStringLiteral("Set"));
+
+ bool updated = false;
+
+ QDomNodeList materialNodes = rootElem.elementsByTagName(QStringLiteral("Material"));
+
+ // Find the material container
+ QDomElement materialContainer;
+ for (int i = 0; i < materialNodes.length(); ++i) {
+ QDomElement elem = materialNodes.at(i).toElement();
+ if (elem.attribute(QStringLiteral("id")) == bridge->getMaterialContainerName()) {
+ materialContainer = elem;
+ break;
+ }
+ }
+
+ if (materialContainer.isNull())
+ return;
+
+ QDomNodeList containerNodes = materialContainer.childNodes();
+
+ // Store the material ids for further use and change the names and ids to new ones
+ QStringList materialIds;
+ for (int i = 0; i < containerNodes.length(); ++i) {
+ QDomElement elem = containerNodes.at(i).toElement();
+ materialIds.append(elem.attribute(QStringLiteral("id")));
+ if (elem.attribute(QStringLiteral("id")) == oldName) {
+ elem.setAttribute(QStringLiteral("id"), newName);
+ updated = true;
+ }
+ }
+
+ // Since rename can change the visible name only in the original presentation
+ // and retain the old id, we have to cross-reference the ids here to the ids
+ // logged previously. If a material has the same visible name as the old name,
+ // the id and the new name are stored
+ QVector<QPair<QString, QString>> materialRenames;
+
+ const auto renameReferencedMaterial = [&](QDomElement &elem) {
+ QString ref = elem.attribute(QStringLiteral("ref"));
+ if (elem.attribute(QStringLiteral("name")) == oldName
+ && !ref.isEmpty() && materialIds.contains(ref.mid(1))) {
+ materialRenames.append(QPair<QString, QString>(ref.mid(1), newName));
+ elem.setAttribute(QStringLiteral("ref"), QLatin1Char('#') + newName);
+ elem.setAttribute(QStringLiteral("name"), newName);
+ updated = true;
+ }
+ if (elem.attribute(QStringLiteral("sourcepath")) == relOldPath) {
+ elem.setAttribute(QStringLiteral("sourcepath"), relNewPath);
+ if (elem.hasAttribute(QStringLiteral("referencedmaterial"))) {
+ elem.setAttribute(QStringLiteral("referencedmaterial"),
+ QLatin1Char('#') + newName);
+ elem.setAttribute(QStringLiteral("name"), refNewName);
+ }
+ updated = true;
+ }
+ };
+
+ for (int i = 0; i < addNodes.length(); ++i) {
+ QDomElement elem = addNodes.at(i).toElement();
+ renameReferencedMaterial(elem);
+ }
+
+ for (int i = 0; i < setNodes.length(); ++i) {
+ QDomElement elem = setNodes.at(i).toElement();
+ renameReferencedMaterial(elem);
+ }
+
+ // New pass is needed to change the ids stored when changing the Add and Set Nodes
+ for (int i = 0; i < containerNodes.length(); ++i) {
+ for (auto &materialRename : qAsConst(materialRenames)) {
+ QDomElement elem = containerNodes.at(i).toElement();
+ if (elem.attribute(QStringLiteral("id")) == materialRename.first) {
+ elem.setAttribute(QStringLiteral("id"), materialRename.second);
+ updated = true;
+ }
+ }
+ }
+
+ if (updated)
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+}
+
+/**
+ * Find the project file path matching the given presentation file path
+ *
+ * @param uipPath presentation file path
+ * @return project file absolute path
+ */
+// static
+QString PresentationFile::findProjectFile(const QString &uipPath)
+{
+ QFileInfo fi(uipPath);
+
+ // first check if there is a uia in the same folder as the uip with the same name
+ QString uiaPath = fi.dir().absoluteFilePath(fi.completeBaseName() + QStringLiteral(".uia"));
+ if (QFile::exists(uiaPath))
+ return uiaPath;
+
+ // next search for a uia starting from uip folder and going up, the first found 1 is assumed
+ // to be the project file
+ QDir currDir = fi.dir();
+ const int MAX_SEARCH_DEPTH = 3; // scan up to 3 levels up (for performance reasons)
+ int searchDepth = 0;
+ do {
+ QDirIterator di(currDir.path(), QDir::NoDotAndDotDot | QDir::Files);
+ while (di.hasNext()) {
+ QFileInfo fi2 = di.next();
+ if (fi2.suffix() == QLatin1String("uia"))
+ return fi2.filePath();
+ }
+ ++searchDepth;
+ } while (searchDepth < MAX_SEARCH_DEPTH && currDir.cdUp());
+
+ return {};
+}
+
+// Get all available child assets source paths (materials, images, effects, etc).
+// The source paths returned are relative to the presentation file being parsed.
+// static
+void PresentationFile::getSourcePaths(const QFileInfo &uipSrc, const QFileInfo &uipTarget,
+ QHash<QString, QString> &outPathMap,
+ QString &outProjPathSrc,
+ QHash<QString, QString> &outPresentationNodes,
+ QSet<QString> &outDataInputs,
+ QSet<QString> &outDataOutputs)
+{
+ QDomDocument domDoc;
+ if (!StudioUtils::readFileToDomDocument(uipTarget.filePath(), domDoc))
+ return;
+
+ QVector<SubPresentationRecord> subpresentations;
+ QString uiaPath = findProjectFile(uipSrc.filePath());
+ if (!uiaPath.isEmpty()) {
+ outProjPathSrc = QFileInfo(uiaPath).path();
+ QString uipPathRelative = QFileInfo(uiaPath).dir().relativeFilePath(uipSrc.filePath());
+ ProjectFile::getPresentations(uiaPath, subpresentations, uipPathRelative);
+ } else {
+ outProjPathSrc = uipSrc.path();
+ }
+ QDir srcProjDir(outProjPathSrc);
+ QDir srcUipDir(uipSrc.path());
+
+ const auto convertPath = [&](const QString &path, bool forceProj = false) -> QString {
+ if (forceProj || path.startsWith(QLatin1String("./")))
+ return srcUipDir.relativeFilePath(srcProjDir.absoluteFilePath(path));
+ else
+ return path; // Assuming path is already presentation relative
+ };
+
+ // Map to cache effect and material properties during the presentation file parsing,
+ // as we don't yet know which ones are actually assets.
+ // Key: Material/effect class id
+ // Value: Set of material/effect properties
+ QHash<QString, QSet<QString>> matEffPropertyMap;
+
+ // search <Classes>
+ QDomElement classesElem = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Classes"));
+ for (QDomElement p = classesElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ const QString sourcepath = convertPath(p.attribute(QStringLiteral("sourcepath")));
+ if (!sourcepath.isEmpty() && !outPathMap.contains(sourcepath)) {
+ outPathMap.insert(sourcepath, {});
+
+ QFileInfo fi(sourcepath);
+ QByteArray ext = fi.suffix().toLatin1();
+
+ // if material or effect, find and add their dependents
+ if (CDialogs::IsMaterialFileExtension(ext.data()) ||
+ CDialogs::IsEffectFileExtension(ext.data())) {
+ QSet<QString> propertySet;
+ QHash<QString, QString> matEffPathMap;
+ g_StudioApp.GetCore()->GetDoc()->GetDocumentReader()
+ .ParseSourcePathsOutOfEffectFile(
+ uipSrc.path() + QStringLiteral("/") + sourcepath,
+ outProjPathSrc, true, matEffPathMap, propertySet);
+ // ParseSourcePathsOutOfEffectFile returns paths relative to project
+ QHashIterator<QString, QString> pathIter(matEffPathMap);
+ while (pathIter.hasNext()) {
+ pathIter.next();
+ outPathMap.insert(convertPath(pathIter.key(), true), pathIter.value());
+ }
+ matEffPropertyMap.insert(QStringLiteral("#") + p.attribute(QStringLiteral("id")),
+ propertySet);
+ }
+ }
+ }
+
+ std::function<void(const QDomElement &, bool)> parseDataInput;
+ parseDataInput = [&](const QDomElement &elem, bool parseChildren) {
+ const QString ctrlAtt = elem.attribute(QStringLiteral("controlledproperty"));
+ if (!ctrlAtt.isEmpty()) {
+ const QStringList dataInputs = ctrlAtt.split(QLatin1Char('$'));
+ for (auto &di : dataInputs) {
+ if (!di.isEmpty())
+ outDataInputs.insert(di.left(di.indexOf(QLatin1Char(' '))));
+ }
+ }
+ if (parseChildren) {
+ const QDomNodeList children = elem.childNodes();
+ for (int i = 0; i < children.count(); ++i)
+ parseDataInput(children.at(i).toElement(), true);
+ }
+ };
+
+ std::function<void(const QDomElement &, bool)> parseDataOutput;
+ parseDataOutput = [&](const QDomElement &elem, bool parseChildren) {
+ const QString obsAtt = elem.attribute(QStringLiteral("observedproperty"));
+ if (!obsAtt.isEmpty()) {
+ const QStringList dataOutputs = obsAtt.split(QLatin1Char('$'));
+ for (auto &dout : dataOutputs) {
+ if (!dout.isEmpty())
+ outDataOutputs.insert(dout.left(dout.indexOf(QLatin1Char(' '))));
+ }
+ }
+ if (parseChildren) {
+ const QDomNodeList children = elem.childNodes();
+ for (int i = 0; i < children.count(); ++i)
+ parseDataOutput(children.at(i).toElement(), true);
+ }
+ };
+ // mesh files for group imports, materials, and effects are found under <Graph>
+ QDomElement graphElement = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Graph"));
+
+ parseDataInput(graphElement, true);
+ parseDataOutput(graphElement, true);
+
+ QDomNodeList modelElems = graphElement.elementsByTagName(QStringLiteral("Model"));
+ for (int i = 0; i < modelElems.count(); ++i) {
+ QDomElement elem = modelElems.at(i).toElement();
+ const QString sourcePath = convertPath(elem.attribute(QStringLiteral("sourcepath")));
+ if (!sourcePath.isEmpty()) {
+ QFileInfo fi(sourcePath);
+ QByteArray ext = fi.suffix().toLatin1();
+ if (CDialogs::isMeshFileExtension(ext.data())) {
+ if (!outPathMap.contains(sourcePath))
+ outPathMap.insert(sourcePath, {});
+ continue;
+ }
+ }
+ }
+
+ // find material and effect files instance ids
+ QHash<QString, QString> matEffClassIdMap;
+ auto parseMatEffIds = [&matEffClassIdMap](const QDomNodeList& nodes) {
+ for (int i = 0; i < nodes.count(); ++i) {
+ QDomElement elem = nodes.at(i).toElement();
+ const QString classId = elem.attribute(QStringLiteral("class"));
+ if (!classId.isEmpty()) {
+ const QString id = elem.attribute(QStringLiteral("id"));
+ matEffClassIdMap.insert(QStringLiteral("#") + id, classId);
+ }
+ }
+ };
+
+ parseMatEffIds(graphElement.elementsByTagName(QStringLiteral("Effect")));
+ parseMatEffIds(graphElement.elementsByTagName(QStringLiteral("CustomMaterial")));
+
+ // search <Logic> -> <State> -> <Add>/<Set>
+ const auto parseNodes = [&](const QDomNodeList &nodes) {
+ for (int i = 0; i < nodes.count(); ++i) {
+ QDomElement elem = nodes.at(i).toElement();
+ const QString sourcePath = convertPath(elem.attribute(QStringLiteral("sourcepath")));
+ if (!sourcePath.isEmpty()) {
+ QFileInfo fi(sourcePath);
+ QByteArray ext = fi.suffix().toLatin1();
+ // supported types:
+ // images, custom mesh files for basic objects, import files, materialdef files
+ if (CDialogs::IsImageFileExtension(ext.data())
+ || CDialogs::isMeshFileExtension(ext.data())
+ || CDialogs::isImportFileExtension(ext.data())
+ || CDialogs::IsMaterialFileExtension(ext.data())) {
+ if (!outPathMap.contains(sourcePath))
+ outPathMap.insert(sourcePath, {});
+ } else {
+ // add layer subpresentations paths
+ auto *sp = std::find_if(
+ subpresentations.begin(), subpresentations.end(),
+ [&sourcePath](const SubPresentationRecord &spr) -> bool {
+ return spr.m_id == sourcePath;
+ });
+ if (sp != subpresentations.end()) { // has a subpresentation
+ QString spPath = convertPath(sp->m_argsOrSrc, true);
+ if (!outPathMap.contains(spPath)) {
+ outPathMap.insert(spPath, {});
+ outPresentationNodes.insert(spPath, sp->m_id);
+ }
+ }
+ }
+ }
+
+ // add texture subpresentations paths
+ QString subpresentation = elem.attribute(QStringLiteral("subpresentation"));
+ if (!subpresentation.isEmpty()) {
+ auto *sp = std::find_if(
+ subpresentations.begin(), subpresentations.end(),
+ [&subpresentation](const SubPresentationRecord &spr) -> bool {
+ return spr.m_id == subpresentation;
+ });
+ if (sp != subpresentations.end()) { // has a subpresentation
+ QString spPath = convertPath(sp->m_argsOrSrc, true);
+ if (!outPathMap.contains(spPath)) {
+ outPathMap.insert(spPath, {});
+ outPresentationNodes.insert(spPath, sp->m_id);
+ }
+ }
+ }
+
+ // add fonts paths
+ QString font = elem.attribute(QStringLiteral("font"));
+ if (!font.isEmpty()) {
+ // the .uip file only shows the font name, we search for the font file in the
+ // current directory plus the 'fonts' directory at the same level or 1 level up.
+
+ const QString TTF_EXT = QStringLiteral(".ttf"); // TODO: should we also handle .otf?
+ const QString slashUipPath = uipSrc.path() + QLatin1Char('/');
+
+ // this is the most probable place so lets search it first
+ QString fontPath = QStringLiteral("../fonts/") + font + TTF_EXT;
+ QFileInfo absFontPath(slashUipPath + fontPath);
+ if (absFontPath.exists()) {
+ if (!outPathMap.contains(fontPath))
+ outPathMap.insert(fontPath, absFontPath.absoluteFilePath());
+ } else {
+ fontPath = font + TTF_EXT;
+ absFontPath = QFileInfo(slashUipPath + fontPath);
+ if (absFontPath.exists()) {
+ if (!outPathMap.contains(fontPath))
+ outPathMap.insert(fontPath, absFontPath.absoluteFilePath());
+ } else {
+ fontPath = QStringLiteral("fonts/") + font + TTF_EXT;
+ absFontPath = QFileInfo(slashUipPath + fontPath);
+ if (!outPathMap.contains(fontPath) && absFontPath.exists())
+ outPathMap.insert(fontPath, absFontPath.absoluteFilePath());
+ }
+ }
+ }
+
+ // add custom material/effect assets
+ const QString ref = elem.attribute(QStringLiteral("ref"));
+ const QString classId = matEffClassIdMap.value(ref, {});
+ if (!classId.isEmpty()) {
+ const QSet<QString> textureProps = matEffPropertyMap.value(classId, {});
+ for (auto &prop : textureProps) {
+ QString texturePath = elem.attribute(prop);
+ if (!texturePath.isEmpty()) {
+ // Typically these paths have ./ prepended even though they are relative
+ // to uip.
+ // Remove it as ./ at start is interpreted as relative to project file
+ if (texturePath.startsWith(QLatin1String("./")))
+ texturePath = texturePath.mid(2);
+ if (!texturePath.isEmpty()) {
+ QFileInfo absTexPath(uipSrc.path() + QLatin1Char('/') + texturePath);
+ if (!outPathMap.contains(texturePath) && absTexPath.exists()
+ && absTexPath.isFile()) {
+ outPathMap.insert(texturePath, absTexPath.absoluteFilePath());
+ }
+ }
+ }
+ }
+ }
+
+ parseDataInput(elem, false);
+ }
+ };
+
+ QDomElement logicElem
+ = domDoc.documentElement().firstChild().firstChildElement(QStringLiteral("Logic"));
+ QDomNodeList addElems = logicElem.elementsByTagName(QStringLiteral("Add"));
+ QDomNodeList setElems = logicElem.elementsByTagName(QStringLiteral("Set"));
+
+ parseNodes(addElems);
+ parseNodes(setElems);
+}
+
+/**
+ * Find datainput use in subpresentation
+ *
+ * @param subpresentation subpresentation
+ * @param outmap returned list of datainput - property name pairs
+ */
+// static
+bool PresentationFile::getDataInputBindings(const SubPresentationRecord &subpresentation,
+ QMultiMap<QString, QPair<QString, QString>> &outmap)
+{
+ QList<QString> ctrldPropList;
+
+ QString spPath(g_StudioApp.getRenderableAbsolutePath(subpresentation.m_id));
+
+ QDomDocument domDoc;
+ if (!StudioUtils::readFileToDomDocument(spPath, domDoc))
+ return false;
+
+ // search <Graph>
+ QDomElement graphElem = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Graph"));
+ for (QDomElement p = graphElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ QString ctrldPropStr = p.attribute(QStringLiteral("controlledproperty"));
+ if (!ctrldPropStr.isEmpty())
+ ctrldPropList.append(ctrldPropStr);
+ }
+ // Search Logic - State - Add
+ QDomNodeList addElems = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Logic"))
+ .elementsByTagName(QStringLiteral("Add"));
+
+ for (int i = 0; i < addElems.count(); ++i) {
+ QDomElement elem = addElems.at(i).toElement();
+ QString ctrldPropStr = elem.attribute(QStringLiteral("controlledproperty"));
+ if (!ctrldPropStr.isEmpty())
+ ctrldPropList.append(ctrldPropStr);
+ }
+
+ for (auto di : qAsConst(ctrldPropList)) {
+ QStringList split = di.split(QLatin1String(" "));
+ for (int i = 0; i < split.size(); i += 2) {
+ QString diName = split[i];
+ // Datainput names indicated with prefix "$", remove
+ // if found.
+ if (diName.startsWith(QLatin1String("$")))
+ diName = diName.mid(1, diName.size() - 1);
+ QString propName = split[i + 1];
+ // We should find the datainput from the global datainput list
+ // parsed out from UIA file, but check just in case and do not insert
+ // if not found.
+ if (g_StudioApp.m_dataInputDialogItems.contains(diName)) {
+ outmap.insert(subpresentation.m_id, QPair<QString, QString>(diName, propName));
+ } else {
+ qWarning() << "Subpresentation" << subpresentation.m_id
+ << "is using datainput" << diName << "that is "
+ "not found from the current UIA file";
+ }
+ }
+ }
+ return true;
+}
+
+bool PresentationFile::getDataOutputBindings(const SubPresentationRecord &subpresentation,
+ QMultiMap<QString, QPair<QString, QString>> &outmap)
+{
+ QList<QString> ctrldPropList;
+
+ QString spPath(g_StudioApp.getRenderableAbsolutePath(subpresentation.m_id));
+
+ QDomDocument domDoc;
+ if (!StudioUtils::readFileToDomDocument(spPath, domDoc))
+ return false;
+
+ // search <Graph>
+ QDomElement graphElem = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Graph"));
+ for (QDomElement p = graphElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ QString ctrldPropStr = p.attribute(QStringLiteral("observedproperty"));
+ if (!ctrldPropStr.isEmpty())
+ ctrldPropList.append(ctrldPropStr);
+ }
+ // Search Logic - State - Add
+ QDomNodeList addElems = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Logic"))
+ .elementsByTagName(QStringLiteral("Add"));
+
+ for (int i = 0; i < addElems.count(); ++i) {
+ QDomElement elem = addElems.at(i).toElement();
+ QString ctrldPropStr = elem.attribute(QStringLiteral("observedproperty"));
+ if (!ctrldPropStr.isEmpty())
+ ctrldPropList.append(ctrldPropStr);
+ }
+
+ for (auto dout : qAsConst(ctrldPropList)) {
+ QStringList split = dout.split(QLatin1Char(' '));
+ for (int i = 0; i < split.size(); i += 2) {
+ QString doName = split[i];
+ // Dataoutput names indicated with prefix "$", remove if found.
+ if (doName.startsWith(QLatin1Char('$')))
+ doName = doName.mid(1, doName.size() - 1);
+ QString propName = split[i + 1];
+ // We should find the dataoutputs from the global dataoutput list
+ // parsed out from UIA file, but check just in case and do not insert
+ // if not found.
+ if (g_StudioApp.m_dataInputDialogItems.contains(doName)) {
+ outmap.insert(subpresentation.m_id, QPair<QString, QString>(doName, propName));
+ } else {
+ qWarning() << "Subpresentation" << subpresentation.m_id
+ << "is using dataoutput" << doName << "that is "
+ "not found from the current UIA file";
+ }
+ }
+ }
+ return true;
+}
diff --git a/src/Authoring/Qt3DStudio/Application/PresentationFile.h b/src/Authoring/Qt3DStudio/Application/PresentationFile.h
new file mode 100644
index 00000000..1d8aba85
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/PresentationFile.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** 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 PRESENTATIONFILE_H
+#define PRESENTATIONFILE_H
+
+#include "ProjectFile.h"
+
+#include <QtCore/qfile.h>
+#include <QtXml/qdom.h>
+#include <QtCore/qmap.h>
+
+QT_FORWARD_DECLARE_CLASS(QDir)
+QT_FORWARD_DECLARE_CLASS(QFileInfo)
+class CDataInputDialogItem;
+
+class PresentationFile
+{
+public:
+ static void getSourcePaths(const QFileInfo &uipSrc, const QFileInfo &uipTarget,
+ QHash<QString, QString> &outPathMap, QString &outRootPath,
+ QHash<QString, QString> &outPresentationNodes,
+ QSet<QString> &outDataInputs, QSet<QString> &outDataOutputs);
+ static void updatePresentationId(const QString &url, const QString &oldId,
+ const QString &newId);
+ static void renameMaterial(const QString &uipPath, const QString &oldName,
+ const QString &newName);
+ static QSize readSize(const QString &uipPath);
+ static QString findProjectFile(const QString &uipPath);
+ static bool getDataInputBindings(const SubPresentationRecord &subpresentation,
+ QMultiMap<QString, QPair<QString, QString>> &outmap);
+ static bool getDataOutputBindings(const SubPresentationRecord &subpresentation,
+ QMultiMap<QString, QPair<QString, QString>> &outmap);
+
+private:
+ PresentationFile();
+};
+
+#endif // PRESENTATIONFILE_H
diff --git a/src/Authoring/Qt3DStudio/Application/ProjectFile.cpp b/src/Authoring/Qt3DStudio/Application/ProjectFile.cpp
new file mode 100644
index 00000000..b4c18c50
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/ProjectFile.cpp
@@ -0,0 +1,1549 @@
+/****************************************************************************
+**
+** 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 "ProjectFile.h"
+#include "Qt3DSFileTools.h"
+#include "Exceptions.h"
+#include "DataInputDlg.h"
+#include "StudioApp.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "Doc.h"
+#include "IDocumentEditor.h"
+#include "PresentationFile.h"
+#include "IStudioRenderer.h"
+#include "StudioUtils.h"
+#include "Dispatch.h"
+#include "MainFrm.h"
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qsavefile.h>
+#include <QtCore/qtimer.h>
+#include <QtWidgets/qmessagebox.h>
+
+ProjectFile::ProjectFile()
+{
+
+}
+
+// find the 1st .uia file in the current or parent directories and assume this is the project file,
+// as a project should have only 1 .uia file
+void ProjectFile::ensureProjectFile()
+{
+ if (!m_fileInfo.exists()) {
+ QFileInfo uipInfo(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath());
+ QString uiaPath(PresentationFile::findProjectFile(uipInfo.absoluteFilePath()));
+
+ if (uiaPath.isEmpty()) {
+ // .uia not found, create a new one in the same folder as uip. Creation sets file info.
+ create(uipInfo.absoluteFilePath().replace(QLatin1String(".uip"),
+ QLatin1String(".uia")));
+ addPresentationNode(uipInfo.absoluteFilePath());
+ updateDocPresentationId();
+ } else {
+ // .uia found, set project file info
+ m_fileInfo.setFile(uiaPath);
+ }
+ }
+}
+
+void ProjectFile::initProjectFile(const QString &presPath)
+{
+ QFileInfo uipFile(presPath);
+ QString uiaPath(PresentationFile::findProjectFile(uipFile.absoluteFilePath()));
+
+ if (uiaPath.isEmpty()) {
+ // .uia not found, clear project file info
+ m_fileInfo = QFileInfo();
+ } else {
+ // .uia found, set project file info
+ m_fileInfo.setFile(uiaPath);
+ }
+}
+
+/**
+ * Add a presentation or presentation-qml node to the project file
+ *
+ * @param pPath the absolute path to the presentation file, it will be saved as relative
+ * @param pId presentation Id
+ */
+void ProjectFile::addPresentationNode(const QString &pPath, const QString &pId)
+{
+ addPresentationNodes({{pPath, pId}});
+}
+
+// Add a list of presentation or presentation-qml nodes to the project file
+void ProjectFile::addPresentationNodes(const QHash<QString, QString> &nodeList)
+{
+ ensureProjectFile();
+
+ QDomDocument doc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, doc))
+ return;
+
+ QDomElement rootElem = doc.documentElement();
+ QDomElement assetsElem = rootElem.firstChildElement(QStringLiteral("assets"));
+
+ // create the <assets> node if it doesn't exist
+ bool initial = false;
+ if (assetsElem.isNull()) {
+ assetsElem = doc.createElement(QStringLiteral("assets"));
+ rootElem.insertBefore(assetsElem, {});
+ initial = true;
+ }
+
+ QHash<QString, QString> changesList;
+ QHashIterator<QString, QString> nodesIt(nodeList);
+ while (nodesIt.hasNext()) {
+ nodesIt.next();
+ const QString presPath = nodesIt.key();
+ const QString presId = nodesIt.value();
+ QString relativePresentationPath
+ = QDir(getProjectPath()).relativeFilePath(presPath);
+
+ // make sure the node doesn't already exist
+ bool nodeExists = false;
+ for (QDomElement p = assetsElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ if ((p.nodeName() == QLatin1String("presentation")
+ || p.nodeName() == QLatin1String("presentation-qml"))
+ && p.attribute(QStringLiteral("src")) == relativePresentationPath) {
+ nodeExists = true;
+ break;
+ }
+ }
+
+ if (!nodeExists) {
+ const QString presentationId
+ = ensureUniquePresentationId(presId.isEmpty()
+ ? QFileInfo(presPath).completeBaseName()
+ : presId);
+
+ if (assetsElem.attribute(QStringLiteral("initial")).isEmpty()) {
+ assetsElem.setAttribute(QStringLiteral("initial"), presentationId);
+ m_initialPresentation = presentationId;
+ }
+
+ // add the presentation node
+ bool isQml = presPath.endsWith(QLatin1String(".qml"));
+ QDomElement pElem = isQml ? doc.createElement(QStringLiteral("presentation-qml"))
+ : doc.createElement(QStringLiteral("presentation"));
+ pElem.setAttribute(QStringLiteral("id"), presentationId);
+ pElem.setAttribute(isQml ? QStringLiteral("args") : QStringLiteral("src"),
+ relativePresentationPath);
+ assetsElem.appendChild(pElem);
+ changesList.insert(relativePresentationPath, presentationId);
+
+ if (!initial) {
+ g_StudioApp.m_subpresentations.push_back(
+ SubPresentationRecord(isQml ? QStringLiteral("presentation-qml")
+ : QStringLiteral("presentation"),
+ presentationId, relativePresentationPath));
+ }
+ }
+ }
+
+ if (initial || changesList.size() > 0)
+ StudioUtils::commitDomDocumentSave(file, doc);
+
+ if (changesList.size() > 0) {
+ g_StudioApp.getRenderer().RegisterSubpresentations(g_StudioApp.m_subpresentations);
+
+ QHashIterator<QString, QString> changesIt(changesList);
+ while (changesIt.hasNext()) {
+ changesIt.next();
+ Q_EMIT presentationIdChanged(changesIt.key(), changesIt.value());
+ }
+ }
+}
+
+// Get the src attribute (relative path) to the initial presentation in a uia file, if no initial
+// presentation exists, the first one is returned. Returns empty string if file cannot be read.
+QString ProjectFile::getInitialPresentationSrc(const QString &uiaPath)
+{
+ QDomDocument domDoc;
+ if (!StudioUtils::readFileToDomDocument(uiaPath, domDoc))
+ return {};
+
+ QString firstPresentationSrc;
+ QDomElement assetsElem = domDoc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ QString initialId = assetsElem.attribute(QStringLiteral("initial"));
+ if (!initialId.isEmpty()) {
+ QDomNodeList pNodes = assetsElem.elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < pNodes.count(); ++i) {
+ QDomElement pElem = pNodes.at(i).toElement();
+ if (pElem.attribute(QStringLiteral("id")) == initialId)
+ return pElem.attribute(QStringLiteral("src"));
+
+ if (i == 0)
+ firstPresentationSrc = pElem.attribute(QStringLiteral("src"));
+ }
+ }
+ }
+
+ return firstPresentationSrc;
+}
+
+/**
+ * Write a presentation id to the project file.
+ * If the presentation id doesn't exist yet in project, it's added.
+ *
+ * This also updates the Doc presentation Id if the src param is empty
+ * or same as current presentation.
+ *
+ * @param id presentation Id
+ * @param src source node, if empty the current document node is used
+ */
+void ProjectFile::writePresentationId(const QString &id, const QString &src)
+{
+ ensureProjectFile();
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ QString theSrc = src.isEmpty() ? doc->getRelativePath() : src;
+ QString theId = id.isEmpty() ? doc->getPresentationId() : id;
+ bool isQml = theSrc.endsWith(QLatin1String(".qml"));
+
+ if (theSrc == doc->getRelativePath())
+ doc->setPresentationId(id);
+
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement assetsElem = domDoc.documentElement().firstChildElement(QStringLiteral("assets"));
+ QDomNodeList pqNodes = isQml ? assetsElem.elementsByTagName(QStringLiteral("presentation-qml"))
+ : assetsElem.elementsByTagName(QStringLiteral("presentation"));
+
+ QString oldId;
+ if (!pqNodes.isEmpty()) {
+ for (int i = 0; i < pqNodes.count(); ++i) {
+ QDomElement pqElem = pqNodes.at(i).toElement();
+ QString srcOrArgs = isQml ? pqElem.attribute(QStringLiteral("args"))
+ : pqElem.attribute(QStringLiteral("src"));
+ if (srcOrArgs == theSrc) {
+ oldId = pqElem.attribute(QStringLiteral("id"));
+ pqElem.setAttribute(QStringLiteral("id"), theId);
+
+ if (assetsElem.attribute(QStringLiteral("initial")) == oldId) {
+ assetsElem.setAttribute(QStringLiteral("initial"), theId);
+ m_initialPresentation = theId;
+ }
+ break;
+ }
+ }
+ }
+
+ if (!src.isEmpty() && oldId.isEmpty()) { // new presentation, add it
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ QDir projectDir(getProjectPath());
+ addPresentationNode(QDir::cleanPath(projectDir.absoluteFilePath(theSrc)), theId);
+ } else if (!oldId.isEmpty()) { // the presentation id changed
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+
+ // update m_subpresentations
+ auto *sp = std::find_if(g_StudioApp.m_subpresentations.begin(),
+ g_StudioApp.m_subpresentations.end(),
+ [&theSrc](const SubPresentationRecord &spr) -> bool {
+ return spr.m_argsOrSrc == theSrc;
+ });
+ if (sp != g_StudioApp.m_subpresentations.end())
+ sp->m_id = theId;
+
+ // update current doc instances (layers and images) that are using this presentation Id
+ qt3dsdm::TInstanceHandleList instancesToRefresh;
+ auto *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::IPropertySystem *propSystem = doc->GetStudioSystem()->GetPropertySystem();
+ std::function<void(qt3dsdm::Qt3DSDMInstanceHandle)>
+ parseChildren = [&](qt3dsdm::Qt3DSDMInstanceHandle instance) {
+ Q3DStudio::CGraphIterator iter;
+ GetAssetChildren(doc, instance, iter);
+
+ while (!iter.IsDone()) {
+ qt3dsdm::Qt3DSDMInstanceHandle child = iter.GetCurrent();
+ if (bridge->GetObjectType(child) & (OBJTYPE_LAYER | OBJTYPE_IMAGE)) {
+ bool add = false;
+ if (bridge->GetSourcePath(child) == oldId) {
+ propSystem->SetInstancePropertyValue(child, bridge->GetSourcePathProperty(),
+ qt3dsdm::SValue(QVariant(theId)));
+ add = true;
+ }
+ if (bridge->getSubpresentation(child).toQString() == oldId) {
+ propSystem->SetInstancePropertyValue(child,
+ bridge->getSubpresentationProperty(),
+ qt3dsdm::SValue(QVariant(theId)));
+ add = true;
+ }
+ if (add)
+ instancesToRefresh.push_back(child);
+ }
+ parseChildren(child);
+ ++iter;
+ }
+ };
+ parseChildren(doc->GetSceneInstance());
+
+ // update changed presentation Id in all .uip files if in-use
+ QDomNodeList pNodes = assetsElem.elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < pNodes.count(); ++i) {
+ QDomElement pElem = pNodes.at(i).toElement();
+ QString path = QDir(getProjectPath())
+ .absoluteFilePath(pElem.attribute(QStringLiteral("src")));
+ PresentationFile::updatePresentationId(path, oldId, theId);
+ }
+ Q_EMIT presentationIdChanged(theSrc, theId);
+
+ g_StudioApp.getRenderer().RegisterSubpresentations(g_StudioApp.m_subpresentations);
+ if (instancesToRefresh.size() > 0) {
+ g_StudioApp.GetCore()->GetDispatch()->FireImmediateRefreshInstance(
+ &(instancesToRefresh[0]), long(instancesToRefresh.size()));
+ for (auto &instance : instancesToRefresh)
+ doc->getSceneEditor()->saveIfMaterial(instance);
+ }
+ }
+}
+
+// Set the doc PresentationId from the project file, this is called after a document is loaded.
+// If there is no project file, it simply clears the id.
+void ProjectFile::updateDocPresentationId()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ doc->setPresentationId({});
+
+ if (!m_fileInfo.exists())
+ return;
+
+ QFile file(getProjectFilePath());
+ if (!file.open(QFile::Text | QFile::ReadOnly)) {
+ qWarning() << file.errorString();
+ return;
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ while (!reader.atEnd()) {
+ if (reader.readNextStartElement() && reader.name() == QLatin1String("presentation")) {
+ const auto attrs = reader.attributes();
+ if (attrs.value(QLatin1String("src")) == doc->getRelativePath()) {
+ // current presentation node
+ doc->setPresentationId(attrs.value(QLatin1String("id")).toString());
+ return;
+ }
+ }
+ }
+}
+
+// get a presentationId that match a given src attribute
+QString ProjectFile::getPresentationId(const QString &src) const
+{
+ if (!m_fileInfo.exists())
+ return {};
+
+ if (src == g_StudioApp.GetCore()->GetDoc()->getRelativePath()) {
+ return g_StudioApp.GetCore()->GetDoc()->getPresentationId();
+ } else {
+ auto *sp = std::find_if(g_StudioApp.m_subpresentations.begin(),
+ g_StudioApp.m_subpresentations.end(),
+ [&src](const SubPresentationRecord &spr) -> bool {
+ return spr.m_argsOrSrc == src;
+ });
+ if (sp != g_StudioApp.m_subpresentations.end())
+ return sp->m_id;
+ }
+
+ return {};
+}
+
+// create the project .uia file
+void ProjectFile::create(const QString &uiaPath)
+{
+ QDomDocument domDoc;
+ domDoc.setContent(QStringLiteral("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<application xmlns=\"http://qt.io/qt3dstudio/uia\">"
+ "</application>"));
+
+ QSaveFile file(uiaPath);
+ if (StudioUtils::openTextSave(file)) {
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ m_fileInfo.setFile(uiaPath);
+ }
+}
+
+/**
+ * Clone the project file with a preview suffix and set the initial attribute to the currently
+ * open document
+ *
+ * @return path to the preview project file. Return path to .uip or preview .uip file if there
+ * is no project file.
+ */
+QString ProjectFile::createPreview()
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ QString uipPrvPath = doc->GetDocumentPath();
+
+ // Commit all open transactions
+ doc->forceCloseTransaction();
+
+ // create a preview uip if doc modified
+ if (doc->isModified()) {
+ uipPrvPath.replace(QLatin1String(".uip"), QLatin1String("_@preview@.uip"));
+ g_StudioApp.GetCore()->OnSaveDocument(uipPrvPath, true);
+ }
+
+ // if no project file exist (.uia) just return the preview uip path
+ if (!m_fileInfo.exists())
+ return uipPrvPath;
+
+ // create a preview project file
+ QString prvPath = getProjectFilePath();
+ prvPath.replace(QLatin1String(".uia"), QLatin1String("_@preview@.uia"));
+
+ if (QFile::exists(prvPath))
+ QFile::remove(prvPath);
+
+ if (QFile::copy(getProjectFilePath(), prvPath)) {
+ QDomDocument domDoc;
+ QSaveFile file(prvPath);
+ if (StudioUtils::openDomDocumentSave(file, domDoc)) {
+ QDomElement assetsElem = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("assets"));
+ assetsElem.setAttribute(QStringLiteral("initial"), doc->getPresentationId());
+
+ if (doc->isModified()) {
+ // Set the preview uip path in the uia file
+ QDomNodeList pNodes = assetsElem.elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < pNodes.count(); ++i) {
+ QDomElement pElem = pNodes.at(i).toElement();
+ if (pElem.attribute(QStringLiteral("id")) == doc->getPresentationId()) {
+ QString src = QDir(getProjectPath()).relativeFilePath(uipPrvPath);
+ pElem.setAttribute(QStringLiteral("src"), src);
+ break;
+ }
+ }
+ }
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ }
+
+ return prvPath;
+ } else {
+ qWarning() << "Couldn't clone project file";
+ }
+
+ return {};
+}
+
+void ProjectFile::parseDataInputElem(const QDomElement &elem,
+ QMap<QString, CDataInputDialogItem *> &dataInputs)
+{
+ if (elem.nodeName() == QLatin1String("dataInput")) {
+ CDataInputDialogItem *item = new CDataInputDialogItem();
+ item->name = elem.attribute(QStringLiteral("name"));
+ QString type = elem.attribute(QStringLiteral("type"));
+ if (type == QLatin1String("Ranged Number")) {
+ item->type = EDataType::DataTypeRangedNumber;
+ item->minValue = elem.attribute(QStringLiteral("min")).toFloat();
+ item->maxValue = elem.attribute(QStringLiteral("max")).toFloat();
+ } else if (type == QLatin1String("String")) {
+ item->type = EDataType::DataTypeString;
+ } else if (type == QLatin1String("Float")) {
+ item->type = EDataType::DataTypeFloat;
+ } else if (type == QLatin1String("Boolean")) {
+ item->type = EDataType::DataTypeBoolean;
+ } else if (type == QLatin1String("Vector4")) {
+ item->type = EDataType::DataTypeVector4;
+ } else if (type == QLatin1String("Vector3")) {
+ item->type = EDataType::DataTypeVector3;
+ } else if (type == QLatin1String("Vector2")) {
+ item->type = EDataType::DataTypeVector2;
+ } else if (type == QLatin1String("Variant")) {
+ item->type = EDataType::DataTypeVariant;
+ }
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ else if (type == QLatin1String("Evaluator")) {
+ item->type = EDataType::DataTypeEvaluator;
+ item->valueString = elem.attribute(QStringLiteral("evaluator"));
+ }
+#endif
+
+ auto metadata = elem.attribute(QStringLiteral("metadata"));
+ if (!metadata.isEmpty()) {
+ auto metadataList = metadata.split(QLatin1Char('$'));
+
+ if (metadataList.size() & 1) {
+ qWarning("Malformed datainput metadata for datainput %s, cannot parse key"
+ " - value pairs. Stop parsing metadata.", qUtf8Printable(item->name));
+ } else {
+ for (int i = 0; i < metadataList.size(); i += 2) {
+ if (metadataList[i].isEmpty()) {
+ qWarning("Malformed datainput metadata for datainput %s - metadata"
+ " key empty. Stop parsing metadata.", qUtf8Printable(item->name));
+ break;
+ }
+ item->metadata.insert(metadataList[i], metadataList[i+1]);
+ }
+ }
+ }
+ dataInputs.insert(item->name, item);
+ }
+}
+
+void ProjectFile::loadDataInputs(const QString &projFile,
+ QMap<QString, CDataInputDialogItem *> &dataInputs)
+{
+ QFileInfo fi(projFile);
+ if (fi.exists()) {
+ QDomDocument doc;
+ if (!StudioUtils::readFileToDomDocument(projFile, doc))
+ return;
+ QDomElement assetsElem = doc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ for (QDomElement p = assetsElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ parseDataInputElem(p, dataInputs);
+ }
+ }
+ }
+}
+
+void ProjectFile::loadSubpresentationsAndDatainputs(
+ QVector<SubPresentationRecord> &subpresentations,
+ QMap<QString, CDataInputDialogItem *> &datainputs)
+{
+ if (!m_fileInfo.exists())
+ return;
+
+ subpresentations.clear();
+ datainputs.clear();
+
+ m_initialPresentation = g_StudioApp.GetCore()->GetDoc()->getPresentationId();
+
+ QDomDocument doc;
+ if (!StudioUtils::readFileToDomDocument(getProjectFilePath(), doc))
+ return;
+
+ QDomElement assetsElem = doc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ QString initial = assetsElem.attribute(QStringLiteral("initial"));
+ if (!initial.isEmpty())
+ m_initialPresentation = initial;
+ for (QDomElement p = assetsElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ if ((p.nodeName() == QLatin1String("presentation")
+ || p.nodeName() == QLatin1String("presentation-qml"))
+ && p.attribute(QStringLiteral("id"))
+ != g_StudioApp.GetCore()->GetDoc()->getPresentationId()) {
+ QString argsOrSrc = p.attribute(QStringLiteral("src"));
+ if (argsOrSrc.isNull())
+ argsOrSrc = p.attribute(QStringLiteral("args"));
+ subpresentations.push_back(
+ SubPresentationRecord(p.nodeName(), p.attribute("id"), argsOrSrc));
+ } else {
+ parseDataInputElem(p, datainputs);
+ }
+ }
+ }
+ g_StudioApp.GetCore()->GetDoc()->UpdateDatainputMap();
+}
+
+/**
+ * Check that a given presentation's or Qml stream's id is unique
+ *
+ * @param id presentation's or Qml stream's Id
+ * @param src source node to exclude from the check. Defaults to empty.
+ */
+bool ProjectFile::isUniquePresentationId(const QString &id, const QString &src) const
+{
+ if (!m_fileInfo.exists())
+ return true;
+
+ bool isCurrDoc = src == g_StudioApp.GetCore()->GetDoc()->getRelativePath();
+
+ if (!isCurrDoc && id == g_StudioApp.GetCore()->GetDoc()->getPresentationId())
+ return false;
+
+ auto *sp = std::find_if(g_StudioApp.m_subpresentations.begin(),
+ g_StudioApp.m_subpresentations.end(),
+ [&id, &src](const SubPresentationRecord &spr) -> bool {
+ return spr.m_id == id && spr.m_argsOrSrc != src;
+ });
+ return sp == g_StudioApp.m_subpresentations.end();
+}
+
+// Returns unique presentation name based on given relative presentation path
+// Only the file name base is returned, no path or suffix.
+QString ProjectFile::getUniquePresentationName(const QString &presSrc) const
+{
+ if (!m_fileInfo.exists())
+ return {};
+
+ const QString fullPresSrc = getAbsoluteFilePathTo(presSrc);
+ QFileInfo fi(fullPresSrc);
+ const QStringList files = fi.dir().entryList(QDir::Files);
+ QString checkName = fi.fileName();
+ if (files.contains(checkName)) {
+ const QString nameTemplate = QStringLiteral("%1%2.%3");
+ const QString suffix = fi.suffix();
+ QString base = fi.completeBaseName();
+ int counter = 0;
+ int checkIndex = base.size();
+ while (checkIndex > 1 && base.at(checkIndex - 1).isDigit())
+ --checkIndex;
+ if (checkIndex < base.size())
+ counter = base.mid(checkIndex).toInt();
+
+ if (counter > 0)
+ base = base.left(checkIndex);
+
+ while (files.contains(checkName))
+ checkName = nameTemplate.arg(base).arg(++counter).arg(suffix);
+ }
+
+ return QFileInfo(checkName).completeBaseName();
+}
+
+QString ProjectFile::ensureUniquePresentationId(const QString &id) const
+{
+ if (!m_fileInfo.exists())
+ return id;
+
+ QDomDocument doc;
+ if (!StudioUtils::readFileToDomDocument(m_fileInfo.filePath(), doc))
+ return id;
+
+ QString newId = id;
+ QDomElement assetsElem = doc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ bool unique;
+ int n = 1;
+ do {
+ unique = true;
+ for (QDomElement p = assetsElem.firstChild().toElement(); !p.isNull();
+ p = p.nextSibling().toElement()) {
+ if ((p.nodeName() == QLatin1String("presentation")
+ || p.nodeName() == QLatin1String("presentation-qml"))
+ && p.attribute(QStringLiteral("id")) == newId) {
+ newId = id + QString::number(n++);
+ unique = false;
+ break;
+ }
+ }
+ } while (!unique);
+ }
+
+ return newId;
+}
+
+// Get the path to the project root. If .uia doesn't exist, return path to current presentation.
+QString ProjectFile::getProjectPath() const
+{
+ if (m_fileInfo.exists())
+ return m_fileInfo.path();
+ else
+ return QFileInfo(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath()).absolutePath();
+}
+
+// Get the path to the project's .uia file. If .uia doesn't exist, return empty string.
+QString ProjectFile::getProjectFilePath() const
+{
+ if (m_fileInfo.exists())
+ return m_fileInfo.filePath();
+ else
+ return {};
+}
+
+// Returns current project name or empty string if there is no .uia file
+QString ProjectFile::getProjectName() const
+{
+ if (m_fileInfo.exists())
+ return m_fileInfo.completeBaseName();
+ else
+ return {};
+}
+
+/**
+ * Get presentations out of a uia file
+ *
+ * @param inUiaPath uia file path
+ * @param outSubpresentations list of collected presentations
+ * @param excludePresentationSrc execluded presentation, (commonly the current presentation)
+ */
+// static
+void ProjectFile::getPresentations(const QString &inUiaPath,
+ QVector<SubPresentationRecord> &outSubpresentations,
+ const QString &excludePresentationSrc)
+{
+ QFile file(inUiaPath);
+ if (!file.open(QFile::Text | QFile::ReadOnly)) {
+ qWarning() << file.errorString();
+ return;
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ while (!reader.atEnd()) {
+ if (reader.readNextStartElement()
+ && (reader.name() == QLatin1String("presentation")
+ || reader.name() == QLatin1String("presentation-qml"))) {
+ const auto attrs = reader.attributes();
+ QString argsOrSrc = attrs.value(QLatin1String("src")).toString();
+ if (excludePresentationSrc == argsOrSrc)
+ continue;
+ if (argsOrSrc.isNull())
+ argsOrSrc = attrs.value(QLatin1String("args")).toString();
+
+ outSubpresentations.push_back(
+ SubPresentationRecord(reader.name().toString(),
+ attrs.value(QLatin1String("id")).toString(),
+ argsOrSrc));
+ } else if (reader.name() == QLatin1String("assets") && !reader.isStartElement()) {
+ break; // reached end of <assets>
+ }
+ }
+}
+
+void ProjectFile::setInitialPresentation(const QString &initialId)
+{
+ if (!initialId.isEmpty() && m_initialPresentation != initialId) {
+ m_initialPresentation = initialId;
+
+ ensureProjectFile();
+
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement assetsElem
+ = domDoc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull() && assetsElem.attribute(QStringLiteral("initial"))
+ != m_initialPresentation) {
+ assetsElem.setAttribute(QStringLiteral("initial"), m_initialPresentation);
+
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ }
+ }
+}
+
+// Returns true if file rename was successful. The parameters are relative to project root.
+bool ProjectFile::renamePresentationFile(const QString &oldName, const QString &newName)
+{
+ const QString fullOldPath = getAbsoluteFilePathTo(oldName);
+ const QString fullNewPath = getAbsoluteFilePathTo(newName);
+ QFile presFile(fullOldPath);
+ const bool success = presFile.rename(fullNewPath);
+
+ if (success) {
+ // Update assets in .uia
+ ensureProjectFile();
+
+ const bool isQml = oldName.endsWith(QLatin1String(".qml"));
+
+ if (isQml && g_StudioApp.m_qmlStreamMap.contains(fullOldPath)) {
+ // Update Qml stream type cache
+ g_StudioApp.m_qmlStreamMap.remove(fullOldPath);
+ g_StudioApp.m_qmlStreamMap.insert(fullNewPath, true);
+ }
+
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return false;
+
+ QDomElement assetsElem
+ = domDoc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ QDomNodeList pqNodes
+ = isQml ? assetsElem.elementsByTagName(QStringLiteral("presentation-qml"))
+ : assetsElem.elementsByTagName(QStringLiteral("presentation"));
+ if (!pqNodes.isEmpty()) {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ for (int i = 0; i < pqNodes.count(); ++i) {
+ QDomElement pqElem = pqNodes.at(i).toElement();
+ const QString attTag = isQml ? QStringLiteral("args") : QStringLiteral("src");
+ const QString srcOrArgs = pqElem.attribute(attTag);
+ if (srcOrArgs == oldName) {
+ pqElem.setAttribute(attTag, newName);
+
+ if (pqElem.attribute(QStringLiteral("id")) != doc->getPresentationId()) {
+ // update m_subpresentations
+ auto *sp = std::find_if(
+ g_StudioApp.m_subpresentations.begin(),
+ g_StudioApp.m_subpresentations.end(),
+ [&oldName](const SubPresentationRecord &spr) -> bool {
+ return spr.m_argsOrSrc == oldName;
+ });
+ if (sp != g_StudioApp.m_subpresentations.end())
+ sp->m_argsOrSrc = newName;
+ } else {
+ // If renaming current presentation, need to update the doc path, too
+ doc->SetDocumentPath(fullNewPath);
+ }
+
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+
+ Q_EMIT assetNameChanged();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return success;
+}
+
+/**
+ * Delete a presentation (or qml-stream) file and remove references to it from the project file.
+ * This function assumes the removed presentation is not referenced by any presentation
+ * in the project and is not the current presentation.
+ *
+ * @param filePath Absolute file path to presentation to delete
+ */
+void ProjectFile::deletePresentationFile(const QString &filePath)
+{
+ QFile(filePath).remove();
+
+ if (m_fileInfo.exists()) {
+ const QString relPath = getRelativeFilePathTo(filePath);
+ const bool isQml = relPath.endsWith(QLatin1String(".qml"));
+
+ // Update records and caches
+ if (isQml && g_StudioApp.m_qmlStreamMap.contains(filePath))
+ g_StudioApp.m_qmlStreamMap.remove(filePath);
+ 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) {
+ g_StudioApp.m_subpresentations.remove(i);
+ break;
+ }
+ }
+
+ // Update project file
+ QDomDocument domDoc;
+ QSaveFile projectFile(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(projectFile, domDoc))
+ return;
+
+ QDomElement assetsElem
+ = domDoc.documentElement().firstChildElement(QStringLiteral("assets"));
+ if (!assetsElem.isNull()) {
+ QDomNodeList pqNodes
+ = isQml ? assetsElem.elementsByTagName(QStringLiteral("presentation-qml"))
+ : assetsElem.elementsByTagName(QStringLiteral("presentation"));
+ if (!pqNodes.isEmpty()) {
+ for (int i = 0; i < pqNodes.count(); ++i) {
+ QDomElement pqElem = pqNodes.at(i).toElement();
+ const QString attTag = isQml ? QStringLiteral("args") : QStringLiteral("src");
+ const QString srcOrArgs = pqElem.attribute(attTag);
+ if (srcOrArgs == relPath) {
+ const QString id = pqElem.attribute(QStringLiteral("id"));
+ // If initial presentation is deleted, change current to initial
+ if (assetsElem.attribute(QStringLiteral("initial")) == id) {
+ m_initialPresentation
+ = g_StudioApp.GetCore()->GetDoc()->getPresentationId();
+ assetsElem.setAttribute(QStringLiteral("initial"),
+ m_initialPresentation);
+ }
+ assetsElem.removeChild(pqNodes.at(i));
+ StudioUtils::commitDomDocumentSave(projectFile, domDoc);
+ break;
+ }
+ }
+ }
+ }
+ // Update registrations asynchronously, as it messes with event processing, which can
+ // cause issues with file models elsewhere in the editor unless file removal is fully
+ // handled.
+ QTimer::singleShot(0, []() {
+ g_StudioApp.getRenderer().RegisterSubpresentations(g_StudioApp.m_subpresentations);
+ });
+ }
+}
+
+void ProjectFile::renameMaterial(const QString &oldName, const QString &newName)
+{
+ for (auto &pres : qAsConst(g_StudioApp.m_subpresentations)) {
+ if (pres.m_type == QLatin1String("presentation")) {
+ PresentationFile::renameMaterial(getAbsoluteFilePathTo(pres.m_argsOrSrc),
+ oldName, newName);
+ }
+ }
+ Q_EMIT assetNameChanged();
+}
+
+// Copies oldPres presentation as newPres. The id for newPres will be autogenerated.
+bool ProjectFile::duplicatePresentation(const QString &oldPres, const QString &newPres)
+{
+ const QString fullOldPath = getAbsoluteFilePathTo(oldPres);
+ const QString fullNewPath = getAbsoluteFilePathTo(newPres);
+ const bool success = QFile::copy(fullOldPath, fullNewPath);
+
+ if (success)
+ addPresentationNode(fullNewPath, {});
+
+ return success;
+}
+
+/**
+ * Returns an absolute file path for a given relative file path.
+ *
+ * @param relFilePath A file path relative to project root to convert.
+ */
+QString ProjectFile::getAbsoluteFilePathTo(const QString &relFilePath) const
+{
+ auto projectPath = QDir(getProjectPath()).absoluteFilePath(relFilePath);
+ return QDir::cleanPath(projectPath);
+}
+
+/**
+ * Returns a file path relative to the project root for given absolute file path.
+ *
+ * @param absFilePath An absolute file path to convert.
+ */
+QString ProjectFile::getRelativeFilePathTo(const QString &absFilePath) const
+{
+ return QDir(getProjectPath()).relativeFilePath(absFilePath);
+}
+
+// Return multimap of type subpresentationid - QPair<datainput, propertyname>
+QMultiMap<QString, QPair<QString, QString>>
+ProjectFile::getDiBindingtypesFromSubpresentations() const
+{
+ QMultiMap<QString, QPair<QString, QString>> map;
+ for (auto sp : qAsConst(g_StudioApp.m_subpresentations))
+ PresentationFile::getDataInputBindings(sp, map);
+
+ return map;
+}
+
+/**
+ * Load variants data to m_variantsDef
+ *
+ * @param filePath the file path to load the variants from. If empty, variants are loaded from the
+ * project file and replace m_variantsDef. If a filePath is specified, the loaded
+ * variants are merged with m_variantsDef.
+ */
+void ProjectFile::loadVariants(const QString &filePath)
+{
+ if (!m_fileInfo.exists())
+ return;
+
+ bool isProj = filePath.isEmpty() || filePath == getProjectFilePath();
+ QFile file(isProj ? getProjectFilePath() : filePath);
+ if (!file.open(QFile::Text | QFile::ReadOnly)) {
+ qWarning() << file.errorString();
+ return;
+ }
+
+ if (isProj) {
+ m_variantsDef.clear();
+ m_variantsDefKeys.clear();
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ VariantGroup *currentGroup = nullptr;
+ while (!reader.atEnd()) {
+ if (reader.readNextStartElement()) {
+ if (reader.name() == QLatin1String("variantgroup")) {
+ QString groupId = reader.attributes().value(QLatin1String("id")).toString();
+ currentGroup = &m_variantsDef[groupId];
+ if (!m_variantsDefKeys.contains(groupId)) {
+ // Only update colors for new groups
+ currentGroup->m_color = reader.attributes().value(
+ QLatin1String("color")).toString();
+ m_variantsDefKeys.append(groupId);
+ }
+ } else if (reader.name() == QLatin1String("variant")) {
+ if (currentGroup) {
+ QString tagId = reader.attributes().value(QLatin1String("id")).toString();
+ if (!currentGroup->m_tags.contains(tagId))
+ currentGroup->m_tags.append(tagId);
+ } else {
+ qWarning() << "Error parsing variant tags.";
+ }
+ } else if (currentGroup) {
+ break;
+ }
+ }
+ }
+
+ if (!isProj) {
+ // if loading variants from a file, update the uia
+ QDomDocument domDoc;
+ QSaveFile fileProj(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(fileProj, domDoc))
+ return;
+
+ QDomElement vElem = domDoc.documentElement().firstChildElement(QStringLiteral("variants"));
+ if (!vElem.isNull())
+ domDoc.documentElement().removeChild(vElem);
+
+ vElem = domDoc.createElement(QStringLiteral("variants"));
+ domDoc.documentElement().appendChild(vElem);
+
+ for (auto &g : qAsConst(m_variantsDefKeys)) {
+ QDomElement gElem = domDoc.createElement(QStringLiteral("variantgroup"));
+ gElem.setAttribute(QStringLiteral("id"), g);
+ gElem.setAttribute(QStringLiteral("color"), m_variantsDef[g].m_color);
+ vElem.appendChild(gElem);
+
+ for (auto &t : qAsConst(m_variantsDef[g].m_tags)) {
+ QDomElement tElem = domDoc.createElement(QStringLiteral("variant"));
+ tElem.setAttribute(QStringLiteral("id"), t);
+ gElem.appendChild(tElem);
+ }
+ }
+
+ StudioUtils::commitDomDocumentSave(fileProj, domDoc);
+ }
+
+ g_StudioApp.m_pMainWnd->updateActionFilterEnableState();
+}
+
+// Add a new tag to a variants group
+void ProjectFile::addVariantTag(const QString &group, const QString &newTag)
+{
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement newTagElem = domDoc.createElement(QStringLiteral("variant"));
+ newTagElem.setAttribute(QStringLiteral("id"), newTag);
+
+ QDomNodeList groupsElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"))
+ .elementsByTagName(QStringLiteral("variantgroup"));
+
+ // update and save the uia
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == group) {
+ gElem.appendChild(newTagElem);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ break;
+ }
+ }
+
+ // update m_variantsDef
+ m_variantsDef[group].m_tags.append(newTag);
+}
+
+// Add a new group, it is assumes that the new group name is unique
+void ProjectFile::addVariantGroup(const QString &newGroup)
+{
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement variantsElem = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"));
+
+ if (variantsElem.isNull()) {
+ QDomElement newVariantsElem = domDoc.createElement(QStringLiteral("variants"));
+ domDoc.documentElement().appendChild(newVariantsElem);
+ variantsElem = newVariantsElem;
+ }
+
+ // a set of predefined variant colors to assign to newly created groups
+ static const QStringList VARIANT_COLORS {
+ QStringLiteral("#06c4f4"), QStringLiteral("#f7752a"),
+ QStringLiteral("#d6c605"), QStringLiteral("#ff00ff"),
+ QStringLiteral("#725de8"), QStringLiteral("#8cc63f"),
+ QStringLiteral("#0071bc"), QStringLiteral("#ed1e79"),
+ QStringLiteral("#f9b406"), QStringLiteral("#74c905"),
+ QStringLiteral("#93278f"), QStringLiteral("#d9e021"),
+ QStringLiteral("#00a99d"), QStringLiteral("#c1272d"),
+ QStringLiteral("#f7931e"), QStringLiteral("#f45d85"),
+ QStringLiteral("#682e7a"), QStringLiteral("#05e2d6"),
+ QStringLiteral("#0000ff"), QStringLiteral("#ff0000")
+ };
+
+ if (m_variantColorsIter == -1) { // initialize m_variantColorsIter
+ m_variantColorsIter = 0;
+ if (!m_variantsDefKeys.isEmpty()) {
+ QString lastGroup = m_variantsDefKeys[m_variantsDefKeys.size() - 1];
+ QString lastGroupColor = m_variantsDef[lastGroup].m_color;
+ for (int i = VARIANT_COLORS.length() - 1; i >= 0; --i) {
+ if (VARIANT_COLORS[i] == lastGroupColor) {
+ m_variantColorsIter = i + 1;
+ break;
+ }
+ }
+ }
+ }
+
+ QString newColor = VARIANT_COLORS[m_variantColorsIter++ % VARIANT_COLORS.size()];
+ QDomElement newGroupElem = domDoc.createElement(QStringLiteral("variantgroup"));
+ newGroupElem.setAttribute(QStringLiteral("id"), newGroup);
+ newGroupElem.setAttribute(QStringLiteral("color"), newColor);
+ variantsElem.appendChild(newGroupElem);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+
+ // update m_variantsDef
+ VariantGroup g;
+ g.m_color = newColor;
+ m_variantsDef[newGroup] = g;
+ m_variantsDefKeys.append(newGroup);
+}
+
+void ProjectFile::renameVariantTag(const QString &group, const QString &oldTag,
+ const QString &newTag)
+{
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ // rename the tag in all uip files
+ QDomNodeList presElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("assets"))
+ .elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ updateVariantsInUip(pPath, VariantsUpdateMode::Rename, group, oldTag, newTag);
+ }
+
+ // update and save the uia
+ QDomNodeList groupsElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"))
+ .elementsByTagName(QStringLiteral("variantgroup"));
+
+ bool renamed = false;
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == group) {
+ QDomNodeList tagsElems = gElem.childNodes();
+ for (int j = 0; j < tagsElems.count(); ++j) {
+ QDomElement tElem = tagsElems.at(j).toElement();
+ if (tElem.attribute(QStringLiteral("id")) == oldTag) {
+ tElem.setAttribute(QStringLiteral("id"), newTag);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ renamed = true;
+ break;
+ }
+ }
+ if (renamed)
+ break;
+ }
+ }
+
+ // update the property
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const QVector<int> instances = doc->getVariantInstances();
+ for (auto instance : instances) {
+ auto property = bridge->getVariantsProperty(instance);
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance, property, sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ QString oldGroupTagPair = QStringLiteral("%1:%2").arg(group).arg(oldTag);
+ if (propVal.contains(oldGroupTagPair)) {
+ propVal.replace(oldGroupTagPair, QStringLiteral("%1:%2").arg(group).arg(newTag));
+ propertySystem->SetInstancePropertyValue(instance, property, QVariant(propVal));
+ }
+ }
+ }
+
+ // update m_variantsDef
+ for (auto &t : m_variantsDef[group].m_tags) {
+ if (t == oldTag) {
+ t = newTag;
+ renamed = true;
+ break;
+ }
+ }
+}
+
+// rename a variant group, newGroup is assumed to be unique
+void ProjectFile::renameVariantGroup(const QString &oldGroup, const QString &newGroup)
+{
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ // rename the group in all uip files
+ QDomNodeList presElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("assets"))
+ .elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ updateVariantsInUip(pPath, VariantsUpdateMode::Rename, oldGroup, {}, newGroup);
+ }
+
+ // update and save the uia
+ QDomNodeList groupsElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"))
+ .elementsByTagName(QStringLiteral("variantgroup"));
+
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == oldGroup) {
+ gElem.setAttribute(QStringLiteral("id"), newGroup);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ break;
+ }
+ }
+
+ // update the property
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const QVector<int> instances = doc->getVariantInstances();
+ for (auto instance : instances) {
+ auto property = bridge->getVariantsProperty(instance);
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance, property, sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ QString oldGroupWithColon = QStringLiteral("%1:").arg(oldGroup);
+ if (propVal.contains(oldGroupWithColon)) {
+ propVal.replace(oldGroupWithColon, QStringLiteral("%1:").arg(newGroup));
+ propertySystem->SetInstancePropertyValue(instance, property, QVariant(propVal));
+ }
+ }
+ }
+
+ // update m_variantsDef
+ m_variantsDef[newGroup] = m_variantsDef[oldGroup];
+ m_variantsDef.remove(oldGroup);
+ for (auto &g : m_variantsDefKeys) {
+ if (g == oldGroup) {
+ g = newGroup;
+ break;
+ }
+ }
+}
+
+void ProjectFile::deleteVariantGroup(const QString &group)
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ // check if group is in use in other presentations in the porject
+ int inUseIdx = -1; // index of first presentation that has the group in-use
+ QDomNodeList presElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("assets"))
+ .elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ if (pPath != doc->GetDocumentPath() && tagExistsInUip(pPath, group)) {
+ inUseIdx = i;
+ break;
+ }
+ }
+
+ if (inUseIdx != -1) {
+ QMessageBox box;
+ box.setWindowTitle(tr("Group tags in use"));
+ box.setText(tr("Some tags in the Group '%1' are in use in the project, are you sure you"
+ " want to delete the group?").arg(group));
+ box.setIcon(QMessageBox::Warning);
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
+ box.setButtonText(QMessageBox::Yes, tr("Delete"));
+ switch (box.exec()) {
+ case QMessageBox::Yes:
+ // delete the group from all uips that use it
+ for (int i = inUseIdx; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ if (pPath != doc->GetDocumentPath())
+ updateVariantsInUip(pPath, VariantsUpdateMode::Delete, group);
+ }
+ break;
+
+ default:
+ // abort deletion
+ return;
+ }
+ }
+
+ // delete the group from current uip, if exists
+ updateVariantsInUip(doc->GetDocumentPath(), VariantsUpdateMode::Delete, group);
+
+ // delete the group from the property (if set)
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const QVector<int> instances = doc->getVariantInstances();
+ for (auto instance : instances) {
+ auto property = bridge->getVariantsProperty(instance);
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance, property, sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ if (propVal.contains(QStringLiteral("%1:").arg(group))) {
+ // property has the deleted group, need to update it, else the deleted group
+ // will be saved the uip if the user saves the presentation.
+ QRegExp rgx(QStringLiteral("%1:\\w*,*|,%1:\\w*").arg(group));
+ propVal.replace(rgx, {});
+ propertySystem->SetInstancePropertyValue(instance, property, QVariant(propVal));
+ }
+ }
+ }
+
+ // update and save the uia
+ QDomElement variantsElem = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"));
+ QDomNodeList groupsElems = variantsElem.elementsByTagName(QStringLiteral("variantgroup"));
+
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == group) {
+ variantsElem.removeChild(gElem);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ break;
+ }
+ }
+
+ // update m_variantsDef
+ m_variantsDef.remove(group);
+ m_variantsDefKeys.removeOne(group);
+}
+
+void ProjectFile::changeVariantGroupColor(const QString &group, const QString &newColor)
+{
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ // update and save the uia
+ QDomNodeList groupsElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"))
+ .elementsByTagName(QStringLiteral("variantgroup"));
+
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == group) {
+ gElem.setAttribute(QStringLiteral("color"), newColor);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ break;
+ }
+ }
+
+ // update m_variantsDef
+ m_variantsDef[group].m_color = newColor;
+}
+
+void ProjectFile::deleteVariantTag(const QString &group, const QString &tag)
+{
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ QDomDocument domDoc;
+ QSaveFile file(getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ // check if tag is in use in other presentations in the porject
+ int inUseIdx = -1; // list of presentations that has the tag in use
+ QDomNodeList presElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("assets"))
+ .elementsByTagName(QStringLiteral("presentation"));
+ for (int i = 0; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ if (pPath != doc->GetDocumentPath()
+ && tagExistsInUip(pPath, group, tag)) {
+ inUseIdx = i;
+ break;
+ }
+ }
+
+ if (inUseIdx != -1) {
+ QMessageBox box;
+ box.setWindowTitle(tr("Tag in use"));
+ box.setText(tr("The tag '%1' is in use in another presentation, are you sure you want to"
+ " delete it?").arg(tag));
+ box.setIcon(QMessageBox::Warning);
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
+ box.setButtonText(QMessageBox::Yes, tr("Delete"));
+ switch (box.exec()) {
+ case QMessageBox::Yes:
+ // delete the tag from all uips that use it
+ for (int i = inUseIdx; i < presElems.count(); ++i) {
+ QString pPath = m_fileInfo.path() + QLatin1Char('/')
+ + presElems.at(i).toElement().attribute(QStringLiteral("src"));
+ if (pPath != doc->GetDocumentPath())
+ updateVariantsInUip(pPath, VariantsUpdateMode::Delete, group, tag);
+ }
+ break;
+
+ default:
+ // abort deletion
+ return;
+ }
+ }
+
+ // delete the tag from current doc, if exists
+ updateVariantsInUip(doc->GetDocumentPath(), VariantsUpdateMode::Delete, group, tag);
+
+ QDomNodeList groupsElems = domDoc.documentElement()
+ .firstChildElement(QStringLiteral("variants"))
+ .elementsByTagName(QStringLiteral("variantgroup"));
+
+ // delete the tag from the property (if set)
+ const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem();
+ const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ const QVector<int> instances = doc->getVariantInstances();
+ for (auto instance : instances) {
+ auto property = bridge->getVariantsProperty(instance);
+ qt3dsdm::SValue sValue;
+ if (propertySystem->GetInstancePropertyValue(instance, property, sValue)) {
+ QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString();
+ if (propVal.contains(QStringLiteral("%1:%2").arg(group).arg(tag))) {
+ // property has the deleted tag, need to update it, else the deleted tag will be
+ // saved in the uip if the user saves the presentation.
+ QRegExp rgx(QStringLiteral("%1:%2,*|,%1:%2").arg(group).arg(tag));
+ propVal.replace(rgx, {});
+ propertySystem->SetInstancePropertyValue(instance, property, QVariant(propVal));
+ }
+ }
+ }
+
+ // update and save the uia
+ bool deleted = false;
+ for (int i = 0; i < groupsElems.count(); ++i) {
+ QDomElement gElem = groupsElems.at(i).toElement();
+ if (gElem.attribute(QStringLiteral("id")) == group) {
+ QDomNodeList tagsElems = gElem.childNodes();
+ for (int j = 0; j < tagsElems.count(); ++j) {
+ QDomElement tElem = tagsElems.at(j).toElement();
+ if (tElem.attribute(QStringLiteral("id")) == tag) {
+ gElem.removeChild(tElem);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ deleted = true;
+ break;
+ }
+ }
+ if (deleted)
+ break;
+ }
+ }
+
+ // update m_variantsDef
+ m_variantsDef[group].m_tags.removeOne(tag);
+}
+
+// rename or delete a tag or group in a uip file
+void ProjectFile::updateVariantsInUip(const QString &src, const VariantsUpdateMode &updateType,
+ const QString &group, const QString &tag,
+ const QString &newName)
+{
+ QDomDocument domDoc;
+ QSaveFile file(src);
+ if (!StudioUtils::openDomDocumentSave(file, domDoc))
+ return;
+
+ QDomElement graphElem = domDoc.documentElement().firstChild()
+ .firstChildElement(QStringLiteral("Graph"));
+ QDomElement sceneElem = graphElem.firstChildElement(QStringLiteral("Scene"));
+
+ bool needsSave = false;
+ QString sceneStr;
+ QTextStream stream(&sceneStr);
+ sceneElem.save(stream, 4);
+
+ // if tag isEmpty() update group, else update tag
+
+ if (updateType == VariantsUpdateMode::Rename) { // rename a tag or a group
+ QString oldPair = group + QLatin1Char(':') + tag;
+ QString newPair = QStringLiteral("%1:%2").arg(tag.isEmpty() ? newName : group)
+ .arg(tag.isEmpty() ? tag : newName);
+ if (sceneStr.contains(oldPair)) {
+ sceneStr.replace(oldPair, newPair);
+ needsSave = true;
+ }
+ } else if (updateType == VariantsUpdateMode::Delete) { // delete a tag or a group
+ QRegExp rgx(tag.isEmpty() ? QStringLiteral("%1:\\w*,*|,%1:\\w*").arg(group)
+ : QStringLiteral("%1:%2,*|,%1:%2").arg(group).arg(tag));
+ if (rgx.indexIn(sceneStr) != -1) {
+ sceneStr.replace(rgx, "");
+ needsSave = true;
+ }
+ }
+
+ if (needsSave) {
+ QDomDocument sceneDom;
+ sceneDom.setContent(sceneStr);
+ graphElem.replaceChild(sceneDom, sceneElem);
+ StudioUtils::commitDomDocumentSave(file, domDoc);
+ }
+}
+
+// if tag param is empty, the method checks if a group exists
+bool ProjectFile::tagExistsInUip(const QString &src, const QString &group, const QString &tag) const
+{
+ QFile file(src);
+ if (!file.open(QFile::Text | QFile::ReadOnly)) {
+ qWarning() << file.errorString();
+ return false;
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ while (!reader.atEnd()) {
+ if (reader.readNextStartElement()) {
+ if (reader.attributes().hasAttribute(QLatin1String("variants"))) {
+ QStringRef v = reader.attributes().value(QLatin1String("variants"));
+ if (v.contains(group + QLatin1Char(':') + tag))
+ return true;
+ } else if (reader.name() == QLatin1String("Logic")) {
+ break;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool ProjectFile::isVariantGroupUnique(const QString &group) const
+{
+ return !m_variantsDef.contains(group);
+}
+
+bool ProjectFile::isVariantTagUnique(const QString &group, const QString &tag) const
+{
+ if (!m_variantsDef.contains(group))
+ return true;
+
+ return !m_variantsDef[group].m_tags.contains(tag);
+}
diff --git a/src/Authoring/Qt3DStudio/Application/ProjectFile.h b/src/Authoring/Qt3DStudio/Application/ProjectFile.h
new file mode 100644
index 00000000..b53f559c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/ProjectFile.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** 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 PROJECTFILE_H
+#define PROJECTFILE_H
+
+#include "Qt3DSFileTools.h"
+#include <QtXml/qdom.h>
+
+namespace Q3DStudio {
+class CFilePath;
+class CString;
+}
+struct SubPresentationRecord;
+class CDataInputDialogItem;
+
+class ProjectFile : public QObject
+{
+ Q_OBJECT
+
+public:
+ ProjectFile();
+
+ struct VariantGroup {
+ QString m_color;
+ QStringList m_tags;
+ };
+
+ void create(const QString &uiaPath);
+ void ensureProjectFile();
+ void initProjectFile(const QString &presPath);
+ static void parseDataInputElem(const QDomElement &elem,
+ QMap<QString, CDataInputDialogItem *> &dataInputs);
+ static void loadDataInputs(const QString &projFile,
+ QMap<QString, CDataInputDialogItem *> &dataInputs);
+ void loadSubpresentationsAndDatainputs(
+ QVector<SubPresentationRecord> &subpresentations,
+ QMap<QString, CDataInputDialogItem *> &datainputs);
+ void writePresentationId(const QString &id, const QString &src = {});
+ void updateDocPresentationId();
+ void addPresentationNode(const QString &uip, const QString &pId = {});
+ void addPresentationNodes(const QHash<QString, QString> &nodeList);
+ bool isUniquePresentationId(const QString &id, const QString &src = {}) const;
+ QString getUniquePresentationName(const QString &presSrc) const;
+ QString getProjectPath() const;
+ QString getProjectFilePath() const;
+ QString getProjectName() const;
+ QString getPresentationId(const QString &src) const;
+ QString getAbsoluteFilePathTo(const QString &relFilePath) const;
+ QString getRelativeFilePathTo(const QString &absFilePath) const;
+ QString createPreview();
+ QMultiMap<QString, QPair<QString, QString>> getDiBindingtypesFromSubpresentations() const;
+
+ static QString getInitialPresentationSrc(const QString &uiaPath);
+ static void getPresentations(const QString &inUiaPath,
+ QVector<SubPresentationRecord> &outSubpresentations,
+ const QString &excludePresentationSrc = {});
+ QString initialPresentation() const { return m_initialPresentation; }
+ void setInitialPresentation(const QString &initialId);
+ bool renamePresentationFile(const QString &oldName, const QString &newName);
+ void deletePresentationFile(const QString &filePath);
+ void renameMaterial(const QString &oldName, const QString &newName);
+ bool duplicatePresentation(const QString &oldPres, const QString &newPres);
+ void loadVariants(const QString &filePath = {});
+ void addVariantTag(const QString &group, const QString &newTag);
+ void renameVariantTag(const QString &group, const QString &oldTag, const QString &newTag);
+ void deleteVariantTag(const QString &group, const QString &tag);
+ void addVariantGroup(const QString &newGroup);
+ void renameVariantGroup(const QString &oldGroup, const QString &newGroup);
+ void deleteVariantGroup(const QString &group);
+ void changeVariantGroupColor(const QString &group, const QString &newColor);
+ bool isVariantGroupUnique(const QString &group) const;
+ bool isVariantTagUnique(const QString &group, const QString &tag) const;
+
+ QHash<QString, VariantGroup> variantsDef() const { return m_variantsDef; }
+ QStringList variantsDefKeys() const { return m_variantsDefKeys; }
+
+Q_SIGNALS:
+ void presentationIdChanged(const QString &path, const QString &id);
+ void assetNameChanged();
+
+private:
+ enum class VariantsUpdateMode { Rename, Delete };
+
+ QString ensureUniquePresentationId(const QString &id) const;
+ bool tagExistsInUip(const QString &src, const QString &group, const QString &tag = {}) const;
+ void updateVariantsInUip(const QString &src, const VariantsUpdateMode &updateType,
+ const QString &group, const QString &tag = {},
+ const QString &newName = {});
+
+ QFileInfo m_fileInfo; // uia file info
+ QString m_initialPresentation;
+ QHash<QString, VariantGroup> m_variantsDef; // definition of variants
+ QStringList m_variantsDefKeys; // maintains insertion order
+ int m_variantColorsIter = -1; // tracks the next default color to assign to newly created group
+};
+
+#endif // PROJECTFILE_H
diff --git a/src/Authoring/Qt3DStudio/Application/StudioApp.cpp b/src/Authoring/Qt3DStudio/Application/StudioApp.cpp
new file mode 100644
index 00000000..f3594c94
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioApp.cpp
@@ -0,0 +1,2239 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+
+#ifdef _WIN32
+#pragma warning(disable : 4100) // unreferenced formal parameter
+#endif
+#include "StudioApp.h"
+#include "PlayerWnd.h"
+#include "DataInputDlg.h"
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
+#include "TimelineWidget.h"
+#include "SlideView.h"
+#include "IKeyframesManager.h"
+#include "PresentationFile.h"
+#include "EditPresentationIdDlg.h"
+
+#include <QtGui/qsurfaceformat.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qsavefile.h>
+#include <QtGui/qopenglcontext.h>
+#include <QtWidgets/qaction.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qcommandlineparser.h>
+#include <QtXml/qdom.h>
+#include <QtQml/qqmlapplicationengine.h>
+#include <QtQuick/qquickitem.h>
+
+#ifdef ENABLE_QT_BREAKPAD
+#include <qtsystemexceptionhandler.h>
+#endif
+
+#ifdef QT3DSTUDIO_REVISION
+#define STRINGIFY_INTERNAL(x) #x
+#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
+const char *const QT3DSTUDIO_REVISION_STR = STRINGIFY(QT3DSTUDIO_REVISION);
+#endif
+
+const QString activePresentationQuery = QStringLiteral("activePresentation:");
+
+int main(int argc, char *argv[])
+{
+ bool isOpenGLES = false;
+
+ // init runtime static resources
+ Q_INIT_RESOURCE(res);
+
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#if !defined(Q_OS_MACOS)
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+#endif
+ SharedTools::QtSingleApplication guiApp(QStringLiteral("Qt3DStudio"), argc, argv);
+
+ // Fix for uia and uip file attribute random ordering (see QTBUG-8158)
+ qSetGlobalQHashSeed(1720419);
+
+#if defined(Q_OS_MACOS)
+ QSurfaceFormat openGL33Format;
+ openGL33Format.setRenderableType(QSurfaceFormat::OpenGL);
+ openGL33Format.setProfile(QSurfaceFormat::CoreProfile);
+ openGL33Format.setMajorVersion(3);
+ openGL33Format.setMinorVersion(3);
+ openGL33Format.setStencilBufferSize(8);
+ QSurfaceFormat::setDefaultFormat(openGL33Format);
+#else
+ QSurfaceFormat format;
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+ QScopedPointer<QOpenGLContext> context(new QOpenGLContext());
+ context->setFormat(format);
+ context->create();
+ isOpenGLES = context->isOpenGLES();
+#endif
+
+#ifdef ENABLE_QT_BREAKPAD
+ const QString libexecPath = QCoreApplication::applicationDirPath() + QStringLiteral("/.");
+ QtSystemExceptionHandler systemExceptionHandler(libexecPath);
+ systemExceptionHandler.setBuildVersion(QT3DSTUDIO_REVISION_STR);
+#endif
+
+ // Parse the command line so we know what's up
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.addPositionalArgument("file",
+ QObject::tr("The presentation file."),
+ QObject::tr("[file]"));
+ parser.addPositionalArgument("folder",
+ QObject::tr("The folder in which to create the new\n"
+ "presentation."),
+ QObject::tr("[folder]"));
+ parser.addOption({"create",
+ QObject::tr("Creates a new presentation.\n"
+ "The file argument must be specified.\n"
+ "The folder argument is optional. In\n"
+ "case it is omitted, the new presentation\n"
+ "is created in the executable folder.")});
+ parser.addOption({"silent",
+ QObject::tr("Allows creating a project silently.\n"
+ "Only has effect with create.")});
+ parser.addOption({"add",
+ QObject::tr("Add a presentation to an existing project.\n"
+ "Omit to create a new project. Only has effect with create.")});
+ parser.process(guiApp);
+
+ const QStringList files = parser.positionalArguments();
+ if (files.count() > 1 && !parser.isSet("create")) {
+ qWarning() << "Only one presentation file can be given.";
+ parser.showHelp(-1);
+ } else if (files.count() > 2 && parser.isSet("create")) {
+ qWarning() << "Only one presentation file and a target folder can be given.";
+ parser.showHelp(-1);
+ } else if (files.count() == 0 && parser.isSet("create")) {
+ qWarning() << "A presentation file is required.";
+ parser.showHelp(-1);
+ }
+
+ QObject::connect(&guiApp, &SharedTools::QtSingleApplication::messageReceived,
+ &g_StudioApp, &CStudioApp::handleMessageReceived);
+
+#if (defined Q_OS_MACOS)
+ QObject::connect(&guiApp, &SharedTools::QtSingleApplication::fileOpenRequest,
+ &g_StudioApp, &CStudioApp::openApplication);
+#endif
+
+ // Load and apply stylesheet for the application
+ QFile styleFile(":/style.qss");
+ styleFile.open(QFile::ReadOnly);
+ guiApp.setStyleSheet(styleFile.readAll());
+ g_StudioApp.initInstance(parser, isOpenGLES);
+ return g_StudioApp.run(parser);
+}
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "Exceptions.h"
+#include "IOLibraryException.h"
+#include "MainFrm.h"
+#include "AboutDlg.h"
+#include "Views.h"
+#include "Doc.h"
+#include "Dialogs.h"
+#include "Dispatch.h"
+#include "StartupDlg.h"
+#include "RecentItems.h"
+#include "StudioPreferences.h"
+#include "MsgRouter.h"
+#include "Views.h"
+#include "Qt3DSFile.h"
+#include "Qt3DSFileTools.h"
+#include "ITickTock.h"
+#include "IStudioRenderer.h"
+#include "IDocumentEditor.h"
+#include "StudioUtils.h"
+
+#include "ClientDataModelBridge.h"
+#include "CommonConstants.h"
+#include "IOLibraryException.h"
+
+#include "Qt3DSDMErrors.h"
+
+#include <iostream>
+#include <fstream>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <QtWidgets/qapplication.h>
+#include <QtCore/qsettings.h>
+
+#include "Core.h"
+#include "HotKeys.h"
+#include "StudioTutorialWidget.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMInspectable.h"
+#include "Qt3DSDMSlides.h"
+#include "Qt3DSDMMaterialInspectable.h"
+#include "Qt3DSDMAnimation.h"
+#include "Qt3DSDMDataCore.h"
+#include "IDirectoryWatchingSystem.h"
+#include "ITickTock.h"
+#include "foundation/Qt3DSLogging.h"
+
+CStudioApp g_StudioApp;
+
+using namespace Q3DStudio;
+
+namespace qt3ds
+{
+void Qt3DSAssert(const char *exp, const char *file, int line, bool *ignore)
+{
+ Q_UNUSED(ignore)
+ g_StudioApp.GetDialogs()->DestroyProgressScreen();
+ g_StudioApp.GetDialogs()->DisplayKnownErrorDialog(exp);
+ qFatal("Assertion thrown %s(%d): %s", file, line, exp);
+}
+}
+
+//=============================================================================
+/**
+ * Constructor
+ */
+CStudioApp::CStudioApp()
+ : m_core(nullptr)
+ , m_isSilent(false)
+ , m_views(nullptr)
+ , m_toolMode(STUDIO_TOOLMODE_MOVE)
+ , m_manipulationMode(StudioManipulationModes::Local)
+ , m_selectMode(STUDIO_SELECTMODE_GROUP)
+ , m_dialogs(nullptr)
+ , m_playbackTime(0)
+ , m_authorZoom(false)
+ , m_welcomeShownThisSession(false)
+ , m_goStraightToWelcomeFileDialog(false)
+ , m_tutorialPage(0)
+ , m_autosaveTimer(new QTimer(this))
+{
+ connect(m_autosaveTimer, &QTimer::timeout, this, [=](){ OnSave(true); });
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CStudioApp::~CStudioApp()
+{
+ delete m_views;
+ m_views = nullptr;
+ delete m_dialogs;
+ m_dialogs = nullptr;
+ delete m_core;
+ m_core = nullptr;
+}
+
+void CStudioApp::performShutdown()
+{
+ // Dispatch un-registration
+ if (m_core) {
+ m_core->GetDispatch()->RemoveAppStatusListener(this);
+ m_core->GetDispatch()->RemoveCoreAsynchronousEventListener(this);
+
+ // close transactions before we call destructors
+ if (m_core->GetDoc()->isTransactionOpened())
+ m_core->GetDoc()->closeTransaction();
+
+ qCInfo(qt3ds::TRACE_INFO) << "Studio exiting successfully";
+ }
+
+ if (m_renderer) {
+ if (m_views->getMainFrame())
+ m_views->getMainFrame()->GetPlayerWnd()->makeCurrent();
+ m_renderer->Close();
+ m_renderer = std::shared_ptr<Q3DStudio::IStudioRenderer>();
+ if (m_views->getMainFrame())
+ m_views->getMainFrame()->GetPlayerWnd()->doneCurrent();
+ }
+
+ delete m_views;
+ m_views = nullptr;
+ delete m_dialogs;
+ m_dialogs = nullptr;
+ delete m_core;
+ m_core = nullptr;
+
+ // Get rid of the temp files
+ Qt3DSFile::ClearCurrentTempCache();
+
+ CMsgRouter::GetInstance()->blockMessages();
+
+ qApp->exit();
+}
+
+//=============================================================================
+/**
+ * Entry location for the creation of this application.
+ * This creates the all the views, then returns if everything
+ * was successful.
+ */
+bool CStudioApp::initInstance(const QCommandLineParser &parser, bool isOpenGLES)
+{
+ QApplication::setOrganizationName("The Qt Company");
+ QApplication::setOrganizationDomain("qt.io");
+ QApplication::setApplicationName("Qt 3D Studio");
+ QApplication::setApplicationVersion(CStudioPreferences::GetVersionString());
+
+ qCInfo(qt3ds::TRACE_INFO) << "Studio: " << QApplication::applicationFilePath();
+ qCInfo(qt3ds::TRACE_INFO) << "Version: " << CStudioPreferences::GetVersionString();
+
+ // Silent is ignored for everything but create
+ m_isSilent = parser.isSet("silent") && parser.isSet("create");
+
+ // Initialize help file paths
+ m_helpFilePath = Qt3DSFile::GetApplicationDirectory()
+ + QStringLiteral("/../doc/qt3dstudio/qt3dstudio-index.html");
+ m_gettingStartedFilePath = Qt3DSFile::GetApplicationDirectory()
+ + QStringLiteral("/../doc/qt3dstudio/getting-started.html");
+
+ QString thePreferencesPath = CFilePath::GetUserApplicationDirectory()
+ + QStringLiteral("/Qt3DStudio/Preferences.setting");
+ CStudioPreferences::loadPreferences(thePreferencesPath);
+
+ m_dialogs = new CDialogs(!m_isSilent);
+
+ if (isOpenGLES) {
+ m_dialogs->DisplayKnownErrorDialog(
+ tr("Qt 3D Studio cannot be started.\n"
+ "OpenGL ES is not supported for Qt 3D Studio Editor.\n"
+ "Make sure you are not using a software renderer or\n"
+ "wrapper like MESA or Angle before trying again."));
+ exit(0);
+ }
+
+ m_views = new CViews();
+
+ m_core = new CCore();
+ getRenderer();
+ m_core->GetDoc()->SetSceneGraph(m_renderer);
+
+ // Dispatch registration
+ m_core->GetDispatch()->AddAppStatusListener(this);
+ m_core->GetDispatch()->AddCoreAsynchronousEventListener(this);
+ m_core->GetDispatch()->AddFailListener(this);
+
+ // Initialize autosave
+ m_autosaveTimer->setInterval(CStudioPreferences::GetAutoSaveDelay() * 1000);
+ if (CStudioPreferences::GetAutoSavePreference())
+ m_autosaveTimer->start();
+
+ return true;
+}
+
+//=============================================================================
+/**
+ * Command handler to display the about dialog.
+ */
+void CStudioApp::onAppAbout()
+{
+ CAboutDlg aboutDlg;
+ aboutDlg.exec();
+}
+
+//=============================================================================
+/**
+ * Main application execution loop.
+ * The application's main thread stays in this until the app exits.
+ * @return true on success; false on failure
+ */
+bool CStudioApp::run(const QCommandLineParser &parser)
+{
+ bool theRetVal = false;
+ try {
+ if (parser.isSet("create")) {
+ // true: add a presentation to a project, false: create a new project
+ bool isAdd = parser.isSet("add");
+ // Create, requires file and folder
+ if (parser.positionalArguments().count() > 1) {
+ theRetVal = createAndRunApplication(parser.positionalArguments().at(0),
+ parser.positionalArguments().at(1), !isAdd);
+ } else {
+ theRetVal = createAndRunApplication(parser.positionalArguments().at(0),
+ QString(), !isAdd);
+ }
+ } else if (parser.positionalArguments().count() > 0) {
+ // Start given file
+ theRetVal = openAndRunApplication(parser.positionalArguments().at(0));
+ } else {
+ // No arguments, normal start
+ theRetVal = blankRunApplication();
+ }
+
+ if (!theRetVal)
+ qWarning("Problem starting application");
+
+ performShutdown();
+ } catch (qt3dsdm::Qt3DSDMError &uicdmError) {
+ Q_UNUSED(uicdmError);
+ exit(1);
+ } catch (...) {
+ throw;
+ }
+
+ return theRetVal;
+}
+
+bool CStudioApp::handleWelcomeRes(int res, bool recursive)
+{
+ bool theReturn = true;
+ bool canceled = false;
+ switch (res) {
+ case StudioTutorialWidget::createNewResult: {
+ if (PerformSavePrompt()) {
+ QString theFile(m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir()));
+ if (!theFile.isEmpty()) {
+ if (!m_core->OnNewDocument(theFile, true)) {
+ // Invalid filename, show a message box and the startup dialog
+ showInvalidFilenameWarning();
+ theReturn = showStartupDialog();
+ } else {
+ theReturn = true;
+ m_welcomeShownThisSession = true;
+ }
+ } else {
+ canceled = true;
+ }
+ } else {
+ canceled = true;
+ }
+ } break;
+
+ case StudioTutorialWidget::openSampleResult: {
+ if (PerformSavePrompt()) {
+ // Try three options:
+ // - open a specific example .uia
+ // - failing that, show the main example root dir
+ // - failing all previous, show default Documents dir
+ QFileInfo filePath;
+ QString theFile(QStringLiteral("."));
+
+#ifndef Q_OS_MACOS
+ filePath.setFile(Qt3DSFile::GetApplicationDirectory() +
+ QStringLiteral("/../examples/studio3d/SampleProject"));
+
+ if (!filePath.exists()) {
+ filePath.setFile(Qt3DSFile::GetApplicationDirectory()
+ + QStringLiteral("/../examples/studio3d"));
+#else
+ filePath.setFile(Qt3DSFile::GetApplicationDirectory()
+ + QStringLiteral("/../../../../examples/studio3d/SampleProject"));
+
+ if (!filePath.exists()) {
+ filePath.setFile(Qt3DSFile::GetApplicationDirectory()
+ + QStringLiteral("/../../../../examples/studio3d"));
+#endif
+ if (!filePath.exists()) {
+ filePath.setFile(QStandardPaths::writableLocation(
+ QStandardPaths::DocumentsLocation));
+ }
+ theFile = m_dialogs->GetFileOpenChoice(filePath.absoluteFilePath());
+ } else {
+ theFile = filePath.absoluteFilePath() + QStringLiteral("/SampleProject.uia");
+ }
+
+ if (!theFile.isEmpty()) {
+ OnLoadDocument(theFile);
+ theReturn = true;
+ m_welcomeShownThisSession = true;
+ } else {
+ canceled = true;
+ }
+ } else {
+ canceled = true;
+ }
+ } break;
+ default:
+ // Welcome screen was simply closed
+ theReturn = false;
+ break;
+ }
+
+ if (canceled) {
+ // User Cancels the dialog. Show the welcome screen.
+ if (recursive) {
+ m_welcomeShownThisSession = false;
+ m_goStraightToWelcomeFileDialog = true;
+ theReturn = showStartupDialog();
+ } else {
+ theReturn = false;
+ }
+ }
+ return theReturn;
+}
+
+QString CStudioApp::resolvePresentationFile(const QString &inFile)
+{
+ QFileInfo inFileInfo(inFile);
+ // a uip file, just return it
+ if (inFileInfo.suffix().compare(QStringLiteral("uip"), Qt::CaseInsensitive) == 0)
+ return inFile;
+
+ // If opening a .uia file, open the initial presentation
+ if (inFileInfo.suffix().compare(QStringLiteral("uia"), Qt::CaseInsensitive) == 0
+ && inFileInfo.exists()) {
+ QString uiaPath = inFileInfo.absoluteFilePath();
+ QString initialPresentation = m_core->getProjectFile().getInitialPresentationSrc(uiaPath);
+
+ if (!initialPresentation.isEmpty())
+ return inFileInfo.path() + QStringLiteral("/") + initialPresentation;
+ }
+
+ // couldn't find a uip file
+ return {};
+}
+
+//=============================================================================
+/**
+ * Show startup dialog and perform necessary action such as create new doc or load doc.
+ * Return false if user requests to exit
+ */
+bool CStudioApp::showStartupDialog()
+{
+#if (defined Q_OS_MACOS)
+ if (m_fileOpenEvent)
+ return true;
+#endif
+
+ int welcomeRes = QDialog::Rejected;
+ bool theReturn = true;
+
+ if (!m_welcomeShownThisSession){
+ m_welcomeShownThisSession = true;
+
+ bool show = false;
+ QSettings settings;
+
+ if (!settings.contains("showWelcomeScreen")) {
+ settings.setValue("showWelcomeScreen", 1);
+ show = true;
+ } else {
+ // if we are returning to welcome dialog page after canceling
+ // file dialog, do not care about settings but always show
+ // welcome
+ show = settings.value("showWelcomeScreen").toBool()
+ || m_goStraightToWelcomeFileDialog;
+ }
+
+ if (show) {
+ StudioTutorialWidget tutorial(m_pMainWnd);
+ welcomeRes = tutorial.exec();
+ }
+ }
+
+ // show the usual startup dialog only if user rejected tutorial
+ // ( = did not open samples or create new project)
+ if (welcomeRes == QDialog::Rejected) {
+ CStartupDlg theStartupDlg(m_pMainWnd);
+
+ // Populate recent items
+ if (m_pMainWnd) {
+ CRecentItems *theRecentItems = m_pMainWnd->GetRecentItems();
+ for (long theIndex = 0; theIndex < theRecentItems->GetItemCount(); ++theIndex)
+ theStartupDlg.AddRecentItem(theRecentItems->GetItem(theIndex));
+ }
+
+ theStartupDlg.exec();
+ CStartupDlg::EStartupChoice theChoice = theStartupDlg.GetChoice();
+
+ switch (theChoice) {
+ case CStartupDlg::EStartupChoice_Exit:
+ theReturn = true;
+ break;
+
+ case CStartupDlg::EStartupChoice_NewDoc: {
+ QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir());
+ if (!theFile.isEmpty()) {
+ if (!m_core->OnNewDocument(theFile, true)) {
+ // Invalid filename, show a message box and the dialog again
+ showInvalidFilenameWarning();
+ theReturn = showStartupDialog();
+ }
+ } else {
+ // User Cancels the dialog. Show startup dialog again.
+ theReturn = showStartupDialog();
+ }
+ } break;
+
+ case CStartupDlg::EStartupChoice_OpenDoc: {
+ QString theFile = m_dialogs->GetFileOpenChoice(getMostRecentDirectory());
+ if (!theFile.isEmpty()) {
+ OnLoadDocument(theFile);
+ theReturn = true;
+ } else {
+ // User Cancels the dialog. Show startup dialog again.
+ theReturn = showStartupDialog();
+ }
+ } break;
+
+ case CStartupDlg::EStartupChoice_OpenRecent: {
+ QString theFile = theStartupDlg.GetRecentDoc();
+ if (!theFile.isEmpty()) {
+ OnLoadDocument(theFile);
+ theReturn = true;
+ } else {
+ // User Cancels the dialog. Show startup dialog again.
+ theReturn = showStartupDialog();
+ }
+ } break;
+
+ default:
+ QT3DS_ASSERT(false); // Should not reach this block.
+ theReturn = false;
+ break;
+ }
+ } else { // open sample or create new
+ theReturn = handleWelcomeRes(welcomeRes, true);
+ }
+ return theReturn;
+}
+
+#if (defined Q_OS_MACOS)
+void CStudioApp::openApplication(const QString &inFilename)
+{
+ m_fileOpenEvent = true;
+ QString loadFile = resolvePresentationFile(inFilename);
+ OnLoadDocument(loadFile, true);
+}
+#endif
+
+//=============================================================================
+/**
+ * Start the app.
+ */
+bool CStudioApp::blankRunApplication()
+{
+ initCore();
+ // Event loop must be running before we launch startup dialog, or possible error message boxes
+ // will cause a silent crash.
+#if (defined Q_OS_MACOS)
+ // Give a bit of time for Finder file open, in case that's how we were started
+ QTimer::singleShot(250, this, &CStudioApp::showStartupDialog);
+#else
+ QTimer::singleShot(0, this, &CStudioApp::showStartupDialog);
+#endif
+ return runApplication();
+}
+
+//=============================================================================
+/**
+ * Open the specified file and run the application.
+ * This will load the file then go into the standard app loop.
+ * On load with the -silent flag, this would force the application to exit on
+ * load failures.
+ * @return true on success; false on failure
+ */
+bool CStudioApp::openAndRunApplication(const QString &inFilename)
+{
+ // Need to resolve the actual file we want to load already to be able to check for it
+ QString loadFile = resolvePresentationFile(inFilename);
+
+ // First check if the desired presentation is already open on another instance
+ SharedTools::QtSingleApplication *app =
+ static_cast<SharedTools::QtSingleApplication *>(QCoreApplication::instance());
+ const auto pids = app->runningInstances();
+ for (const auto pid : pids) {
+ app->setBlock(true);
+ QString query = activePresentationQuery + loadFile;
+ if (app->sendMessage(query, true, 5000, pid))
+ return true;
+ }
+
+ bool theSuccess = false;
+ initCore();
+ // Load document. Upon failure, don't show startup dialog but exit immediately.
+ if (OnLoadDocument(loadFile, false))
+ theSuccess = runApplication();
+ return theSuccess;
+}
+
+bool CStudioApp::createAndRunApplication(const QString &filename, const QString &folder,
+ bool isNewProject)
+{
+ bool theSuccess = false;
+ initCore();
+ // Append .uip if it is not included in the filename
+ QString actualFilename = filename;
+ if (!actualFilename.endsWith(QLatin1String(".uip")))
+ actualFilename.append(QLatin1String(".uip"));
+
+ QString actualFolder = folder;
+ if (!actualFolder.endsWith(QLatin1String("/")))
+ actualFilename.append(QLatin1String("/"));
+
+ // Create presentation
+ QString filePath = actualFolder + actualFilename;
+ if (!filePath.isEmpty()) {
+ theSuccess = m_core->OnNewDocument(filePath, isNewProject, m_isSilent);
+ if (!theSuccess)
+ return false;
+
+ theSuccess = m_isSilent || runApplication();
+ }
+ return theSuccess;
+}
+
+//=============================================================================
+/**
+ * This is the app execution loop, the main thread loops here until the app exits.
+ * @return true on success; false on failure
+ */
+bool CStudioApp::runApplication()
+{
+ return qApp->exec() == 0;
+}
+
+//=============================================================================
+/**
+ * Initialize the core and all the views.
+ */
+void CStudioApp::initCore()
+{
+ // Initialize and cache the RenderSelector values for the first time,
+ // this way, subsequent attempts to instantiate a RenderSelector would circumvent the need
+ // for any extra (unneccesary) creation of render contexts which inadvertently cause exceptions
+ // to be thrown.
+
+ m_core->Initialize();
+
+ if (m_views) {
+ m_views->createViews(m_isSilent);
+ m_pMainWnd = m_views->getMainFrame();
+ m_pMainWnd->initializeGeometryAndState();
+ }
+
+ RegisterGlobalKeyboardShortcuts(m_core->GetHotKeys(), m_pMainWnd);
+ m_core->GetDispatch()->AddPresentationChangeListener(this);
+}
+
+struct SIImportFailedHandler : public Q3DStudio::IImportFailedHandler
+{
+ CDialogs &m_Dialogs;
+ SIImportFailedHandler(CDialogs &dialogs)
+ : m_Dialogs(dialogs)
+ {
+ }
+ void DisplayImportFailed(const QString &inDocumentPath, const QString &inDescription,
+ bool inWarningsOnly) override
+ {
+ m_Dialogs.DisplayImportFailed(QUrl(inDocumentPath), inDescription, inWarningsOnly);
+ }
+};
+
+struct SIDeletingReferencedObjectHandler : public Q3DStudio::IDeletingReferencedObjectHandler
+{
+ CDialogs &m_Dialogs;
+
+ SIDeletingReferencedObjectHandler(CDialogs &dialogs)
+ : m_Dialogs(dialogs)
+ {
+ }
+
+ void DisplayMessageBox(const QString &inDescription) override
+ {
+ QString theTitle = QObject::tr("Warning");
+ QString theMessage = QObject::tr("The following objects have action(s) that reference this "
+ "object and/or its descendants:\n%1\nPlease fix the "
+ "action(s) accordingly.").arg(inDescription);
+
+ m_Dialogs.DisplayMessageBox(theTitle, theMessage, Qt3DSMessageBox::ICON_WARNING, false);
+ }
+};
+
+struct SIMoveRenameHandler : public Q3DStudio::IMoveRenameHandler
+{
+ CDialogs &m_dialogs;
+
+ SIMoveRenameHandler(CDialogs &dialogs)
+ : m_dialogs(dialogs)
+ {
+ }
+
+ void displayMessageBox(const QString &origName, const QString &newName) override
+ {
+ g_StudioApp.GetDialogs()->DisplayObjectRenamed(origName, newName);
+ }
+};
+
+void CStudioApp::setupTimer(long inMessageId, QWidget *inWnd)
+{
+ m_tickTock = ITickTock::CreateTickTock(inMessageId, inWnd);
+ getDirectoryWatchingSystem();
+ m_core->GetDoc()->SetDirectoryWatchingSystem(m_directoryWatchingSystem);
+ m_core->GetDoc()->SetImportFailedHandler(
+ std::make_shared<SIImportFailedHandler>(std::ref(*GetDialogs())));
+ m_core->GetDoc()->SetDocMessageBoxHandler(
+ std::make_shared<SIDeletingReferencedObjectHandler>(std::ref(*GetDialogs())));
+ m_core->GetDoc()->setMoveRenameHandler(
+ std::make_shared<SIMoveRenameHandler>(std::ref(*GetDialogs())));
+}
+
+ITickTock &CStudioApp::getTickTock()
+{
+ if (m_tickTock == nullptr)
+ throw std::runtime_error("Uninitialized TickTock");
+ return *m_tickTock;
+}
+
+Q3DStudio::IStudioRenderer &CStudioApp::getRenderer()
+{
+ if (!m_renderer)
+ m_renderer = Q3DStudio::IStudioRenderer::CreateStudioRenderer();
+ return *m_renderer;
+}
+
+void CStudioApp::clearGuides()
+{
+ SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(), QObject::tr("Clear Guides"))->ClearGuides();
+}
+
+void CStudioApp::handleMessageReceived(const QString &message, QObject *socket)
+{
+ if (message.startsWith(activePresentationQuery)) {
+ QLocalSocket *lsocket = qobject_cast<QLocalSocket *>(socket);
+ if (lsocket) {
+ // Another studio instance wants to know if specified presentation is open on this one
+ QString checkPath(message.mid(activePresentationQuery.size()));
+ QFileInfo checkFile(checkPath);
+ QString docPath;
+ if (m_core)
+ docPath = m_core->GetDoc()->GetDocumentPath();
+ QFileInfo openFile(docPath);
+ if (!checkPath.isEmpty() && checkFile == openFile) {
+ lsocket->write(SharedTools::QtLocalPeer::acceptReply(),
+ SharedTools::QtLocalPeer::acceptReply().size());
+ // Since we accept active presentation query, it means the querying instance will
+ // shut down and this instance must be made active window.
+ if (m_pMainWnd) {
+ m_pMainWnd->setWindowState(m_pMainWnd->windowState() & ~Qt::WindowMinimized);
+ m_pMainWnd->raise();
+ m_pMainWnd->activateWindow();
+ }
+ } else {
+ lsocket->write(SharedTools::QtLocalPeer::denyReply(),
+ SharedTools::QtLocalPeer::denyReply().size());
+ }
+ lsocket->waitForBytesWritten(1000);
+ }
+ }
+ if (socket)
+ delete socket;
+}
+
+void SendAsyncCommand(CDispatch &inDispatch, Q3DStudio::TCallbackFunc inFunc)
+{
+ inDispatch.FireOnAsynchronousCommand(inFunc);
+}
+
+IDirectoryWatchingSystem &CStudioApp::getDirectoryWatchingSystem()
+{
+ if (m_directoryWatchingSystem == nullptr) {
+ Q3DStudio::TCallbackCaller theCaller =
+ std::bind(SendAsyncCommand, std::ref(*m_core->GetDispatch()),
+ std::placeholders::_1);
+ m_directoryWatchingSystem =
+ IDirectoryWatchingSystem::CreateThreadedDirectoryWatchingSystem(theCaller);
+ }
+ return *m_directoryWatchingSystem;
+}
+
+CCore *CStudioApp::GetCore()
+{
+ return m_core;
+}
+
+//=============================================================================
+/**
+ * Get the view manager for this core to communicate to the views.
+ */
+CViews *CStudioApp::GetViews()
+{
+ return m_views;
+}
+
+//=============================================================================
+/**
+ * Get the dialog manager for this core for displaying dialogs.
+ */
+CDialogs *CStudioApp::GetDialogs()
+{
+ return m_dialogs;
+}
+
+long CStudioApp::GetToolMode()
+{
+ return m_toolMode;
+}
+
+void CStudioApp::SetToolMode(long inToolMode)
+{
+ if (m_toolMode != inToolMode) {
+ m_toolMode = inToolMode;
+ m_core->GetDispatch()->FireOnToolbarChange();
+ }
+}
+
+long CStudioApp::GetSelectMode()
+{
+ return m_selectMode;
+}
+
+void CStudioApp::SetSelectMode(long inSelectMode)
+{
+ if (m_selectMode != inSelectMode) {
+ m_selectMode = inSelectMode;
+ m_core->GetDispatch()->FireOnToolbarChange();
+ }
+}
+
+StudioManipulationModes::Enum CStudioApp::GetManipulationMode() const
+{
+ return m_manipulationMode;
+}
+void CStudioApp::SetManipulationMode(StudioManipulationModes::Enum inManipulationMode)
+{
+ if (m_manipulationMode != inManipulationMode) {
+ m_manipulationMode = inManipulationMode;
+ m_core->GetDispatch()->FireOnToolbarChange();
+ }
+}
+
+//=============================================================================
+/**
+ * return true if undo is possible
+ */
+bool CStudioApp::CanUndo()
+{
+ return m_core->GetCmdStack()->CanUndo()
+ && !m_views->getMainFrame()->getTimelineWidget()->dndActive();
+}
+
+//=============================================================================
+/**
+ * return true if redo is possible
+ */
+bool CStudioApp::CanRedo()
+{
+ return m_core->GetCmdStack()->CanRedo();
+}
+
+void CStudioApp::OnCopy()
+{
+ m_core->GetDoc()->HandleCopy();
+}
+
+bool CStudioApp::CanCopy()
+{
+ return m_core->GetDoc()->canCopy();
+}
+
+//=============================================================================
+/**
+ * Get a string describing the type of the copy operation that can be done.
+ * Precedence of copying is 1) Actions; 2) Keyframes; 3) Objects
+ */
+QString CStudioApp::GetCopyType()
+{
+ QString theCopyType;
+
+ CDoc *theDoc = m_core->GetDoc();
+ if (theDoc->canCopySelectedActions())
+ theCopyType = tr("Action");
+ else if (theDoc->canCopySelectedKeyframes())
+ theCopyType = tr("Keyframes");
+ else
+ theCopyType = tr("Object");
+
+ return theCopyType;
+}
+
+QString CStudioApp::getDuplicateType() const
+{
+ const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr;
+ CDoc *doc = m_core->GetDoc();
+ if (slide) {
+ qt3dsdm::Qt3DSDMSlideHandle handle = doc->GetActiveSlide();
+ if (handle != doc->GetStudioSystem()->GetSlideSystem()->GetMasterSlide(handle))
+ return tr("Slide");
+ } else {
+ qt3dsdm::Qt3DSDMInstanceHandle selectedInstance = doc->GetSelectedInstance();
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ if (bridge->IsDuplicateable(selectedInstance))
+ return tr("Object");
+ }
+ return {};
+}
+
+QString CStudioApp::getDeleteType() const
+{
+ // Delete priority: keyframes, slides, objects
+ const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr;
+ CDoc *doc = m_core->GetDoc();
+ if (m_pMainWnd->getTimelineWidget()->hasSelectedKeyframes()) {
+ return tr("Keyframes");
+ } else if (slide) {
+ // Check if the slide is the last one or the master
+ qt3dsdm::Qt3DSDMSlideHandle slideHandle = doc->GetActiveSlide();
+ qt3dsdm::ISlideSystem *slideSys = doc->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle masterSlideHandle = slideSys->GetMasterSlide(slideHandle);
+ size_t slideCount = slideSys->GetSlideCount(masterSlideHandle);
+ if (slideHandle != masterSlideHandle && slideCount > 2)
+ return tr("Slide");
+ } else {
+ qt3dsdm::TInstanceHandleList selected = doc->GetSelectedValue().GetSelectedInstances();
+ CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ int deletableCount = 0;
+ for (size_t idx = 0, end = selected.size(); idx < end; ++idx) {
+ if (bridge->CanDelete(selected[idx]))
+ deletableCount++;
+ }
+ if (deletableCount && deletableCount == selected.size())
+ return tr("Object");
+ }
+ return {};
+}
+
+bool CStudioApp::canGroupSelectedObjects() const
+{
+ // Grouping is allowed for single and for multiple selected items.
+ qt3dsdm::TInstanceHandleList selected = m_core->GetDoc()
+ ->GetSelectedValue().GetSelectedInstances();
+ if (selected.size() >= 1) {
+ // Scene objects, any direct children of scene objects (layers and behaviors), effects,
+ // root components, images, and materials are not groupable. Anything that can be
+ // multiselected can be grouped, so its enough to check the first selected object's type.
+ // Behavior that is not direct child of scene could technically be grouped, but since it
+ // cannot be multiselected, we treat it as ungroupable.
+ qt3dsdm::Qt3DSDMInstanceHandle first = selected[0];
+ if (first.Valid()) {
+ auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ EStudioObjectType type = bridge->GetObjectType(first);
+
+ if (type & OBJTYPE_IS_UNGROUPABLE)
+ return false;
+
+ // Components can't be grouped if they are the root of currently active time context
+ if (type == OBJTYPE_COMPONENT && bridge->IsActiveComponent(first))
+ return false;
+
+ // All items must either be on master slide or not be on master slide
+ bool isMaster = bridge->IsMaster(first);
+ for (size_t i = 1, end = selected.size(); i < end; ++i) {
+ if (isMaster != bridge->IsMaster(selected[i]))
+ return false;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CStudioApp::canUngroupSelectedObjects() const
+{
+ qt3dsdm::TInstanceHandleList selected = m_core->GetDoc()
+ ->GetSelectedValue().GetSelectedInstances();
+ if (selected.size() == 1) {
+ qt3dsdm::Qt3DSDMInstanceHandle first = selected[0];
+ return (first.Valid() && m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge()
+ ->GetObjectType(first) == OBJTYPE_GROUP);
+ }
+ return false;
+}
+
+bool CStudioApp::groupSelectedObjects() const
+{
+ if (canGroupSelectedObjects()) {
+ qt3dsdm::TInstanceHandleList selected = m_core->GetDoc()
+ ->GetSelectedValue().GetSelectedInstances();
+ SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(),
+ QObject::tr("Group objects"))->groupObjects(selected);
+ return true;
+ }
+ return false;
+}
+
+bool CStudioApp::ungroupSelectedObjects() const
+{
+ if (canUngroupSelectedObjects()) {
+ qt3dsdm::TInstanceHandleList selected = m_core->GetDoc()
+ ->GetSelectedValue().GetSelectedInstances();
+ SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(),
+ QObject::tr("Ungroup objects"))->ungroupObjects(selected);
+ return true;
+ }
+ return false;
+}
+
+//=============================================================================
+/**
+ * Cuts the selected object or keys
+ */
+void CStudioApp::OnCut()
+{
+ m_core->GetDoc()->HandleCut();
+}
+
+bool CStudioApp::CanCut()
+{
+ return m_core->GetDoc()->canCut();
+}
+
+//=============================================================================
+/**
+ * Paste keys from the copied list yo
+ */
+void CStudioApp::OnPaste()
+{
+ m_core->GetDoc()->HandlePaste();
+}
+
+bool CStudioApp::CanPaste()
+{
+ return m_core->GetDoc()->canPaste();
+}
+
+//=============================================================================
+/**
+ * Get a string describing the type of the paste operation that can be done.
+ * Precedence of paste is 1) Actions; 2) Object ; 3) Keyframes
+ */
+QString CStudioApp::GetPasteType()
+{
+ QString thePasteType;
+
+ CDoc *theDoc = m_core->GetDoc();
+ if (theDoc->canPasteActions())
+ thePasteType = tr("Action");
+ else if (theDoc->canPasteObjects())
+ thePasteType = tr("Object");
+ else
+ thePasteType = tr("Keyframes");
+
+ return thePasteType;
+}
+
+bool CStudioApp::CanChangeTimebarColor()
+{
+ bool theRetVal = true;
+ qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = m_core->GetDoc()->GetSelectedInstance();
+ if (!theSelectedInstance.Valid()
+ || m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge()->IsSceneInstance(
+ theSelectedInstance)) {
+ theRetVal = false;
+ }
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Sets any changed keyframes on the selected object
+ */
+void CStudioApp::HandleSetChangedKeys()
+{
+ m_core->GetDoc()->SetChangedKeyframes();
+}
+
+//=============================================================================
+/**
+ * Deletes all selected keys
+ */
+void CStudioApp::DeleteSelectedKeys()
+{
+ m_core->GetDoc()->deleteSelectedKeyframes();
+}
+
+//=============================================================================
+/**
+ * Deletes selected object or keyframes
+ */
+void CStudioApp::DeleteSelectedObject()
+{
+ const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr;
+ m_core->GetDoc()->DeleteSelectedItems(slide);
+}
+
+/**
+ * Handles the duplicate object command
+ */
+void CStudioApp::HandleDuplicateCommand()
+{
+ const bool slide = qobject_cast<SlideView *>(m_lastActiveView) != nullptr;
+ m_core->GetDoc()->HandleDuplicateCommand(slide);
+}
+
+/**
+ * Toggles the state of autoset keyframes.
+ */
+void CStudioApp::OnToggleAutosetKeyframes()
+{
+ SetAutosetKeyframes(!CStudioPreferences::IsAutosetKeyframesOn());
+
+ m_core->GetDispatch()->FireOnToolbarChange();
+}
+
+//==============================================================================
+/**
+ * Updates the preferences, and AnimationSystem.
+ */
+void CStudioApp::SetAutosetKeyframes(bool inFlag)
+{
+ CStudioPreferences::SetAutosetKeyframesOn(inFlag);
+
+ m_core->GetDoc()->GetStudioSystem()->GetAnimationSystem()->SetAutoKeyframe(inFlag);
+}
+
+//==============================================================================
+/**
+ * If the presentation is not currently playing, this function will make it
+ * start playing from the current position. The starting point of the playhead
+ * is saved so that it can be restored later.
+ */
+void CStudioApp::PlaybackPlay()
+{
+ // Do not start playback if user is currently interacting with scene
+ if (getRenderer().isMouseDown())
+ return;
+
+ CDoc *theDoc = m_core->GetDoc();
+ if (!theDoc->IsPlaying()) {
+ m_playbackTime = theDoc->GetCurrentViewTime();
+ m_playbackOriginalSlide = theDoc->GetActiveSlide();
+ theDoc->SetPlayMode(PLAYMODE_PLAY);
+ }
+}
+
+//==============================================================================
+/**
+ * If the presentation is currently playing, it is stopped. The playhead is
+ * left wherever it was stopped at (hence it's not restored).
+ */
+void CStudioApp::PlaybackStopNoRestore()
+{
+ m_core->GetDoc()->SetPlayMode(PLAYMODE_STOP);
+}
+
+//==============================================================================
+/**
+ * Moves the playhead back to time zero.
+ */
+void CStudioApp::PlaybackRewind()
+{
+ CDoc *theDoc = m_core->GetDoc();
+ if (theDoc->IsPlaying()) {
+ theDoc->SetPlayMode(PLAYMODE_STOP, 0);
+ theDoc->SetPlayMode(PLAYMODE_PLAY);
+ } else {
+ m_core->GetDoc()->NotifyTimeChanged(0);
+ }
+}
+
+bool CStudioApp::IsPlaying()
+{
+ return m_core->GetDoc()->IsPlaying();
+}
+
+//=============================================================================
+/**
+ * Performs a file revert.
+ * This will revert the doc to the last saved version.
+ */
+void CStudioApp::OnRevert()
+{
+ if (!m_core->GetDoc()->isModified() || m_dialogs->ConfirmRevert()) {
+ QString theCurrentDoc = m_core->GetDoc()->GetDocumentPath();
+ OnLoadDocument(theCurrentDoc);
+ }
+}
+
+//=============================================================================
+/**
+ * Check to see if it is possible to perform a revert.
+ */
+bool CStudioApp::CanRevert() const
+{
+ return m_core->GetDoc()->isModified() && m_core->GetDoc()->isValid();
+}
+
+//==============================================================================
+/**
+ * Handles the recent list.
+ */
+void CStudioApp::OnFileOpenRecent(const QString &inDocument)
+{
+ if (PerformSavePrompt())
+ OnLoadDocument(inDocument);
+}
+
+//==============================================================================
+/**
+ * Called when closing the current doc, this prompts the user to save the doc.
+ * This will only prompt if the doc is modified, and if the user selects save
+ * then this will perform the save operation.
+ * @return true if the operation should continue, false if not.
+ */
+bool CStudioApp::PerformSavePrompt()
+{
+ if (m_core->GetDoc()->isModified()) {
+ CDialogs::ESavePromptResult theResult = m_dialogs->PromptForSave();
+ if (theResult == CDialogs::SAVE_FIRST) {
+ bool onSaveResult = OnSave();
+ if (onSaveResult)
+ return true;
+ } else if (theResult == CDialogs::CONTINUE_NO_SAVE) {
+ return true;
+ }
+
+ return false;
+ }
+ return true;
+}
+
+//==============================================================================
+/**
+ * If the presentation is currently playing, it is stopped. The playhead is
+ * restored to the position found in m_PlaybackTime.
+ */
+void CStudioApp::PlaybackStop()
+{
+ CDoc *theDoc = m_core->GetDoc();
+ // change it back to the original slide first before restoring the original time
+ if (m_playbackOriginalSlide.Valid()) {
+ if (m_playbackOriginalSlide != theDoc->GetActiveSlide())
+ theDoc->NotifyActiveSlideChanged(m_playbackOriginalSlide);
+ theDoc->SetPlayMode(PLAYMODE_STOP, m_playbackTime);
+ }
+ // Invalidate the playback original slide so we don't inadvertently trigger this code later.
+ m_playbackOriginalSlide = 0;
+}
+
+//=============================================================================
+/**
+ * Used for track wheel to do smooth tracking on mac, just scrolls the playhead.
+ */
+void CStudioApp::AdvanceTime()
+{
+ long theDeltaTime = CStudioPreferences::GetTimeAdvanceAmount();
+ long theTime =
+ (m_core->GetDoc()->GetCurrentViewTime() + theDeltaTime) / theDeltaTime * theDeltaTime;
+ m_core->GetDoc()->NotifyTimeChanged(theTime);
+}
+
+//=============================================================================
+/**
+ * Used for track wheel to do smooth tracking on mac, just scrolls the playhead.
+ */
+void CStudioApp::ReduceTime()
+{
+ long theDeltaTime = CStudioPreferences::GetTimeAdvanceAmount();
+ long theTime = (m_core->GetDoc()->GetCurrentViewTime() - 1) / theDeltaTime * theDeltaTime;
+ m_core->GetDoc()->NotifyTimeChanged(theTime);
+}
+
+//=============================================================================
+/**
+ * Used for track wheel to do smooth tracking on mac, just scrolls the playhead.
+ */
+void CStudioApp::AdvanceUltraBigTime()
+{
+ long theDeltaTime = CStudioPreferences::GetBigTimeAdvanceAmount();
+ long theTime =
+ (m_core->GetDoc()->GetCurrentViewTime() + theDeltaTime) / theDeltaTime * theDeltaTime;
+ m_core->GetDoc()->NotifyTimeChanged(theTime);
+}
+
+//=============================================================================
+/**
+ * Used for track wheel to do smooth tracking on mac, just scrolls the playhead.
+ */
+void CStudioApp::ReduceUltraBigTime()
+{
+ long theDeltaTime = CStudioPreferences::GetBigTimeAdvanceAmount();
+ long theTime = (m_core->GetDoc()->GetCurrentViewTime() - 1) / theDeltaTime * theDeltaTime;
+ m_core->GetDoc()->NotifyTimeChanged(theTime);
+}
+
+//==============================================================================
+/**
+ * If the presentation is currently playing, it is stopped. Otherwise, the
+ * presetation starts playing from its current position. Called when the user
+ * presses the Enter key.
+ */
+void CStudioApp::PlaybackToggle()
+{
+ // If the presentation is playing, stop it and leave the playhead where it is
+ if (m_core->GetDoc()->IsPlaying())
+ PlaybackStopNoRestore();
+ // Otherwise, the presentation is stopped, so start it playing
+ else
+ PlaybackPlay();
+}
+
+// TODO: move to more appropriate place (InspectorControlModel.cpp)
+CInspectableBase *CStudioApp::getInspectableFromInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ CInspectableBase *inspectableBase = nullptr;
+ CDoc *doc = m_core->GetDoc();
+
+ if (doc->GetDocumentReader().IsInstance(inInstance)) {
+ CClientDataModelBridge *theBridge = doc->GetStudioSystem()->GetClientDataModelBridge();
+ qt3dsdm::Qt3DSDMSlideHandle activeSlide = doc->GetActiveSlide();
+
+ // Slide, scene or component
+ if (inInstance == theBridge->GetOwningComponentInstance(activeSlide)) {
+ Qt3DSDMInstanceHandle activeSlideInstance = doc->GetStudioSystem()->GetSlideSystem()
+ ->GetSlideInstance(activeSlide);
+
+ inspectableBase = new Qt3DSDMInspectable(inInstance, activeSlideInstance);
+ }
+ if (!inspectableBase) {
+ if (theBridge->IsMaterialBaseInstance(inInstance))
+ inspectableBase = new Qt3DSDMMaterialInspectable(inInstance);
+ else
+ inspectableBase = new Qt3DSDMInspectable(inInstance);
+ }
+ }
+
+ return inspectableBase;
+}
+
+void CStudioApp::RegisterGlobalKeyboardShortcuts(CHotKeys *inShortcutHandler,
+ QWidget *actionParent)
+{
+ m_core->RegisterGlobalKeyboardShortcuts(inShortcutHandler, actionParent);
+
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::Key_Period),
+ CStudioApp::AdvanceTime);
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::Key_Comma),
+ CStudioApp::ReduceTime);
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::ShiftModifier | Qt::Key_Period),
+ CStudioApp::AdvanceUltraBigTime);
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::ShiftModifier | Qt::Key_Comma),
+ CStudioApp::ReduceUltraBigTime);
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::Key_Return),
+ CStudioApp::PlaybackToggle);
+
+ inShortcutHandler->RegisterKeyUpEvent(
+ new CDynHotKeyConsumer<CStudioApp>(this, &CStudioApp::playbackPreviewEnd), 0,
+ Qt::Key_Space);
+ inShortcutHandler->RegisterKeyDownEvent(
+ new CDynHotKeyConsumer<CStudioApp>(this, &CStudioApp::playbackPreviewStart), 0,
+ Qt::Key_Space);
+
+ if (m_views)
+ m_views->registerGlobalKeyboardShortcuts(inShortcutHandler, actionParent);
+}
+
+void CStudioApp::playbackPreviewStart()
+{
+ if (!m_playbackPreviewOn) {
+ m_playbackPreviewOn = true;
+ m_core->GetDoc()->setPlayBackPreviewState(true);
+ PlaybackPlay();
+ }
+}
+
+void CStudioApp::playbackPreviewEnd()
+{
+ m_core->GetDoc()->setPlayBackPreviewState(false);
+ m_playbackPreviewOn = false;
+ PlaybackStop();
+}
+
+bool CStudioApp::isPlaybackPreviewOn() const
+{
+ return m_playbackPreviewOn;
+}
+
+/**
+ * Handles the Save command
+ * This will save the file, if the file has not been saved before this will
+ * do a save as operation.
+ * @param autosave set true if triggering an autosave.
+ * @return true if the file was successfully saved.
+ */
+bool CStudioApp::OnSave(bool autosave)
+{
+ Qt3DSFile theCurrentDoc = m_core->GetDoc()->GetDocumentPath();
+ if (!theCurrentDoc.IsFile()) {
+ return false;
+ } else if (!theCurrentDoc.CanWrite()) {
+ m_dialogs->DisplaySavingPresentationFailed();
+ return false;
+ } else {
+ // Compose autosave filename (insert _autosave before extension)
+ QString autosaveFile = theCurrentDoc.GetPath().toQString();
+ int insertionPoint = autosaveFile.lastIndexOf(QLatin1String(".uip"));
+ autosaveFile.insert(insertionPoint, QStringLiteral("_autosave"));
+
+ if (autosave) {
+ // Set the copy flag to avoid changing actual document name & history
+ m_core->OnSaveDocument(autosaveFile, true);
+ } else {
+ m_core->OnSaveDocument(theCurrentDoc.GetAbsolutePath().toQString());
+ // Delete previous autosave file
+ QFile::remove(autosaveFile);
+ }
+
+ return true;
+ }
+}
+
+/**
+ * This will prompt the user for a location and name to save a copy of the project folder.
+ * Saving under the current project is not possible.
+ */
+void CStudioApp::onProjectSaveAs()
+{
+ if (PerformSavePrompt()) {
+ const QString newProj = m_dialogs->GetSaveAsChoice(QObject::tr("Save Project As"),
+ true, true);
+ if (!newProj.isEmpty()) {
+ // Copy the project
+ const QString currentProj = GetCore()->getProjectFile().getProjectPath();
+ if (CFilePath::copyFolder(currentProj, newProj)) {
+ // Prompt whether to open the new project
+ QMessageBox box(m_pMainWnd);
+ box.setWindowTitle(QObject::tr("Project copy saved successfully"));
+ box.setText(QObject::tr("Continue working with the original project?"));
+ box.setIcon(QMessageBox::Question);
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+ box.setButtonText(QMessageBox::No, QObject::tr("No, open the one I just saved"));
+ box.setDefaultButton(QMessageBox::Yes);
+ int choice = box.exec();
+ if (choice == QMessageBox::No) {
+ const QString newProjDoc = newProj
+ + QDir::cleanPath(GetCore()->GetDoc()->GetDocumentPath()).mid(
+ currentProj.length());
+ QTimer::singleShot(0, [this, newProjDoc]() {
+ OnLoadDocument(newProjDoc);
+ });
+ }
+ } else {
+ const QString title(QObject::tr("Project save failed"));
+ const QString warning = QObject::tr("Failed to save the project as %1.")
+ .arg(newProj);
+ QMessageBox::warning(m_pMainWnd, title, warning);
+ }
+ }
+ }
+}
+
+void CStudioApp::SetAutosaveEnabled(bool enabled)
+{
+ if (enabled)
+ m_autosaveTimer->start();
+ else
+ m_autosaveTimer->stop();
+}
+
+void CStudioApp::SetAutosaveInterval(int interval)
+{
+ m_autosaveTimer->setInterval(interval * 1000);
+}
+
+//=============================================================================
+/**
+ * Call to load a new document.
+ * There should not be a currently active document when this is called.
+ * @param inDocument the path to the UIP file to be loaded.
+ * @param inShowStartupDialogOnError true to show startup dialog if loading document is error
+ * @return true if loading was successful
+ */
+bool CStudioApp::OnLoadDocument(const QString &inDocument, bool inShowStartupDialogOnError)
+{
+ bool theLoadResult = false;
+ QString theLoadErrorParameter;
+ QString theErrorText;
+ QString loadFile = resolvePresentationFile(inDocument);
+ QFileInfo loadFileInfo(loadFile);
+
+ m_core->GetDispatch()->FireOnProgressBegin(QObject::tr("Loading "), loadFileInfo.fileName());
+
+ // Make sure scene is visible
+ if (m_views)
+ m_views->getMainFrame()->showScene();
+
+ try {
+ m_core->getProjectFile().initProjectFile(loadFile);
+ OnLoadDocumentCatcher(loadFile);
+ m_core->GetDispatch()->FireOnOpenDocument(loadFile, true);
+ // Loading was successful
+ theLoadResult = true;
+ } catch (ProjectFileNotFoundException &) {
+ theErrorText = tr("Project file was not found");
+ // No project file (.uia) was found
+ } catch (CUnsupportedFileFormatException &) {
+ theErrorText = tr("The file could not be opened. It is either invalid or was made with an "
+ "old version of Studio.");
+ // We've encountered a file format that is older than the current, OR
+ // corrupt files, unsupported file formats and illegal types.
+ } catch (CInvalidFileFormatException &) {
+ theErrorText = tr("The file could not be opened. The file format is invalid.");
+ } catch (CLoadReferencedFileException &inError) {
+ // referenced files (e.g. Data Files) failed to load
+ theErrorText = tr("%1 failed to load due to invalid referenced file: %2.").arg(
+ loadFileInfo.completeBaseName(), inError.GetFilePath());
+ const QString theDesc = inError.GetDescription();
+ if (!theDesc.isEmpty()) {
+ // append any description is provided
+ theErrorText += QStringLiteral("\n") + inError.GetDescription();
+ }
+ } catch (CIOException &) { // provide specific error message if possible
+ if (!loadFileInfo.exists())
+ theLoadErrorParameter = tr(" does not exist.");
+ qCCritical(qt3ds::INTERNAL_ERROR)
+ << "Failed to load document, IO error (file may be unreadable or nonexistent)";
+ } catch (...) {
+ qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to load document, unknown error";
+ // We don't know exactly what went wrong during a load, but let studio 'fail gracefully'.
+ }
+
+ if (!theErrorText.isEmpty()) {
+ qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to load document: "
+ << theErrorText;
+ }
+
+ m_core->GetDispatch()->FireOnProgressEnd();
+
+ // load fail
+ if (!theLoadResult) {
+#if (defined Q_OS_MACOS)
+ m_fileOpenEvent = false;
+#endif
+ if (!theErrorText.isEmpty()) {
+ m_dialogs->DisplayKnownErrorDialog(theErrorText);
+ } else {
+ m_dialogs->DisplayLoadingPresentationFailed(loadFileInfo, inDocument,
+ theLoadErrorParameter);
+ }
+
+ m_core->GetDispatch()->FireOnOpenDocument(loadFile, false);
+
+ // Show startup dialog
+ if (inShowStartupDialogOnError) {
+ if (!showStartupDialog())
+ qApp->quit();
+ }
+ } else {
+ m_dialogs->ResetSettings(loadFile);
+ m_core->getProjectFile().updateDocPresentationId();
+ m_core->getProjectFile().loadSubpresentationsAndDatainputs(m_subpresentations,
+ m_dataInputDialogItems);
+ m_core->getProjectFile().loadVariants();
+ GetViews()->getMainFrame()->getSlideView()->refreshVariants();
+ getRenderer().RegisterSubpresentations(m_subpresentations);
+
+ m_authorZoom = false;
+
+ m_core->GetDispatch()->FireAuthorZoomChanged();
+ verifyDatainputBindings();
+ checkDeletedDatainputs();
+ }
+
+ return theLoadResult;
+}
+
+void CStudioApp::saveDataInputsToProjectFile()
+{
+ m_core->getProjectFile().ensureProjectFile();
+
+ QDomDocument doc;
+ QSaveFile file(m_core->getProjectFile().getProjectFilePath());
+ if (!StudioUtils::openDomDocumentSave(file, doc))
+ return;
+
+ QDomElement assetsNode = doc.documentElement().firstChildElement(QStringLiteral("assets"));
+
+ if (!assetsNode.isNull()) {
+ // remove old dataInput nodes
+ for (int i = assetsNode.childNodes().count() - 1; i >= 0; --i) {
+ QDomNode node_i = assetsNode.childNodes().at(i);
+ if (node_i.nodeName() == QLatin1String("dataInput"))
+ assetsNode.removeChild(node_i);
+ }
+
+ // add the new dataInputs
+ for (CDataInputDialogItem *item : qAsConst(m_dataInputDialogItems)) {
+ QDomElement diNode = doc.createElement(QStringLiteral("dataInput"));
+ diNode.setAttribute(QStringLiteral("name"), item->name);
+
+ if (item->type == EDataType::DataTypeRangedNumber) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Ranged Number"));
+ diNode.setAttribute(QStringLiteral("min"), item->minValue);
+ diNode.setAttribute(QStringLiteral("max"), item->maxValue);
+ } else if (item->type == EDataType::DataTypeString) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("String"));
+ } else if (item->type == EDataType::DataTypeFloat) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Float"));
+ } else if (item->type == EDataType::DataTypeBoolean) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Boolean"));
+ } else if (item->type == EDataType::DataTypeVector4) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector4"));
+ } else if (item->type == EDataType::DataTypeVector3) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector3"));
+ } else if (item->type == EDataType::DataTypeVector2) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Vector2"));
+ } else if (item->type == EDataType::DataTypeVariant) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Variant"));
+ }
+#ifdef DATAINPUT_EVALUATOR_ENABLED
+ else if (item->type == EDataType::DataTypeEvaluator) {
+ diNode.setAttribute(QStringLiteral("type"), QStringLiteral("Evaluator"));
+ diNode.setAttribute(QStringLiteral("evaluator"), item->valueString);
+ }
+#endif
+
+ QHashIterator<QString, QString> it(item->metadata);
+ QString metadataStr;
+ while (it.hasNext()) {
+ it.next();
+ metadataStr.append(it.key() + QLatin1Char('$') + it.value() + QLatin1Char('$'));
+ }
+ metadataStr.chop(1);
+
+ diNode.setAttribute(QStringLiteral("metadata"), metadataStr.trimmed());
+
+ assetsNode.appendChild(diNode);
+ }
+ StudioUtils::commitDomDocumentSave(file, doc);
+ }
+}
+
+QString CStudioApp::getMostRecentDirectory() const
+{
+ QFileInfo mostRecentDirectory = QFileInfo(QStringLiteral("."));
+ if (m_views) {
+ CRecentItems *recentItems = m_views->getMainFrame()->GetRecentItems();
+ if (recentItems->GetItemCount() > 0) {
+ mostRecentDirectory
+ = QFileInfo(recentItems->GetItem(0)).path();
+ }
+ }
+ return mostRecentDirectory.absoluteFilePath();
+}
+
+QString CStudioApp::getMostRecentProjectParentDir() const
+{
+ QString parentDirectory(QStringLiteral("."));
+ if (m_views) {
+ CRecentItems *recentItems = m_views->getMainFrame()->GetRecentItems();
+ if (recentItems->GetItemCount() > 0) {
+ QString mostRecentPresentation = recentItems->GetItem(0);
+ QFileInfo projectFile(PresentationFile::findProjectFile(mostRecentPresentation));
+ if (!projectFile.exists())
+ projectFile.setFile(mostRecentPresentation);
+ if (!projectFile.exists()) {
+ parentDirectory = QStandardPaths::writableLocation(
+ QStandardPaths::DocumentsLocation);
+ } else {
+ QDir dir = projectFile.absoluteDir();
+ dir.cdUp();
+ parentDirectory = dir.absolutePath();
+ }
+ }
+ }
+ return parentDirectory;
+}
+
+bool CStudioApp::isQmlStream(const QString &fileName)
+{
+ bool retval = false;
+ if (m_qmlStreamMap.contains(fileName)) {
+ retval = m_qmlStreamMap[fileName];
+ } else {
+ if (!fileName.endsWith(QLatin1String(".qml")) || !QFileInfo(fileName).exists()) {
+ return false; // Don't pollute the map with non-qml or nonexistent files
+ } else {
+ QQmlApplicationEngine qmlEngine(fileName);
+ if (qmlEngine.rootObjects().size() > 0) {
+ const char *rootClassName = qmlEngine.rootObjects().at(0)
+ ->metaObject()->superClass()->className();
+ retval = strcmp(rootClassName, "Q3DStudio::Q3DSQmlBehavior") != 0;
+ }
+ }
+ m_qmlStreamMap.insert(fileName, retval);
+ }
+ return retval;
+}
+
+/**
+ * Called by OnLoadDocument, to allow the error reporting to be inserted.
+ * Because of the nature of the error reporting, OnLoadDocument has to have
+ * a certain structure that limits it (C type variables, no object destructors).
+ */
+void CStudioApp::OnLoadDocumentCatcher(const QString &inDocument)
+{
+ {
+ CDispatchDataModelNotificationScope __scope(*m_core->GetDispatch());
+
+ // CloseDocument() clears all the OpenGL buffers so it needs the correct context
+ getRenderer().MakeContextCurrent();
+ m_core->GetDoc()->CloseDocument();
+ getRenderer().ReleaseContext();
+
+ m_core->GetDoc()->LoadDocument(inDocument);
+ }
+
+ // Make sure the client scene is resized properly
+ if (m_views)
+ m_views->recheckMainframeSizingMode();
+}
+
+void CStudioApp::OnFileOpen()
+{
+ if (PerformSavePrompt()) {
+ QString theFile = m_dialogs->GetFileOpenChoice(getMostRecentDirectory());
+ if (!theFile.isEmpty())
+ OnLoadDocument(theFile);
+ }
+}
+
+/**
+ * Create a new project
+ * this creates the project file (.uia), asset folders, and a default .uip file
+ */
+QString CStudioApp::OnProjectNew()
+{
+ if (PerformSavePrompt()) {
+ QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentProjectParentDir(), true);
+ if (!theFile.isEmpty()) {
+ if (!m_core->OnNewDocument(theFile, true))
+ showInvalidFilenameWarning();
+ }
+ }
+ return {};
+}
+
+/**
+ * Create a new presentation
+ * this creates a .uip file
+ */
+void CStudioApp::OnFileNew()
+{
+ if (PerformSavePrompt()) {
+ QString theFile = m_dialogs->GetNewDocumentChoice(getMostRecentDirectory(), false);
+ if (!theFile.isEmpty() && !m_core->OnNewDocument(theFile, false))
+ showInvalidFilenameWarning();
+ }
+}
+
+bool CStudioApp::IsAuthorZoom() const
+{
+ return m_authorZoom;
+}
+
+bool CStudioApp::isOnProgress() const
+{
+ return m_isOnProgress;
+}
+
+void CStudioApp::SetAuthorZoom(bool inZoom)
+{
+ if (m_authorZoom != inZoom) {
+ m_authorZoom = inZoom;
+ m_core->GetDispatch()->FireAuthorZoomChanged();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// These commands come over the dispatch from inside the core. The core doesn't
+// have access to the CMsgRouter at the moment, so this relays the message.
+void CStudioApp::OnAsynchronousCommand(CCmd *inCmd)
+{
+ CMsgRouter::GetInstance()->SendCommand(inCmd, m_core);
+}
+
+void CStudioApp::OnDisplayAppStatus(const QString &inStatusMsg)
+{
+ // Do nothing, it was used to show this in the status bar
+}
+
+void CStudioApp::OnProgressBegin(const QString &inActionText,
+ const QString &inAdditionalText)
+{
+ m_isOnProgress = true;
+ m_dialogs->DisplayProgressScreen(inActionText, inAdditionalText);
+}
+
+void CStudioApp::OnProgressEnd()
+{
+ m_dialogs->DestroyProgressScreen();
+ QTimer::singleShot(0, [this]() {
+ m_isOnProgress = false;
+ });
+}
+
+void CStudioApp::OnAssetDeleteFail()
+{
+ m_dialogs->DisplayAssetDeleteFailed();
+}
+
+void CStudioApp::OnPasteFail()
+{
+ m_dialogs->DisplayPasteFailed();
+}
+
+void CStudioApp::OnBuildconfigurationFileParseFail(const QString &inMessage)
+{
+ m_dialogs->DisplayMessageBox(tr("Build Configurations Error"), inMessage,
+ Qt3DSMessageBox::ICON_ERROR, false);
+}
+
+void CStudioApp::OnSaveFail(bool inKnownError)
+{
+ qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to save project: "
+ << (inKnownError ? "KnownError" : "UnknownError");
+ if (inKnownError)
+ m_dialogs->DisplaySavingPresentationFailed();
+ else
+ m_dialogs->DisplayKnownErrorDialog(tr("Unknown error encountered while saving."));
+}
+
+void CStudioApp::OnErrorFail(const QString &inText)
+{
+ qCCritical(qt3ds::INTERNAL_ERROR) << inText;
+ m_dialogs->DisplayMessageBox(tr("Qt 3D Studio"), inText, Qt3DSMessageBox::ICON_ERROR, false);
+}
+
+void CStudioApp::OnRefreshResourceFail(const QString &inResourceName, const QString &inDescription)
+{
+ qCCritical(qt3ds::INTERNAL_ERROR) << "Failed to refresh resource: "
+ << inResourceName;
+ qCCritical(qt3ds::INTERNAL_ERROR) << inDescription;
+ m_dialogs->DisplayRefreshResourceFailed(inResourceName, inDescription);
+}
+
+void CStudioApp::OnNewPresentation()
+{
+ m_core->GetDoc()->GetStudioSystem()->GetAnimationSystem()->SetAutoKeyframe(
+ CStudioPreferences::IsAutosetKeyframesOn());
+ qCInfo(qt3ds::TRACE_INFO) << "New Presentation: "
+ << m_core->GetDoc()->GetDocumentPath();
+}
+
+void CStudioApp::OnPresentationModifiedExternally()
+{
+ int theUserChoice = m_dialogs->DisplayChoiceBox(
+ tr("Warning!"),
+ tr("This project has changed on disk. Do you want to reload it?"),
+ Qt3DSMessageBox::ICON_WARNING);
+ if (theUserChoice == IDYES) {
+ QString theCurrentDoc = m_core->GetDoc()->GetDocumentPath();
+ OnLoadDocument(theCurrentDoc);
+ }
+}
+
+// Converts a renderable path to the format used in the SubPresentationRecord struct
+// filePath can be absolute or relative to either presentation or project
+QString CStudioApp::getRenderablePath(const QString &filePath) const
+{
+ QString renderablePath;
+ QDir projectDir(m_core->getProjectFile().getProjectPath());
+ const QString projectPath = QDir::cleanPath(projectDir.absolutePath());
+ int index = projectPath.length() + 1;
+ QFileInfo fi(filePath);
+ if (fi.isAbsolute()) {
+ renderablePath = filePath.mid(index);
+ } else {
+ QFileInfo presFile(m_core->GetDoc()->GetDocumentPath());
+ QDir presDir(presFile.absoluteDir());
+ QString checkFile = QDir::cleanPath(presDir.absoluteFilePath(filePath));
+ if (!QFileInfo(checkFile).exists()) {
+ checkFile = QDir::cleanPath(projectDir.absoluteFilePath(filePath));
+ if (!QFileInfo(checkFile).exists())
+ return {};
+ }
+ renderablePath = checkFile.mid(index);
+ }
+ return renderablePath;
+}
+
+// Get the presentation id, returns an empty string for qml streams
+// filePath can be absolute or relative to either presentation or project
+QString CStudioApp::getPresentationId(const QString &filePath) const
+{
+ QString renderablePath = getRenderablePath(filePath);
+ for (SubPresentationRecord r : qAsConst(m_subpresentations)) {
+ if (r.m_type == QLatin1String("presentation") && r.m_argsOrSrc == renderablePath)
+ return r.m_id;
+ }
+ return {};
+}
+
+// Get the qml stream id, returns an empty string for presentations
+// filePath can be absolute or relative to either presentation or project
+QString CStudioApp::getQmlId(const QString &filePath) const
+{
+ QString renderablePath = getRenderablePath(filePath);
+ for (SubPresentationRecord r : qAsConst(m_subpresentations)) {
+ if (r.m_type == QLatin1String("presentation-qml") && r.m_argsOrSrc == renderablePath)
+ return r.m_id;
+ }
+ return {};
+}
+
+// Get the renderable id for a file path.
+// filePath can be absolute or relative to either presentation or project
+QString CStudioApp::getRenderableId(const QString &filePath) const
+{
+ QString renderablePath = getRenderablePath(filePath);
+ for (SubPresentationRecord r : qAsConst(m_subpresentations)) {
+ if (r.m_argsOrSrc == renderablePath)
+ return r.m_id;
+ }
+ return {};
+}
+
+QString CStudioApp::getRenderableAbsolutePath(const QString &renderableId) const
+{
+ for (SubPresentationRecord r : qAsConst(m_subpresentations)) {
+ if (r.m_id == renderableId) {
+ QDir projectDir(m_core->getProjectFile().getProjectPath());
+ return QDir::cleanPath(projectDir.absoluteFilePath(r.m_argsOrSrc));
+ }
+ }
+ return {};
+}
+
+// Returns renderable size in pixels.
+QSize CStudioApp::getRenderableSize(const QString &renderableId)
+{
+ for (int i = 0; i < m_subpresentations.size(); ++i) {
+ SubPresentationRecord &r = m_subpresentations[i];
+ if (r.m_id == renderableId) {
+ if (!r.m_size.isValid()) {
+ QDir projectDir(m_core->getProjectFile().getProjectPath());
+ QString path = QDir::cleanPath(projectDir.absoluteFilePath(r.m_argsOrSrc));
+ QString type = r.m_type;
+ if (type == QLatin1String("presentation")) {
+ r.m_size = PresentationFile::readSize(path);
+ } else { // QML stream
+ QQmlApplicationEngine qmlEngine(path);
+ if (qmlEngine.rootObjects().size() > 0) {
+ QQuickItem *item = qobject_cast<QQuickItem *>(qmlEngine.rootObjects().at(0));
+ if (item)
+ r.m_size = QSize(qRound(item->width()), qRound(item->height()));
+ }
+ }
+ }
+ return r.m_size;
+ }
+ }
+ return {};
+}
+
+void CStudioApp::OnUndefinedDatainputsFail(
+ const QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle>> *map)
+{
+ bool res = m_dialogs->DisplayUndefinedDatainputDlg(map);
+
+ // Delete invalid datainput bindings if user prompted so.
+ if (res) {
+ m_core->GetDoc()->RemoveDatainputBindings(map);
+ // clear commands as we do not want to create undo point
+ // for automatic datainput deletion
+ m_core->GetCmdStack()->Clear();
+ }
+}
+
+void CStudioApp::toggleEyeball()
+{
+ CDoc *doc = m_core->GetDoc();
+ if (doc->getSelectedInstancesCount() > 0) {
+ qt3dsdm::Qt3DSDMPropertyHandle property
+ = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball;
+ SCOPED_DOCUMENT_EDITOR(*doc, tr("Visibility Toggle"))
+ ->toggleBoolPropertyOnSelected(property);
+ }
+}
+
+void CStudioApp::toggleShy()
+{
+ CDoc *doc = m_core->GetDoc();
+ if (doc->getSelectedInstancesCount() > 0) {
+ qt3dsdm::Qt3DSDMPropertyHandle property
+ = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Shy;
+ SCOPED_DOCUMENT_EDITOR(*doc, tr("Shy Toggle"))
+ ->toggleBoolPropertyOnSelected(property);
+ }
+}
+
+void CStudioApp::toggleLocked()
+{
+ CDoc *doc = m_core->GetDoc();
+ if (doc->getSelectedInstancesCount() > 0) {
+ qt3dsdm::Qt3DSDMPropertyHandle property
+ = doc->GetStudioSystem()->GetClientDataModelBridge()->GetSceneAsset().m_Locked;
+ SCOPED_DOCUMENT_EDITOR(*doc, tr("Locked Toggle"))
+ ->toggleBoolPropertyOnSelected(property);
+
+ // Since you are not supposed to be able to select locked objects,
+ // we just assume anything toggled was actually locked and deselect everything
+ doc->DeselectAllItems();
+ }
+}
+
+void CStudioApp::showPresentationIdUniqueWarning()
+{
+ m_dialogs->DisplayMessageBox(tr("Warning"),
+ tr("Presentation Id must be unique."),
+ Qt3DSMessageBox::ICON_WARNING, false);
+}
+
+void CStudioApp::showPresentationIdEmptyWarning()
+{
+ m_dialogs->DisplayMessageBox(tr("Warning"),
+ tr("Presentation Id must not be empty."),
+ Qt3DSMessageBox::ICON_WARNING, false);
+}
+
+void CStudioApp::showInvalidFilenameWarning()
+{
+ m_dialogs->DisplayMessageBox(tr("Invalid filename"),
+ tr("The filename given was invalid."),
+ Qt3DSMessageBox::ICON_WARNING, false);
+}
+
+void CStudioApp::checkDeletedDatainputs()
+{
+ QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMPropertyHandle>> *map;
+ map = new QMultiMap<QString, QPair<qt3dsdm::Qt3DSDMInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle>>;
+ auto doc = m_core->GetDoc();
+ // Update datainputs for the currently open presentation
+ doc->UpdateDatainputMap(map);
+
+ if (!map->empty())
+ m_core->GetDispatch()->FireOnUndefinedDatainputsFail(map);
+
+ // Update allowed property types for datainput-controlled properties
+ // in subpresentations. It is ok to do this once
+ // at the project opening, as the assumption is that subpresentation files
+ // do not change while we are editing currently open presentation.
+
+ // Clear the old subpresentation binding info only.
+ for (auto it : qAsConst(m_dataInputDialogItems))
+ it->externalPresBoundTypes.clear();
+
+ const QMultiMap<QString, QPair<QString, QString>> spDatainputs
+ = GetCore()->getProjectFile().getDiBindingtypesFromSubpresentations();
+
+ // For datainput bindings in subpresentations we do not have specific
+ // instance and/or property handles. Get the datatype for property using
+ // the generic name string and leave instance/property handle empty.
+ for (auto sp = spDatainputs.cbegin(); sp != spDatainputs.cend(); ++sp) {
+ const QString propName = sp->second;
+ CDataInputDialogItem *item = m_dataInputDialogItems.find(sp->first).value();
+ QPair<qt3dsdm::DataModelDataType::Value, bool> spEntry;
+ if (propName == QLatin1String("@timeline")) {
+ spEntry.first = qt3dsdm::DataModelDataType::Value::RangedNumber;
+ spEntry.second = true;
+ } else if (propName == QLatin1String("@slide")) {
+ spEntry.first = qt3dsdm::DataModelDataType::Value::String;
+ spEntry.second = true;
+ } else {
+ qt3dsimp::SImportComposerTypes theTypes;
+ qt3dsimp::SImportAsset &theAsset(theTypes.GetImportAssetForType(
+ qt3dsdm::ComposerObjectTypes::ControllableObject));
+ qt3dsdm::DataModelDataType::Value theType(
+ theAsset.GetPropertyDataType(propName.toStdWString().c_str()));
+ spEntry.first = theType;
+ spEntry.second = false;
+ }
+
+ item->externalPresBoundTypes.insert(sp.key(), spEntry);
+ }
+}
+
+void CStudioApp::verifyDatainputBindings()
+{
+ m_core->GetDoc()->getSceneEditor()->BeginAggregateOperation();
+ bool res = m_core->GetDoc()->VerifyControlledProperties(
+ m_core->GetDoc()->GetActiveRootInstance());
+ m_core->GetDoc()->getSceneEditor()->EndAggregateOperation();
+
+ if (!res) {
+ // we remove invalid control bindings directly without transaction, so
+ // we need to explicitly fire notification in order to update UI
+ m_core->GetDispatch()->FireEndDataModelNotifications();
+ m_core->GetDoc()->SetModifiedFlag(true);
+ m_dialogs->DisplayMessageBox(tr("Invalid datainput usage in UIP file"),
+ tr("Some objects had invalid datainput control bindings."
+ " Invalid entries have been removed."),
+ Qt3DSMessageBox::ICON_WARNING, false);
+ }
+}
+
+/**
+* This will prompt the user for a new name for the duplicated presentation and copy the current
+* presentation with that name in the same folder as the original. The new presentation is added
+* to the project and gets autogenerated id. The new presentation is made current.
+*/
+void CStudioApp::duplicatePresentation(const QString &presFile)
+{
+ QString thePresFile = presFile;
+ bool qmlStream = presFile.endsWith(QLatin1String(".qml"));
+ if (presFile.isEmpty())
+ thePresFile = GetCore()->GetDoc()->GetDocumentPath();
+
+ if (qmlStream || PerformSavePrompt()) {
+ QFileInfo fi(thePresFile);
+ QString relativePresPath = QDir(GetCore()->getProjectFile().getProjectPath())
+ .relativeFilePath(fi.absoluteFilePath());
+
+ EditPresentationIdDlg dlg(relativePresPath,
+ qmlStream ? EditPresentationIdDlg::DuplicateQmlStream
+ : EditPresentationIdDlg::DuplicatePresentation,
+ m_pMainWnd);
+ dlg.exec();
+ const QString newPres = dlg.getDuplicateFile();
+ if (!qmlStream && !newPres.isEmpty())
+ OnLoadDocument(newPres);
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Application/StudioApp.h b/src/Authoring/Qt3DStudio/Application/StudioApp.h
new file mode 100644
index 00000000..86603af0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioApp.h
@@ -0,0 +1,276 @@
+/****************************************************************************
+**
+** 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 INCLUDED_STUDIO_APP_H
+#define INCLUDED_STUDIO_APP_H
+
+#include "StudioObjectTypes.h"
+#include "Qt3DSImportComposerTypes.h"
+#include "Qt3DSDMComposerTypeDefinitions.h"
+#include "DispatchListeners.h"
+#include "Qt3DSDMHandles.h"
+#include <QtWidgets/qapplication.h>
+
+namespace Q3DStudio {
+class IInternalDirectoryWatchingSystem;
+class IDirectoryWatchingSystem;
+class ITickTock;
+class IStudioRenderer;
+struct SSelectedValue;
+}
+
+namespace qt3dsdm {
+class ISignalConnection;
+}
+
+struct StudioManipulationModes
+{
+ enum Enum {
+ Local,
+ Global,
+ };
+};
+
+class CCore;
+class CDialogs;
+class CInspectableBase;
+class CDirectoryWatchingSystemWrapper;
+class CHotKeys;
+class CViews;
+class CMainFrame;
+enum EStudioObjectType;
+struct SubPresentationRecord;
+class CDataInputDialogItem;
+
+QT_FORWARD_DECLARE_CLASS(QCommandLineParser)
+
+class CStudioApp : public QObject,
+ public CCoreAsynchronousEventListener,
+ public CAppStatusListener,
+ public CFailListener,
+ public CPresentationChangeListener // to setup auto set keyframes
+{
+ Q_OBJECT
+public:
+ CStudioApp();
+ virtual ~CStudioApp();
+
+ virtual bool initInstance(const QCommandLineParser &parser, bool isOpenGLES = false);
+ virtual bool run(const QCommandLineParser &parser);
+
+ void onAppAbout();
+
+ Q3DStudio::IDirectoryWatchingSystem &getDirectoryWatchingSystem();
+ void setupTimer(long inMessageId, QWidget *inWnd);
+ Q3DStudio::ITickTock &getTickTock();
+ Q3DStudio::IStudioRenderer &getRenderer();
+ void clearGuides();
+#if (defined Q_OS_MACOS)
+ void openApplication(const QString &inFilename);
+#endif
+ bool handleWelcomeRes(int res, bool recursive);
+
+public Q_SLOTS:
+ void handleMessageReceived(const QString &message, QObject *socket);
+ void performShutdown();
+
+protected:
+ bool runApplication();
+ bool blankRunApplication();
+ bool openAndRunApplication(const QString &inFilename);
+ bool createAndRunApplication(const QString &filename, const QString &folder = QString(),
+ bool isNewProject = true);
+ void initCore();
+ bool showStartupDialog();
+ QString resolvePresentationFile(const QString &inFile);
+
+ CCore *m_core;
+ bool m_isSilent; // true indicates Studio running in silent mode (no GUI)
+ CViews *m_views;
+ long m_toolMode;
+ StudioManipulationModes::Enum m_manipulationMode; // Controls what space the tras, rot, and
+ // scale manipulations work in.
+ long m_selectMode;
+ CDialogs *m_dialogs;
+ long m_playbackTime; // Stores the playhead's starting position so that it can be restored
+ //after playing the presentation for a little while
+ qt3dsdm::Qt3DSDMSlideHandle m_playbackOriginalSlide; // Stores the current slide handle
+ // before playback started.
+
+ std::shared_ptr<Q3DStudio::ITickTock> m_tickTock;
+ std::shared_ptr<Q3DStudio::IDirectoryWatchingSystem> m_directoryWatchingSystem;
+ std::shared_ptr<Q3DStudio::IStudioRenderer> m_renderer;
+ bool m_authorZoom;
+
+private:
+ void playbackPreviewStart();
+ void playbackPreviewEnd();
+ QString getRenderablePath(const QString &filePath) const;
+
+ bool m_welcomeShownThisSession;
+ // are we are launching welcome screen again due to user canceling file dialog?
+ bool m_goStraightToWelcomeFileDialog;
+ bool m_playbackPreviewOn = false;
+ bool m_isOnProgress = false;
+ int m_tutorialPage;
+ QTimer *m_autosaveTimer;
+#if (defined Q_OS_MACOS)
+ bool m_fileOpenEvent = false;
+#endif
+
+public:
+ CMainFrame* m_pMainWnd;
+ QWidget *m_lastActiveView = nullptr;
+ QHash<QString, bool> m_qmlStreamMap;
+
+ CCore *GetCore();
+ CViews *GetViews();
+ CDialogs *GetDialogs();
+ long GetToolMode();
+ void SetToolMode(long inToolMode);
+ long GetSelectMode();
+ void SetSelectMode(long inSelectMode);
+
+ StudioManipulationModes::Enum GetManipulationMode() const;
+ void SetManipulationMode(StudioManipulationModes::Enum inManipulationMode);
+
+ bool CanUndo();
+ bool CanRedo();
+ void OnCopy();
+ bool CanCopy();
+ QString GetCopyType();
+ QString getDuplicateType() const;
+ QString getDeleteType() const;
+ bool canGroupSelectedObjects() const;
+ bool canUngroupSelectedObjects() const;
+ bool groupSelectedObjects() const;
+ bool ungroupSelectedObjects() const;
+ void OnCut();
+ bool CanCut();
+ void OnPaste();
+ bool CanPaste();
+ QString GetPasteType();
+ void SetSelectedObjectTimebarColor();
+ bool CanChangeTimebarColor();
+ void HandleSetChangedKeys();
+ void DeleteSelectedKeys();
+ void DeleteSelectedObject();
+ void HandleDuplicateCommand();
+ void OnToggleAutosetKeyframes();
+ void SetAutosetKeyframes(bool inFlag);
+ void PlaybackPlay();
+ void PlaybackStopNoRestore();
+ void PlaybackRewind();
+ bool IsPlaying();
+ void OnRevert();
+ bool CanRevert() const;
+ void OnFileOpenRecent(const QString &inDocument);
+ bool PerformSavePrompt();
+ void PlaybackStop();
+ bool isPlaybackPreviewOn() const;
+ void AdvanceTime();
+ void ReduceTime();
+ void AdvanceUltraBigTime();
+ void ReduceUltraBigTime();
+ void PlaybackToggle();
+ CInspectableBase *getInspectableFromInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ void RegisterGlobalKeyboardShortcuts(CHotKeys *inShortcutHandler, QWidget *actionParent);
+ bool OnSave(bool autosave = false);
+ void onProjectSaveAs();
+ bool OnLoadDocument(const QString &inDocument, bool inShowStartupDialogOnError = true);
+ void OnLoadDocumentCatcher(const QString &inLocation);
+ void OnFileOpen();
+ QString OnProjectNew();
+ void OnFileNew();
+ bool IsAuthorZoom() const;
+ bool isOnProgress() const;
+ void SetAuthorZoom(bool inZoom);
+ void SetAutosaveEnabled(bool enabled);
+ void SetAutosaveInterval(int interval);
+ void toggleEyeball();
+ void toggleShy();
+ void toggleLocked();
+ void showPresentationIdUniqueWarning();
+ void showPresentationIdEmptyWarning();
+ void showInvalidFilenameWarning();
+ void checkDeletedDatainputs();
+ void saveDataInputsToProjectFile();
+ void verifyDatainputBindings();
+ void duplicatePresentation(const QString &presFile = {});
+
+ // CCoreAsynchronousEventListener
+ void OnAsynchronousCommand(CCmd *inCmd) override;
+
+ // CAppStatusListener
+ void OnDisplayAppStatus(const QString &inStatusMsg) override;
+ void OnProgressBegin(const QString &inActionText,
+ const QString &inAdditionalText) override;
+ void OnProgressEnd() override;
+
+ // CFailListener
+ void OnAssetDeleteFail() override;
+ void OnPasteFail() override;
+ void OnBuildconfigurationFileParseFail(const QString &inMessage) override;
+ void OnSaveFail(bool inKnownError) override;
+ void OnErrorFail(const QString &inText) override;
+ void OnRefreshResourceFail(const QString &inResourceName,
+ const QString &inDescription) override;
+ void OnUndefinedDatainputsFail(
+ const QMultiMap<QString,
+ QPair<qt3dsdm::Qt3DSDMInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle>> *map) override;
+
+ // CPresentationChangeListener
+ void OnNewPresentation() override;
+ void OnPresentationModifiedExternally() override;
+
+ QString m_helpFilePath;
+ QString m_gettingStartedFilePath;
+
+ QVector<SubPresentationRecord> m_subpresentations;
+ QMap<QString, CDataInputDialogItem *> m_dataInputDialogItems;
+
+ QString getPresentationId(const QString &filePath) const;
+ QString getQmlId(const QString &filePath) const;
+ QString getRenderableId(const QString &filePath) const;
+ QString getRenderableAbsolutePath(const QString &renderableId) const;
+ QSize getRenderableSize(const QString &renderableId);
+
+ QString getMostRecentDirectory() const;
+ QString getMostRecentProjectParentDir() const;
+
+ void setLastActiveView(QWidget *widget) { m_lastActiveView = widget; }
+ QWidget *lastActiveView() const { return m_lastActiveView; }
+
+ bool isQmlStream(const QString &fileName);
+};
+
+extern CStudioApp g_StudioApp;
+
+#endif // INCLUDED_STUDIO_APP_H
diff --git a/src/Authoring/Qt3DStudio/Application/StudioConst.h b/src/Authoring/Qt3DStudio/Application/StudioConst.h
new file mode 100644
index 00000000..93a91835
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioConst.h
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#pragma once
+// User Windows messages
+#ifndef WM_APP
+#define WM_APP 0x8000
+#endif
+#define WM_STUDIO_TIMER (WM_APP + 6)
+
+// property pages
+const long PAGE_STUDIOAPPPREFERENCES = 0;
+const long PAGE_STUDIOPROJECTSETTINGS = 1;
+
+// Used to reset default preferences
+const long PREFS_SETTINGS_RESTART = 997;
+const long PREFS_RESET_LAYOUT = 998;
+const long PREFS_RESET_DEFAULTS = 999;
diff --git a/src/Authoring/Qt3DStudio/Application/StudioDefs.h b/src/Authoring/Qt3DStudio/Application/StudioDefs.h
new file mode 100644
index 00000000..0ee45b2c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioDefs.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 Anark 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 STUDIO_DEFINITIONS_H
+#define STUDIO_DEFINITIONS_H 1
+// NOTE: no #pragma once otherwise Mac will not compile
+
+// Copyright date (year only) used through out the Studio program; must be a quoted string.
+#define STUDIO_COPYRIGHT_YEAR "2017-2019"
+
+#endif // STUDIO_DEFINITIONS_H
diff --git a/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.cpp b/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.cpp
new file mode 100644
index 00000000..3acce780
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.cpp
@@ -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$
+**
+****************************************************************************/
+
+#include "StudioTutorialPageIndicator.h"
+#include <QtGui/qpainter.h>
+#include <QtCore/qdebug.h>
+
+StudioTutorialPageIndicator::StudioTutorialPageIndicator(QWidget *parent)
+ : QWidget(parent)
+{
+ m_pixmapActive = QPixmap(":/images/Tutorial/dot_active.png");
+ m_pixmapInactive = QPixmap(":/images/Tutorial/dot_inactive.png");
+}
+
+void StudioTutorialPageIndicator::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event)
+ QPainter painter(this);
+ for (int i = 0; i < m_pageCount; i++) {
+ const QPixmap pixmap = (i == m_currentIndex) ? m_pixmapActive : m_pixmapInactive;
+ painter.drawPixmap(i * (m_dotSize + m_dotMargin), height() - m_dotSize, pixmap);
+ }
+}
+
+void StudioTutorialPageIndicator::mousePressEvent(QMouseEvent *event)
+{
+ int posX = event->pos().x();
+ int index = posX / (m_dotSize + m_dotMargin);
+ emit indexChanged(index);
+}
+
+void StudioTutorialPageIndicator::setCount(int count)
+{
+ m_pageCount = count;
+ int dotSize = m_pixmapActive.width();
+ int margin = 4;
+ setFixedSize(m_pageCount * (dotSize + margin), 80);
+}
+
+void StudioTutorialPageIndicator::setCurrentIndex(int index)
+{
+ m_currentIndex = index;
+ update();
+}
diff --git a/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.h b/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.h
new file mode 100644
index 00000000..6f47ecd0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioTutorialPageIndicator.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 STUDIOTUTORIALPAGEINDICATOR_H
+#define STUDIOTUTORIALPAGEINDICATOR_H
+
+#include <QtWidgets/qwidget.h>
+#include <QtGui/qevent.h>
+
+class StudioTutorialPageIndicator : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit StudioTutorialPageIndicator(QWidget *parent = nullptr);
+
+ void setCount(int count);
+ void setCurrentIndex(int index);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+
+signals:
+ void indexChanged(int index);
+
+private:
+ int m_pageCount = 0;
+ int m_currentIndex = 0;
+ int m_dotSize = 26;
+ int m_dotMargin = 4;
+ QPixmap m_pixmapActive;
+ QPixmap m_pixmapInactive;
+};
+
+#endif // STUDIOTUTORIALPAGEINDICATOR_H
diff --git a/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.cpp b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.cpp
new file mode 100644
index 00000000..bb80e7bf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** 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 "StudioTutorialWidget.h"
+#include "ui_StudioTutorialWidget.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "StudioPreferences.h"
+#include <QtWidgets/qdesktopwidget.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qbrush.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qdesktopservices.h>
+#include <QtCore/qurl.h>
+
+StudioTutorialWidget::StudioTutorialWidget(QWidget *parent) :
+ QDialog(parent, Qt::MSWindowsFixedSizeDialogHint),
+ m_ui(new Ui::StudioTutorialWidget)
+{
+ m_ui->setupUi(this);
+
+ connect(m_ui->studioTutorialShowAgain, &QCheckBox::stateChanged, this,
+ &StudioTutorialWidget::handleDoNotShowAgainChange);
+ connect(m_ui->studioTutorialNew, &QPushButton::clicked, this,
+ &StudioTutorialWidget::handleCreateNew);
+ connect(m_ui->studioTutorialOpen, &QPushButton::clicked, this,
+ &StudioTutorialWidget::handleOpenSample);
+ connect(m_ui->studioTutorialQuickStart, &QPushButton::clicked, this,
+ &StudioTutorialWidget::handleQuickStartGuide);
+
+ QTimer::singleShot(0, this, &StudioTutorialWidget::OnInitDialog);
+
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+}
+
+StudioTutorialWidget::~StudioTutorialWidget()
+{
+ delete m_ui;
+ delete m_backgroundPalette;
+}
+
+void StudioTutorialWidget::OnInitDialog()
+{
+ QSize backgroundSize = size();
+ QRect screenRect = QApplication::desktop()->availableGeometry(
+ QApplication::desktop()->screenNumber(this));
+ QSize windowSize = screenRect.size();
+
+ move(screenRect.x() + (windowSize.width() - backgroundSize.width()) / 2,
+ screenRect.y() + (windowSize.height() - backgroundSize.height()) / 2);
+
+ setFixedSize(backgroundSize);
+
+ QSettings settings;
+ m_ui->studioTutorialShowAgain->setChecked(!settings.value("showWelcomeScreen").toBool());
+ m_backgroundPalette = new QPalette(palette());
+ QPixmap background(size());
+ QPainter backgroundPainter(&background);
+ QLinearGradient gradient = CStudioPreferences::welcomeBackgroundGradient();
+ gradient.setFinalStop(background.width(), 0.0);
+ backgroundPainter.fillRect(background.rect(), gradient);
+ QPixmap laptop(":/images/welcomedialog/laptop.png");
+ backgroundPainter.drawPixmap(0, 100, laptop.width(), laptop.height(), laptop);
+ backgroundPainter.end();
+ m_backgroundPalette->setBrush(backgroundRole(), QBrush(background));
+ setAutoFillBackground(true);
+ setPalette(*m_backgroundPalette);
+}
+
+
+void StudioTutorialWidget::handleDoNotShowAgainChange(int state)
+{
+ QSettings settings;
+ const bool show = !(state == Qt::Checked);
+ settings.setValue("showWelcomeScreen", show);
+}
+
+void StudioTutorialWidget::handleOpenSample()
+{
+ this->done(StudioTutorialWidget::openSampleResult);
+}
+
+void StudioTutorialWidget::handleCreateNew()
+{
+ this->done(StudioTutorialWidget::createNewResult);
+}
+
+void StudioTutorialWidget::handleQuickStartGuide()
+{
+ QFile theFile(g_StudioApp.m_gettingStartedFilePath);
+ if (theFile.exists())
+ QDesktopServices::openUrl(QUrl::fromLocalFile(theFile.fileName()));
+}
diff --git a/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.h b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.h
new file mode 100644
index 00000000..8cd8ca0f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.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 STUDIOTUTORIALWIDGET_H
+#define STUDIOTUTORIALWIDGET_H
+
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qdatetime.h>
+#include <QtGui/qpixmap.h>
+#include <QtGui/qpalette.h>
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class StudioTutorialWidget;
+}
+QT_END_NAMESPACE
+
+class StudioTutorialWidget : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit StudioTutorialWidget(QWidget *parent);
+
+ ~StudioTutorialWidget();
+
+ enum result {
+ acceptResult = QDialog::Accepted,
+ rejectResult = QDialog::Rejected,
+ openSampleResult,
+ createNewResult
+ };
+
+protected:
+ void OnInitDialog();
+
+public:
+ void handleDoNotShowAgainChange(int state);
+ void handleOpenSample();
+ void handleCreateNew();
+ void handleQuickStartGuide();
+
+private:
+ Ui::StudioTutorialWidget *m_ui;
+ QPalette *m_backgroundPalette = nullptr;
+};
+
+#endif // STUDIOTUTORIALWIDGET_H
diff --git a/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.ui b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.ui
new file mode 100644
index 00000000..0a781f37
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/StudioTutorialWidget.ui
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StudioTutorialWidget</class>
+ <widget class="QDialog" name="StudioTutorialWidget">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1200</width>
+ <height>700</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Welcome to Qt 3D Studio</string>
+ </property>
+ <property name="accessibleName">
+ <string>StudioTutorialWidget</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <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="studioTutorialBackground" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QWidget" name="CheckboxLayout" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="studioTutorialShowAgain">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="accessibleName">
+ <string>studioTutorialShowAgain</string>
+ </property>
+ <property name="text">
+ <string>Do Not Show This Again</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="dummyLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_2">
+ <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>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <spacer name="verticalSpacer_4">
+ <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>125</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>620</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>45</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>200</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="studioLabel" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="studioTutorialWelcome">
+ <property name="font">
+ <font>
+ <family>Titillium Web</family>
+ <pointsize>24</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Welcome to</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="studioTutorialName">
+ <property name="font">
+ <font>
+ <family>Titillium Web</family>
+ <pointsize>24</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Qt 3D Studio</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <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>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonsContainer" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonsArea" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>80</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="buttons">
+ <property name="spacing">
+ <number>18</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="studioTutorialQuickStart">
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>24</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="accessibleName">
+ <string>studioTutorialNew</string>
+ </property>
+ <property name="text">
+ <string>Getting Started Guide</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="studioTutorialOpen">
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>24</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="accessibleName">
+ <string>studioTutorialOpen</string>
+ </property>
+ <property name="text">
+ <string>Open Example Project</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="studioTutorialNew">
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>24</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="accessibleName">
+ <string>studioTutorialNew</string>
+ </property>
+ <property name="text">
+ <string>Create New Project</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Application/TimeEditDlg.cpp b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.cpp
new file mode 100644
index 00000000..eb9d0324
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.cpp
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** 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 "ui_TimeEditDlg.h"
+#include "TimeEditDlg.h"
+#include "KeyframeManager.h"
+#include "IDoc.h"
+#include "TimeEnums.h"
+#include <QtGui/qvalidator.h>
+
+CTimeEditDlg::CTimeEditDlg(KeyframeManager *keyframeManager)
+ : m_ui(new Ui::TimeEditDlg)
+ , m_keyframeManager(keyframeManager)
+{
+ m_ui->setupUi(this);
+ setWindowFlag(Qt::WindowContextHelpButtonHint, false); // remove '?' from the dialog title bar
+
+ QIntValidator *minValidator = new QIntValidator(this);
+ minValidator->setRange(0, 9999);
+ m_ui->lineEditMinutes->setValidator(minValidator);
+ QIntValidator *secValidator = new QIntValidator(this);
+ secValidator->setRange(0, 59);
+ m_ui->lineEditSeconds->setValidator(secValidator);
+ QIntValidator *msecValidator = new QIntValidator(this);
+ msecValidator->setRange(0, 999);
+ m_ui->lineEditMilliseconds->setValidator(msecValidator);
+
+ connect(m_ui->lineEditMinutes, &QLineEdit::textEdited, this, &CTimeEditDlg::onTimeChanged);
+ connect(m_ui->lineEditSeconds, &QLineEdit::textEdited, this, &CTimeEditDlg::onTimeChanged);
+ connect(m_ui->lineEditMilliseconds, &QLineEdit::textEdited, this, &CTimeEditDlg::onTimeChanged);
+}
+
+CTimeEditDlg::~CTimeEditDlg()
+{
+ delete m_ui;
+}
+
+/**
+ * Initializes and shows the dialog
+ * @param inTime the initial time which will be shown when the dialog pops up
+ * @param inDoc this can be nullptr where its not applicable
+ * @param inObjectAssociation the identifier for the object associated with the dialog (playhead
+ * or keyframe)
+ */
+void CTimeEditDlg::showDialog(long inTime, IDoc *inDoc, long inObjectAssociation)
+{
+ m_initialTime = inTime;
+ m_objectAssociation = inObjectAssociation;
+ m_Doc = inDoc;
+
+ // Set initial values to dialog
+ formatTime(m_initialTime);
+
+ exec();
+}
+
+void CTimeEditDlg::formatTime(long inTime)
+{
+ long mins = 0;
+ long secs = 0;
+ long mils = 0;
+
+ if (inTime != 0) {
+ mins = inTime % 3600000 / 60000;
+ secs = inTime % 60000 / 1000;
+ mils = inTime % 1000;
+ }
+
+ // display milliseconds in 3 digits (5 -> 005)
+ QString milsStr = QString("%1").arg(mils, 3, 10, QChar('0'));
+ m_ui->lineEditMinutes->setText(QString::number(mins));
+ m_ui->lineEditSeconds->setText(QString::number(secs));
+ m_ui->lineEditMilliseconds->setText(milsStr);
+
+ // Select the biggest non-zero unit
+ if (mins > 0) {
+ m_ui->lineEditMinutes->setFocus();
+ m_ui->lineEditMinutes->selectAll();
+ } else if (secs > 0) {
+ m_ui->lineEditSeconds->setFocus();
+ m_ui->lineEditSeconds->selectAll();
+ } else {
+ m_ui->lineEditMilliseconds->setFocus();
+ m_ui->lineEditMilliseconds->selectAll();
+ }
+}
+
+void CTimeEditDlg::showEvent(QShowEvent *e)
+{
+ onInitDialog();
+ QDialog::showEvent(e);
+}
+
+void CTimeEditDlg::onInitDialog()
+{
+ QString title;
+ // Display the window captions for the correct object type
+ switch (m_objectAssociation) {
+ case PLAYHEAD:
+ title = QObject::tr("Go To Time");
+ break;
+ case ASSETKEYFRAME:
+ title = QObject::tr("Set Keyframe Time");
+ Q_ASSERT(m_keyframeManager != nullptr);
+ break;
+ }
+ setWindowTitle(title);
+ m_ui->labelTitle->setText(title);
+}
+
+void CTimeEditDlg::accept()
+{
+ // Only commit here, cos dup keyframes will be deleted.
+ if (m_objectAssociation == ASSETKEYFRAME && m_Doc) {
+ if (m_endTime == m_initialTime)
+ m_keyframeManager->RollbackChangedKeyframes();
+ else
+ m_keyframeManager->CommitChangedKeyframes();
+ }
+
+ QDialog::accept();
+}
+
+void CTimeEditDlg::reject()
+{
+ // Only commit here, cos dup keyframes will be deleted.
+ if (m_objectAssociation == ASSETKEYFRAME && m_Doc)
+ m_keyframeManager->RollbackChangedKeyframes();
+ QDialog::reject();
+}
+
+/**
+ * Updates the playhead or keyframe time according to the time displayed in the time edit dialogue.
+ * @param inTime the time that will be updated.
+ */
+void CTimeEditDlg::updateObjectTime(long inTime)
+{
+ switch (m_objectAssociation) {
+ case PLAYHEAD: // Update the playhead time
+ if (m_Doc)
+ m_Doc->NotifyTimeChanged(inTime);
+ break;
+ case ASSETKEYFRAME: // Update the keyframe time
+ if (m_Doc) {
+ m_endTime = inTime;
+ m_keyframeManager->moveSelectedKeyframes(inTime);
+ }
+ break;
+ }
+}
+
+void CTimeEditDlg::onTimeChanged()
+{
+ long min = m_ui->lineEditMinutes->text().toInt();
+ long sec = m_ui->lineEditSeconds->text().toInt();
+ long msec = m_ui->lineEditMilliseconds->text().toInt();
+
+ long theGoToTime = min * 60000 + sec * 1000 + msec;
+
+ // make sure min keyframe time doesn't go below zero
+ long offset = m_keyframeManager->getPressedKeyframeOffset();
+ if (theGoToTime - offset < 0) {
+ theGoToTime = offset;
+ formatTime(theGoToTime);
+ }
+ // Go to the time specified in the time edit display
+ updateObjectTime(theGoToTime);
+
+ // If max number of digits reached in a number field, select the next
+ if (m_ui->lineEditMinutes->hasFocus() && min > 999) {
+ m_ui->lineEditSeconds->setFocus();
+ m_ui->lineEditSeconds->selectAll();
+ } else if (m_ui->lineEditSeconds->hasFocus() && sec > 9) {
+ m_ui->lineEditMilliseconds->setFocus();
+ m_ui->lineEditMilliseconds->selectAll();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Application/TimeEditDlg.h b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.h
new file mode 100644
index 00000000..ef4f8d1a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** 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 TIME_EDIT_DIALOG_H
+#define TIME_EDIT_DIALOG_H
+
+#include <QtWidgets/qdialog.h>
+
+class CTimebarControl;
+class IDoc;
+class KeyframeManager;
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class TimeEditDlg;
+}
+QT_END_NAMESPACE
+
+class CTimeEditDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ CTimeEditDlg(KeyframeManager *keyframeManager);
+ virtual ~CTimeEditDlg() override;
+ void showDialog(long inTime, IDoc *inDoc, long inObjectAssociation);
+
+public Q_SLOTS:
+ void accept() override;
+ void reject() override;
+
+protected:
+ void showEvent(QShowEvent *) override;
+
+private:
+ void onInitDialog();
+ void onTimeChanged();
+
+ void formatTime(long inTime);
+ void updateObjectTime(long inTime);
+
+ Ui::TimeEditDlg *m_ui = nullptr;
+ IDoc *m_Doc = nullptr;
+ KeyframeManager *m_keyframeManager = nullptr;
+ long m_initialTime = 0;
+ long m_endTime = 0;
+ long m_objectAssociation = 0;
+};
+#endif // TIME_EDIT_DIALOG_H
diff --git a/src/Authoring/Qt3DStudio/Application/TimeEditDlg.ui b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.ui
new file mode 100644
index 00000000..54a8dbd7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/TimeEditDlg.ui
@@ -0,0 +1,349 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TimeEditDlg</class>
+ <widget class="QDialog" name="TimeEditDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>343</width>
+ <height>119</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>535</width>
+ <height>119</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Set Keyframe Time</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <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">
+ <item>
+ <widget class="QWidget" name="editLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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="labelTitle">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Set keyframe time</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditMinutes">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditSeconds">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditMilliseconds">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="labelsLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <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>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>120</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>min</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>sec</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>ms</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonsLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_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>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </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>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TimeEditDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>207</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>149</x>
+ <y>56</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TimeEditDlg</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>207</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>149</x>
+ <y>56</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Application/TimeEnums.h b/src/Authoring/Qt3DStudio/Application/TimeEnums.h
new file mode 100644
index 00000000..ce671201
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Application/TimeEnums.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2002 NVIDIA Corporation.
+** 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 TIME_ENUMS_H
+#define TIME_ENUMS_H
+
+enum ETimeFormat { MSEC, SEC_MSEC, MIN_SEC_MSEC };
+
+enum ETimeConversionOperation {
+ CONVERT_MIN_TO_MSEC,
+ CONVERT_SEC_TO_MSEC,
+ CONVERT_MSEC_TO_MIN,
+ CONVERT_MSEC_TO_SEC,
+ CONVERT_TIME_TO_MSEC,
+ CONVERT_MSEC_TO_MIN_SEC_MSEC
+};
+
+enum EObjectAssociation {
+ PLAYHEAD,
+ ASSETKEYFRAME
+};
+
+#endif // TIME_ENUMS_H
diff --git a/src/Authoring/Qt3DStudio/Controls/AppFonts.cpp b/src/Authoring/Qt3DStudio/Controls/AppFonts.cpp
new file mode 100644
index 00000000..6b491cf1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/AppFonts.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "AppFonts.h"
+#include "StudioPreferences.h"
+
+/**
+ * Constructor for a CAppFonts object.
+ */
+//=============================================================================
+CAppFonts::CAppFonts()
+{
+ // Normal font
+ const QString theFontFace = CStudioPreferences::GetFontFaceName();
+
+ m_NormalFont.setFamily(theFontFace);
+ m_NormalFont.setPointSize(13);
+}
+
+//==============================================================================
+/**
+ * Releases the object.
+ */
+//==============================================================================
+CAppFonts::~CAppFonts()
+{
+}
+
+//==============================================================================
+/**
+ * Returns a pointer to the only instance of this class. Automatically cleaned
+ * up when the program exits.
+ */
+//==============================================================================
+CAppFonts *CAppFonts::GetInstance()
+{
+ static CAppFonts theAppFonts;
+ return &theAppFonts;
+}
+
+//==============================================================================
+/**
+ * Returns the normal font for use on GUI elements for the application.
+ */
+//==============================================================================
+QFont CAppFonts::GetNormalFont()
+{
+ return m_NormalFont;
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/AppFonts.h b/src/Authoring/Qt3DStudio/Controls/AppFonts.h
new file mode 100644
index 00000000..a3c10b01
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/AppFonts.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** 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
+//==============================================================================
+
+#ifndef INCLUDED_APP_FONTS_H
+#define INCLUDED_APP_FONTS_H 1
+
+#pragma once
+
+//==============================================================================
+// Includes
+//==============================================================================
+
+#include <QtGui/qfont.h>
+//==============================================================================
+/**
+ * Dispatches commands
+ */
+//==============================================================================
+class CAppFonts
+{
+public:
+ CAppFonts();
+ virtual ~CAppFonts();
+ static CAppFonts *GetInstance();
+ QFont GetNormalFont();
+
+private:
+ QFont m_NormalFont;
+};
+
+#endif // INCLUDED_APP_FONTS_H
diff --git a/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.cpp b/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.cpp
new file mode 100644
index 00000000..5a97f0d3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.cpp
@@ -0,0 +1,214 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+
+#include "BufferedRenderer.h"
+#include "AppFonts.h"
+
+
+//=============================================================================
+/**
+ * Create a buffered renderer.
+ * @param inSize the size of this renderer.
+ */
+CBufferedRenderer::CBufferedRenderer(const QSize &inSize)
+ : CWinRenderer()
+{
+ m_CurrentBitmap = QPixmap(inSize);
+
+ // KDAB_TODO: I don't see why m_OldBitmap is needed, nor what is the difference between CBufferedRenderer and COffscreenRenderer
+ m_OldBitmap = m_CurrentBitmap;
+
+ m_painter = new QPainter(&m_CurrentBitmap);
+ QFont font = CAppFonts::GetInstance()->GetNormalFont();
+ m_painter->setFont(font);
+
+ m_Size = inSize;
+
+ PushClippingRect(QRect(QPoint(0,0), inSize));
+}
+
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CBufferedRenderer::~CBufferedRenderer()
+{
+ delete m_painter;
+ m_painter = nullptr;
+}
+
+//=============================================================================
+/**
+ * Copy this BufferedRenderer to inRenderer using alpha blend.
+ * This will semi-transparently copy the contents of this buffer to inRenderer
+ * using the alpha.
+ * @param inRenderer the destination renderer.
+ * @param inAlpha the opacity, 255 = opaque 0 = transparent.
+ */
+/*void CBufferedRenderer::TransparentBltTo( CRenderer* inRenderer, short inAlpha )
+{
+
+ CPt theDestination;
+ theDestination.Offset( inRenderer->GetTranslation( ) );
+
+ AlphaBlendExt( inRenderer->GetGraphicsDevice( )->m_hDC, (short)theDestination.x,
+(short)theDestination.y, (short)m_Size.x, (short)m_Size.y, m_DC->m_hDC, 0, 0, (short)m_Size.x,
+(short)m_Size.y, inAlpha );
+// ::DrawAlphaBlendBitmap( inRenderer->GetDC( ), (short)theDestination.x,
+(short)theDestination.y, m_CurrentBitmap, (short)m_Size.x, (short)m_Size.y, 0, 0, inAlpha );
+
+}*/
+
+//==============================================================================
+/**
+ * Draws a bitmap using alpha blending. Works in NT/2000/9x
+ *
+ * @param inDCDest Destination device context
+ * @param inX X coordinate for drawing
+ * @param inY Y coordinate for drawing
+ * @param inWidth Drawing width
+ * @param inHeight Drawing height
+ * @param inDCSrc Source device context
+ * @param inSourceX X source coordinate
+ * @param inSourceY Y source coordinate
+ * @param inSourceWidth Source drawing width
+ * @param inSourceHeight Source drawing height
+ * @param inAlpha Alpha blend: 0-255, where 255 is opaque
+ */
+//==============================================================================
+/*bool CBufferedRenderer::AlphaBlendExt( HDC inDCDest, short inX, short inY, short inWidth, short
+inHeight, HDC inDCSrc, short inSourceX, short inSourceY, short inSourceWidth, short inSourceHeight,
+short inAlpha )
+{
+ BITMAPINFOHEADER theBMI;
+
+ theBMI.biSize = sizeof( BITMAPINFOHEADER );
+ theBMI.biWidth = inWidth;
+ theBMI.biHeight = inHeight;
+ theBMI.biPlanes = 1;
+ theBMI.biBitCount = 32; // 24 bits + alpha channel
+ theBMI.biCompression = BI_RGB; // no compression
+ theBMI.biSizeImage = 0;
+ theBMI.biXPelsPerMeter = 0;
+ theBMI.biYPelsPerMeter = 0;
+ theBMI.biClrUsed = 0; // use the whole palette
+ theBMI.biClrImportant = 0;
+
+ BYTE* theSrcBits;
+ HBITMAP theBmpSrc;
+
+ // Create DIB section in shared memory
+ theBmpSrc = CreateDIBSection( inDCSrc, ( BITMAPINFO* ) &theBMI, DIB_RGB_COLORS, ( void** )
+&theSrcBits, 0, 0L );
+
+ BYTE* theDestBits;
+ HBITMAP theBmpDest;
+
+ // Create DIB section in shared memory
+ theBmpDest = CreateDIBSection( inDCDest, ( BITMAPINFO* ) &theBMI, DIB_RGB_COLORS, ( void** )
+&theDestBits, 0, 0L );
+
+ // Copy our source and destination bitmaps onto our DIBSections.
+ // so we can get access to their bits using the BYTE*'s we used
+ // in the CreateDIBSection()s above.
+
+ HDC theDC = CreateCompatibleDC( nullptr );
+
+ HBITMAP theDCOld = ( HBITMAP ) SelectObject( theDC, theBmpSrc );
+
+ if ( !StretchBlt( theDC, 0, 0, inWidth, inHeight, inDCSrc, inSourceX, inSourceY,
+inSourceWidth, inSourceHeight, SRCCOPY ) )
+ {
+ SelectObject( theDC, theDCOld );
+ DeleteDC( theDC );
+ DeleteObject( theBmpSrc );
+ DeleteObject( theBmpDest );
+ return false;
+ }
+
+ SelectObject( theDC, theBmpDest );
+
+ if (! StretchBlt( theDC, 0, 0, inWidth, inHeight, inDCDest, inX, inY, inWidth, inHeight,
+SRCCOPY ) )
+ {
+ SelectObject( theDC, theDCOld );
+ DeleteDC( theDC );
+ DeleteObject( theBmpSrc );
+ DeleteObject( theBmpDest );
+ return true;
+ }
+
+ SelectObject( theDC, theDCOld );
+ DeleteDC( theDC );
+
+ short theXLoop, theYLoop;
+
+ for( theYLoop=0; theYLoop<inHeight; ++theYLoop )
+ {
+ LPBYTE theDestRGB = ( LPBYTE ) &( ( DWORD* ) theDestBits)[theYLoop * inWidth];
+ LPBYTE theSrcRGB = ( LPBYTE ) &( ( DWORD* ) theSrcBits)[theYLoop * inWidth];
+
+ for( theXLoop=0; theXLoop<inWidth; theXLoop++ )
+ {
+ theSrcRGB[0] = ( BYTE ) ( ( theDestRGB[0] * ( 255 - inAlpha ) + theSrcRGB[0]
+* inAlpha ) >> 8 );
+ theSrcRGB[1] = ( BYTE ) ( ( theDestRGB[1] * ( 255 - inAlpha ) + theSrcRGB[1]
+* inAlpha ) >> 8 );
+ theSrcRGB[2] = ( BYTE ) ( ( theDestRGB[2] * ( 255 - inAlpha ) + theSrcRGB[2]
+* inAlpha ) >> 8 );
+
+ theSrcRGB += 4;
+ theDestRGB += 4;
+ }
+ }
+
+ theDC = CreateCompatibleDC( nullptr );
+
+ theDCOld = ( HBITMAP ) SelectObject( theDC, theBmpSrc );
+
+ if ( !BitBlt( inDCDest, inX, inY, inWidth, inHeight, theDC, 0, 0, SRCCOPY ) )
+ {
+ SelectObject( theDC, theDCOld );
+ DeleteDC( theDC );
+ DeleteObject( theBmpSrc );
+ DeleteObject( theBmpDest );
+ return false;
+ }
+
+ SelectObject( theDC, theDCOld );
+ DeleteDC( theDC );
+
+ DeleteObject( theBmpSrc );
+ DeleteObject( theBmpDest );
+
+ return true;
+}*/
diff --git a/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.h b/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.h
new file mode 100644
index 00000000..19cc8f78
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/BufferedRenderer.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** 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_BUFFERED_RENDERER_H
+#define INCLUDED_BUFFERED_RENDERER_H 1
+
+#pragma once
+
+#include "WinRenderer.h"
+
+#include <QPainter>
+#include <QPixmap>
+#include <QFont>
+
+class CBufferedRenderer : public CWinRenderer
+{
+public:
+ CBufferedRenderer(const QSize &inSize);
+
+ virtual ~CBufferedRenderer();
+ QPixmap pixmap() const override { return m_CurrentBitmap;}
+
+ // void TransparentBltTo( CRenderer* inRenderer, short inAlpha );
+
+protected:
+ QPixmap m_OldBitmap;
+ QPixmap m_CurrentBitmap;
+ QSize m_Size;
+
+ // bool AlphaBlendExt( HDC inDCDest, short inX, short inY, short inWidth, short inHeight, HDC
+ //inDCSrc, short inSourceX, short inSourceY, short inSourceWidth, short inSourceHeight, short
+ //inAlpha );
+};
+#endif // INCLUDED_BUFFERED_RENDERER_H
diff --git a/src/Authoring/Qt3DStudio/Controls/ClickableLabel.cpp b/src/Authoring/Qt3DStudio/Controls/ClickableLabel.cpp
new file mode 100644
index 00000000..73995fcf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ClickableLabel.cpp
@@ -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$
+**
+****************************************************************************/
+
+#include "ClickableLabel.h"
+
+#include <QMouseEvent>
+
+ClickableLabel::ClickableLabel(QWidget *pr) :
+ QLabel(pr)
+{
+
+}
+
+void ClickableLabel::mousePressEvent(QMouseEvent *event)
+{
+ if (rect().contains(event->pos()))
+ event->accept();
+}
+
+void ClickableLabel::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (rect().contains(event->pos())) {
+ event->accept();
+ Q_EMIT clicked();
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/ClickableLabel.h b/src/Authoring/Qt3DStudio/Controls/ClickableLabel.h
new file mode 100644
index 00000000..591baa9a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ClickableLabel.h
@@ -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$
+**
+****************************************************************************/
+
+#ifndef CLICKABLELABEL_H
+#define CLICKABLELABEL_H
+
+#include <QLabel>
+
+class ClickableLabel : public QLabel
+{
+ Q_OBJECT
+public:
+ ClickableLabel(QWidget *pr = nullptr);
+
+Q_SIGNALS:
+ void clicked();
+
+protected:
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+};
+
+#endif // CLICKABLELABEL_H
diff --git a/src/Authoring/Qt3DStudio/Controls/Control.cpp b/src/Authoring/Qt3DStudio/Controls/Control.cpp
new file mode 100644
index 00000000..03f01403
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/Control.cpp
@@ -0,0 +1,1738 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+
+#include "Control.h"
+#include "Renderer.h"
+#include "MasterP.h"
+#include "StudioUtils.h"
+#include "HotKeys.h"
+#include "ControlGraph.h"
+#include "ControlData.h"
+#include "ResourceCache.h"
+#include "MouseCursor.h"
+
+#include <QtWidgets/qapplication.h>
+
+using namespace Q3DStudio::Control;
+
+IMPLEMENT_OBJECT_COUNTER(CControl)
+
+//=============================================================================
+/**
+ * Constructor, creates a control with all default values.
+ */
+CControl::CControl()
+ : m_cursorSet(-1)
+{
+ m_ControlData = CControlData::CreateControlData(*this);
+ ADDTO_OBJECT_COUNTER(CControl)
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CControl::~CControl()
+{
+ REMOVEFROM_OBJECT_COUNTER(CControl)
+ m_ControlData->ReleaseControl();
+}
+
+//=============================================================================
+/**
+ * Draw this control and all children below it.
+ * This can be overriden by controls to draw themselves but should still be
+ * called if sub controls are to be drawn.
+ * This will call Draw if this control is actually supposed to be drawn. It will
+ * be drawn if it is invalidated or if anything above it in it's heirarchy has
+ * been invalidated.
+ * @param inRenderer the renderer to draw this control out to.
+ */
+void CControl::OnDraw(CRenderer *inRenderer, CRct &inDirtyRect,
+ bool inIgnoreValidation /* = false */)
+{
+ EnsureLayout();
+ bool isInvalidated = IsInvalidated();
+
+ // inRenderer->PushClippingRect( GetSize( ) );
+
+ CRct theBoundingBox = inRenderer->GetClippingRect();
+
+ if (isInvalidated) {
+ CRct theRect(GetSize());
+ theRect.And(theBoundingBox);
+ theRect.Offset(inRenderer->GetTranslation());
+ inDirtyRect.Or(theRect);
+ }
+
+ if (isInvalidated || inIgnoreValidation)
+ Draw(inRenderer);
+
+ // Notify the children in the reverse order that they are drawn.
+ ControlGraph::SReverseIterator theRPos = ControlGraph::GetRChildren(*this);
+ for (; !theRPos.IsDone(); ++theRPos) {
+ (*theRPos)->EnsureLayout();
+ (*theRPos)->BeginDrawChildren(inRenderer);
+ }
+
+ // Go through all the children and draw them in the correct order. By keeping
+ // this order there is a semblance of depth.
+ ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this);
+ for (; !thePos.IsDone(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->IsVisible()) {
+ CPt thePosition = theChild->GetPosition();
+ inRenderer->PushTranslation(thePosition);
+
+ // Clipping of non-visible objects.
+ if (theChild->IsInRect(theBoundingBox))
+ theChild->OnDraw(inRenderer, inDirtyRect, inIgnoreValidation || isInvalidated);
+ else
+ theChild->NotifyNotInClipRect();
+ inRenderer->PopTranslation();
+ }
+ }
+
+ // inRenderer->PopClippingRect( );
+
+ // Set this as not being invalidated.
+ Invalidate(false);
+}
+
+//=============================================================================
+/**
+ * Performs the drawing for this control.
+ * Does nothing by default.
+ * @param inRenderer the renderer to draw to.
+ */
+void CControl::Draw(CRenderer *inRenderer)
+{
+ Q_UNUSED(inRenderer);
+}
+
+//=============================================================================
+/**
+ * Notification that this control is not in the clipping rect and hence will
+ * not be drawn.
+ * By default, tell inform its children
+ */
+void CControl::NotifyNotInClipRect()
+{
+ ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this);
+ for (; !thePos.IsDone(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ theChild->NotifyNotInClipRect();
+ }
+}
+
+//=============================================================================
+/**
+ * Get the position of this control.
+ * The position of this control is relative to it's parent's 0,0 coordinate.
+ * @return the position relative to it's parent's position.
+ */
+CPt CControl::GetPosition() const
+{
+ return m_ControlData->m_Position;
+}
+
+//=============================================================================
+/**
+ * Set the position of this control.
+ * The position of this control is relative to it's parent's 0,0 coordinate.
+ * @return the position relative to it's parent's position.
+ */
+void CControl::SetPosition(CPt inPosition)
+{
+ if (inPosition != m_ControlData->m_Position) {
+ MarkChildrenNeedLayout();
+ m_ControlData->m_Position = inPosition;
+ Invalidate();
+ }
+}
+
+//=============================================================================
+/**
+ * Overload of SetPosition to allow it to take 2 parameters.
+ * This will call SetPosition( CPt ) so there is no need to extend this
+ * method.
+ * @param inX the X position to set.
+ * @param inY the Y position to set.
+ */
+void CControl::SetPosition(long inX, long inY)
+{
+ SetPosition(CPt(inX, inY));
+}
+
+//=============================================================================
+/**
+ * Get the size of the control.
+ * The size is the width and height of the control from it's position.
+ * @return the size of this control.
+ */
+CPt CControl::GetSize() const
+{
+ return m_ControlData->m_Size;
+}
+
+//=============================================================================
+/**
+ * Set the size of this control.
+ * The size is the width and height of the control from it's position.
+ * @param inSize the new size of the control.
+ */
+void CControl::SetSize(CPt inSize)
+{
+ if (GetSize() != inSize) {
+ m_ControlData->m_Size = inSize;
+ if (GetParent() != nullptr)
+ GetParent()->OnChildSizeChanged(this); // this callback needs to die
+ OnSizeChanged(inSize);
+ }
+}
+
+void CControl::OnSizeChanged(CPt /*inSize*/)
+{
+ MarkChildrenNeedLayout();
+ Invalidate();
+}
+
+//=============================================================================
+/**
+ * Overload of SetSize to allow it to take 2 parameters.
+ * This will call SetSize( CPt ) so there is no need to extend this method.
+ * @param inWidth the new width of this control.
+ * @param inHeight the new height of this control.
+ */
+void CControl::SetSize(long inX, long inY)
+{
+ SetSize(CPt(inX, inY));
+}
+
+//=============================================================================
+/**
+ * Get the minimum allowable size of this control.
+ * This is used by layout managers to get the minimum size that a control is
+ * allowed to be when it is resizing the control. This defaults to 0,0.
+ * @return the minimum size that this control is allowed to be.
+ */
+CPt CControl::GetMinimumSize()
+{
+ return m_ControlData->m_MinSize;
+}
+
+//=============================================================================
+/**
+ * Set the minimum allowable size of this control.
+ * This is used by layout managers to get the minimum size that a control is
+ * allowed to be when it is resizing the control. This defaults to 0,0.
+ * param inSize the minimum size that this control is allowed to be.
+ */
+void CControl::SetMinimumSize(CPt inSize)
+{
+ if (inSize != m_ControlData->m_MinSize) {
+ NotifyParentNeedsLayout();
+ m_ControlData->m_MinSize = inSize;
+ if (inSize.x > m_ControlData->m_MaxSize.x)
+ m_ControlData->m_MaxSize.x = inSize.x;
+ if (inSize.y > m_ControlData->m_MaxSize.y)
+ m_ControlData->m_MaxSize.y = inSize.y;
+ }
+}
+
+//=============================================================================
+/**
+ * Get the maximum allowable size of this control.
+ * This is used by layout managers to get the maximum size that a control is
+ * allowed to be when it is resizing the control. This defaults to LONG_MAX,LONG_MAX.
+ * @return the maximum size that this control is allowed to be.
+ */
+CPt CControl::GetMaximumSize()
+{
+ return m_ControlData->m_MaxSize;
+}
+
+//=============================================================================
+/**
+ * Set the maximum allowable size of this control.
+ * This is used by layout managers to get the maximum size that a control is
+ * allowed to be when it is resizing the control. This defaults to LONG_MAX,LONG_MAX.
+ * @param inSize the maximum size that this control is allowed to be.
+ */
+void CControl::SetMaximumSize(CPt inSize)
+{
+ if (inSize != m_ControlData->m_MaxSize) {
+ NotifyParentNeedsLayout();
+ m_ControlData->m_MaxSize = inSize;
+ }
+}
+
+//=============================================================================
+/**
+ * Get the preferred size of this control.
+ * This is used by layout managers to get the preferred size of the control.
+ * The preferred size is often used for relative scaling of sibling controls.
+ * @return the preferred size of this control.
+ */
+CPt CControl::GetPreferredSize()
+{
+ return m_ControlData->m_PrefSize;
+}
+
+//=============================================================================
+/**
+ * Set the preferred size of this control.
+ * This is used by layout managers to get the preferred size of the control.
+ * The preferred size is often used for relative scaling of sibling controls.
+ * @param the preferred size of this control.
+ */
+void CControl::SetPreferredSize(CPt inSize)
+{
+ if (inSize != m_ControlData->m_PrefSize) {
+ NotifyParentNeedsLayout();
+ Invalidate();
+ m_ControlData->m_PrefSize = inSize;
+ }
+}
+
+void CControl::SetAbsoluteSize(CPt inSize)
+{
+ SetMinimumSize(inSize);
+ SetMaximumSize(inSize);
+ SetPreferredSize(inSize);
+ SetSize(inSize);
+}
+
+//=============================================================================
+/**
+ * Event called when the mouse is moving.
+ * This event will be called when the mouse is over this control or when this
+ * control has the focus. This can be extended by controls but should be called
+ * if the event is to be propagated down to child controls.
+ * @param inPoint the location of the mouse relative to this control.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ */
+void CControl::OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ bool theHitFlag = false;
+ // Go through all the children notifying them of mouse moves.
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If a child has not already gotten the move (higher level child covering a lower level
+ // one)
+ // then check the hit test.
+ if (!theHitFlag && theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ // This is the first child to be hit, do not allow the message to go onto another
+ // sibling under this one.
+ theHitFlag = true;
+
+ // Do not fire events to non-enabled children.
+ if (theChild->IsEnabled()) {
+ // If this is the first time the mouse is over this then fire a mouse over
+ if (!theChild->IsMouseOver())
+ theChild->OnMouseOver(theChildPoint, inFlags);
+ // Fire a mouse move as well, this will also propagate the mouse over to
+ // grand-children etc.
+ theChild->OnMouseMove(theChildPoint, inFlags);
+ }
+ }
+ // Check all the children and if they think the mouse is over them then notify them of a
+ // mouse out.
+ else if (theChild->IsMouseOver()) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ theChild->OnMouseOut(theChildPoint, inFlags);
+ }
+ }
+
+ // If there is a child with focus and the mouse is not over it then still notify it of the mouse
+ // move
+ // If the mouse is over it then it would have gotten the move from the above loop.
+ if (m_ControlData->m_MouseFocus && !m_ControlData->m_MouseFocus->IsMouseOver()) {
+ CPt theChildPoint = inPoint - m_ControlData->m_MouseFocus->GetPosition();
+ m_ControlData->m_MouseFocus->OnMouseMove(theChildPoint, inFlags);
+ }
+}
+
+//=============================================================================
+/**
+ * Notification that the mouse is over this control.
+ * This is only called once when the mouse enters the control, and does not get
+ * called until the mouse leaves then re-enters the control.
+ * @param inPoint where the mouse is, relative to this control.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ */
+void CControl::OnMouseOver(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ Q_UNUSED(inPoint);
+ Q_UNUSED(inFlags);
+ m_ControlData->m_IsMouseOver = true;
+}
+
+//=============================================================================
+/**
+ * Notification that the mouse left this control.
+ * This is only called once when the mouse leaves the control and does not get
+ * called until the mouse enters then re-leaves the control.
+ * This also notifies all children that the mouse is over that it is no longer
+ * over.
+ * @param inPoint where the mouse is, relative to this control.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ */
+void CControl::OnMouseOut(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ m_ControlData->m_IsMouseOver = false;
+
+ // Go through all the children looking for ones that think the mouse is over.
+ // If it is then notify it of a mouse out.
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->IsMouseOver()) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ theChild->OnMouseOut(inPoint, inFlags);
+ }
+ }
+}
+
+//=============================================================================
+/**
+ * Notification that the left mouse button was clicked on this control.
+ * This handles the mouse hits and sets the focus to the control that was
+ * clicked on.
+ * @param inPoint where the mouse was clicked, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ * @return true if the mouse event is processed.
+ */
+bool CControl::OnMouseDown(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ bool theRetVal = false;
+ bool theChildGotHit = false;
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ theChildGotHit = true;
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled()) {
+ theRetVal = theChild->OnMouseDown(theChildPoint, inFlags);
+
+ if (m_ControlData->m_Focus != theChild) {
+ if (m_ControlData->m_Focus)
+ m_ControlData->m_Focus->OnLoseFocus();
+ if (theChild->CanGainFocus()) {
+ m_ControlData->m_Focus = theChild;
+ m_ControlData->m_Focus->OnGainFocus();
+ } else {
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ }
+ }
+ m_ControlData->m_MouseFocus = theChild;
+ } else {
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ }
+
+ // only want OnMouseDown to be called on the first child under the point.
+ break;
+ }
+ }
+ if (!theChildGotHit && m_ControlData->m_Focus) {
+ m_ControlData->m_Focus->OnLoseFocus();
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ }
+ m_ControlData->SetMouseDown(!theRetVal);
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Notification that the right mouse button was clicked on this control.
+ * This handles the mouse hits and sets the focus to the control that was
+ * clicked on.
+ * @param inPoint where the mouse was clicked, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ * @return true if the mouse event is processed.
+ */
+bool CControl::OnMouseRDown(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ bool theRetVal = false;
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled()) {
+ if (m_ControlData->m_Focus != theChild) {
+ if (m_ControlData->m_Focus)
+ m_ControlData->m_Focus->OnLoseFocus();
+ if (theChild->CanGainFocus()) {
+ m_ControlData->m_Focus = theChild;
+ m_ControlData->m_Focus->OnGainFocus();
+ } else {
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ }
+ }
+ m_ControlData->m_MouseFocus = theChild;
+ theRetVal = theChild->OnMouseRDown(theChildPoint, inFlags);
+ } else {
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ }
+
+ // only want OnMouseDown to be called on the first child under the point.
+ break;
+ }
+ }
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Notification thatthe mouse was double clicked on this control.
+ * This handled the mouse hits and passes it down to all the children.
+ * @param inPoint where the mouse was clicked, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ * @return true if the mouse event is processed.
+ */
+bool CControl::OnMouseDoubleClick(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ bool theRetVal = false;
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled())
+ theRetVal = theChild->OnMouseDoubleClick(theChildPoint, inFlags);
+
+ // only want OnMouseDown to be called on the first child under the point.
+ break;
+ }
+ }
+
+ return theRetVal;
+}
+
+bool CControl::OnMouseWheel(CPt inPoint, long inAmount, Qt::KeyboardModifiers inFlags)
+{
+ bool theRetVal = false;
+
+ // try letting the focus getting the wheel first
+ if (m_ControlData->m_Focus) {
+ CPt theChildPoint = inPoint - m_ControlData->m_Focus->GetPosition();
+ theRetVal = m_ControlData->m_Focus->OnMouseWheel(theChildPoint, inAmount, inFlags);
+ }
+
+ // if the focus does not want the wheel then let the mouse pos do it.
+ if (!theRetVal) {
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->GetMouseWheelEventState() == ControlEventState::Listening
+ && theChild->IsEnabled() && theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ theRetVal = theChild->OnMouseWheel(theChildPoint, inAmount, inFlags);
+ break;
+ }
+ }
+ }
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Notification that the mouse is hovering over this control.
+ * This handles the mouse hover and passes it down to all the children.
+ * @param inPoint where the mouse is located, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ * @return true if the mouse event is processed.
+ */
+bool CControl::OnMouseHover(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ bool theRetVal = false;
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled())
+ theRetVal = theChild->OnMouseHover(theChildPoint, inFlags);
+
+ // only want OnMouseHover to be called on the first child under the point.
+ break;
+ }
+ }
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Notification that the left mouse button was released.
+ * This is only called on the control that has focus, not on the control under
+ * the mouse.
+ * @param inPoint where the mouse was released, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ */
+void CControl::OnMouseUp(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_ControlData->m_MouseFocus) {
+ CPt theChildPoint = inPoint - m_ControlData->m_MouseFocus->GetPosition();
+ m_ControlData->m_MouseFocus->OnMouseUp(theChildPoint, inFlags);
+ }
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled() && theChild != m_ControlData->m_MouseFocus
+ && theChild != m_ControlData->m_Focus) {
+ theChild->OnMouseUp(theChildPoint, inFlags);
+ }
+
+ // only want OnMouseUp to be called on the first child under the point.
+ break;
+ }
+ }
+ m_ControlData->m_MouseFocus = std::shared_ptr<CControlData>();
+
+ if (m_ControlData->m_IsMouseDown) {
+ OnMouseClick(inPoint, inFlags);
+ m_ControlData->SetMouseDown(false);
+ }
+}
+
+//=============================================================================
+/**
+ * Notification that the right mouse button was released.
+ * This is only called on the control that has focus, not on the control under
+ * the mouse.
+ * @param inPoint where the mouse was released, in local coordinates.
+ * @param inFlags Modifier keys that are down at time of event. Can be any
+ * combination of the following: MODIFIER_CONTROL | MODIFIER_SHIFT |
+ * MODIFIER_ALT | MOUSE_LBUTTON | MOUSE_RBUTTON | MOUSE_MBUTTON
+ */
+void CControl::OnMouseRUp(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_ControlData->m_MouseFocus) {
+ CPt theChildPoint = inPoint - m_ControlData->m_MouseFocus->GetPosition();
+ m_ControlData->m_MouseFocus->OnMouseRUp(theChildPoint, inFlags);
+ }
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ // If the child was hit then notify it of the mouse down, and set the focus to
+ // be it.
+ if (theChild->HitTest(inPoint)) {
+ CPt theChildPoint = inPoint - theChild->GetPosition();
+ // Only send the event if the child is enabled.
+ if (theChild->IsEnabled() && theChild != m_ControlData->m_MouseFocus
+ && theChild != m_ControlData->m_Focus) {
+ theChild->OnMouseRUp(theChildPoint, inFlags);
+ }
+
+ // only want OnMouseUp to be called on the first child under the point.
+ break;
+ }
+ }
+ m_ControlData->m_MouseFocus = std::shared_ptr<CControlData>();
+}
+
+//=============================================================================
+/**
+ * Handles character input from the keyboard.
+ *
+ * @param inChar Character that was pressed
+ * @return true if the character was handled, false if this control does not
+ * care about the character that was pressed
+ */
+bool CControl::OnChar(const QString &inChar, Qt::KeyboardModifiers inModifiers)
+{
+ if (m_ControlData->m_Focus)
+ return m_ControlData->m_Focus->OnChar(inChar, inModifiers);
+ else
+ return false;
+}
+
+//=============================================================================
+/**
+ * Handles a key down message from the keyboard.
+ *
+ * @param inChar Character that was pressed
+ * @return true if the character was handled, false if this control does not
+ * care about the character that was pressed
+ */
+bool CControl::OnKeyDown(unsigned int inChar, Qt::KeyboardModifiers inModifiers)
+{
+ bool theRetVal = false;
+
+ if (m_ControlData->m_Focus)
+ theRetVal = m_ControlData->m_Focus->OnKeyDown(inChar, inModifiers);
+
+ if (!theRetVal) {
+ if (inChar == Qt::Key_Tab) {
+ if (inModifiers & Qt::ShiftModifier)
+ OnReverseTab();
+ else
+ OnTab();
+ theRetVal = true;
+ }
+ }
+
+ return theRetVal;
+}
+
+//=============================================================================
+/**
+ * Handles a key up from the keyboard.
+ *
+ * @param inChar Character that was pressed
+ * @return true if the character was handled, false if this control does not
+ * care about the character that was pressed
+ */
+bool CControl::OnKeyUp(unsigned int inChar, Qt::KeyboardModifiers)
+{
+ Q_UNUSED(inChar);
+ return false;
+}
+
+//=============================================================================
+/**
+ * Find the first child (descendant) control that has a valid drop target.
+ */
+CDropTarget *CControl::FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType)
+{
+ CDropTarget *theDropTarget = NULL;
+
+ // Go through all the children looking for the first one that was clicked on
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->IsMouseOver() && theChild->IsEnabled()) {
+ // Put the point into this childs coords.
+ CPt theChildPoint = inMousePoint - theChild->GetPosition();
+
+ // Allow the child the opportunity to respond
+ theDropTarget = theChild->FindDropCandidate(theChildPoint, inFlags, objectType,
+ fileType);
+
+ if (theDropTarget)
+ break;
+ }
+ }
+ return theDropTarget;
+}
+
+//=============================================================================
+/**
+ * Add a child control to this control.
+ * This will make the child behave as a child of this, get set up for drawing
+ * and events. The inInsertBefore control is used to determine Z-depth, or
+ * manually insert a control into a specific location.
+ * The child cannot already be a child of another control.
+ * @param inControl the control to be added.
+ * @param inInsertBefore the control to be inserted before, or std::shared_ptr<CControlData>() to
+ * be at the back.\
+ */
+void CControl::AddChild(CControl *inControl,
+ CControl *inInsertBefore /*=std::shared_ptr<CControlData>()*/)
+{
+ NotifyParentNeedsLayout();
+ ControlGraph::AddChild(*this, *inControl, inInsertBefore);
+}
+
+//=============================================================================
+/**
+ * Remove a child control from this control.
+ * This will remove it from drawing and getting any events.
+ * @param inControl the control to be removed.
+ */
+void CControl::RemoveChild(CControl *inControl)
+{
+ if (inControl) {
+ ControlGraph::RemoveChild(*this, *inControl);
+
+ if (m_ControlData->m_Focus == inControl->m_ControlData)
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ if (m_ControlData->m_MouseFocus == inControl->m_ControlData)
+ m_ControlData->m_MouseFocus = std::shared_ptr<CControlData>();
+ }
+}
+
+//=============================================================================
+/**
+ * Remove all child controls from this control.
+ * This will remove it from drawing and getting any events.
+ *
+ * This is not recursive
+ */
+void CControl::RemoveAllChildren()
+{
+ NotifyParentNeedsLayout();
+ ControlGraph::RemoveAllChildren(*this);
+
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ m_ControlData->m_MouseFocus = std::shared_ptr<CControlData>();
+}
+
+//=============================================================================
+/**
+ * Retrieve the index of a child control. The index will return the zero based position.
+ * @param inChildControl the control that is a direct child of this control
+ * @return the zero-based index of this control we will return -1 if we don't find the control
+ */
+long CControl::GetChildIndex(CControl *inChildControl)
+{
+ return ControlGraph::GetChildIndex(*this, *inChildControl);
+}
+
+static inline CControl *ToControl(std::shared_ptr<CControlData> inPtr)
+{
+ if (inPtr)
+ return inPtr->GetControl();
+ return nullptr;
+}
+
+//=============================================================================
+/**
+ * Finds a child control by its name
+ * @return the child if found, std::shared_ptr<CControlData>() otherwise
+ */
+CControl *CControl::FindChildByName(const Q3DStudio::CString &inName)
+{
+ std::shared_ptr<CControlData> theResult = std::shared_ptr<CControlData>();
+
+ ControlGraph::SIterator theChildIter = GetChildren();
+ for (; !theChildIter.IsDone(); ++theChildIter) {
+ if (theChildIter.GetCurrent()->GetName() == inName) {
+ theResult = theChildIter.GetCurrent();
+ break;
+ }
+ }
+
+ return ToControl(theResult);
+}
+
+CControl *CControl::FocusedChild()
+{
+ CControl *theResult = nullptr;
+
+ ControlGraph::SIterator theChildIter = GetChildren();
+ for (; !theChildIter.IsDone(); ++theChildIter) {
+ auto current = ToControl(theChildIter.GetCurrent());
+ auto hasFocus = HasFocus(current);
+ if (hasFocus) {
+ if (current->GetFirstChild())
+ theResult = current->FocusedChild();
+ else
+ theResult = current;
+ break;
+ }
+ }
+
+ return theResult;
+}
+
+//=============================================================================
+/**
+ * Check to see if the mouse is over this control or not.
+ * @return true if the mouse is over this control.
+ */
+bool CControl::IsMouseOver() const
+{
+ return m_ControlData->m_IsMouseOver;
+}
+
+//=============================================================================
+/**
+ * Check to see if inPoint is over this control or not.
+ * This is used for mouse hits and can be extended for non-standard control
+ * shapes. Non-visible controls always return false.
+ * @param inPoint the location of the mouse in local coordinates.
+ */
+bool CControl::HitTest(const CPt &inPoint) const
+{
+ CPt thePoint = inPoint - GetPosition();
+ CPt theSize = GetSize();
+ // Basic check to see if it's in the size.
+ if (IsVisible() && thePoint.x >= 0 && thePoint.y >= 0 && thePoint.x < theSize.x
+ && thePoint.y < theSize.y) {
+ return true;
+ }
+ return false;
+}
+
+//=============================================================================
+/**
+ * Checks to see if any part of this control is in the rect.
+ * This is used for drawing and ignoring objects that do not need to be
+ * redrawn or need to be drawn.
+ * @param inRect the rect to check to see if this is in.
+ * @return true if this is in the rect.
+ */
+bool CControl::IsInRect(const CRct &inRect) const
+{
+ CRct myRect(GetPosition(), GetSize());
+
+ if (myRect.position <= inRect.size + inRect.position) {
+ if (myRect.size + myRect.position >= inRect.position)
+ return true;
+ }
+ return false;
+}
+
+//=============================================================================
+/**
+ * Invalidate this control and cause it to be redrawn.
+ * @param inInvalidate true if this is to be invalidated.
+ */
+void CControl::Invalidate(bool inInvalidate /*= true*/)
+{
+ m_ControlData->m_IsInvalidated = inInvalidate;
+ if (inInvalidate && GetParent() != nullptr)
+ GetParent()->OnChildInvalidated();
+ if (!inInvalidate)
+ m_ControlData->m_IsChildInvalidated = false;
+}
+
+//=============================================================================
+/**
+ * Invalidate this object and all children within inRect.
+ * @param inRect the rect in which to invalidate all children.
+ */
+void CControl::InvalidateRect(const CRct &inRect)
+{
+ Invalidate();
+
+ ControlGraph::SIterator thePos = GetChildren();
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->IsInRect(inRect)) {
+ CRct theChildRect = inRect;
+ theChildRect.Offset(theChild->GetPosition());
+
+ theChild->InvalidateRect(inRect);
+ }
+ }
+}
+
+//=============================================================================
+/**
+ * Check to see if this control is invalidated or not.
+ * @return true if this control is invalidated.
+ */
+bool CControl::IsInvalidated() const
+{
+ return m_ControlData->IsInvalidated();
+}
+
+//=============================================================================
+/**
+ * Notifies this control that a child of it has been invalidated.
+ */
+void CControl::OnChildInvalidated()
+{
+ // Only do it if we haven't already, avoid multiple traversals up the tree.
+ if (!m_ControlData->m_IsChildInvalidated) {
+ if (GetParent() != nullptr)
+ GetParent()->OnChildInvalidated();
+ else if (m_ControlData->m_WindowListener != nullptr)
+ m_ControlData->m_WindowListener->OnControlInvalidated();
+
+ m_ControlData->m_IsChildInvalidated = true;
+ }
+}
+
+//=============================================================================
+/**
+ * Checks to see if a child of this control is invalidated.
+ */
+bool CControl::IsChildInvalidated() const
+{
+ return m_ControlData->m_IsChildInvalidated || m_ControlData->m_IsInvalidated;
+}
+
+//=============================================================================
+/**
+ * Set this control as being visible or not.
+ * If the control is not visible then it will not be drawn and will not
+ * get mouse clicks.
+ * @param inIsVisible true if this control is to be visible.
+ */
+void CControl::SetVisible(bool inIsVisible)
+{
+ if (inIsVisible != m_ControlData->m_IsVisible) {
+ m_ControlData->m_IsVisible = inIsVisible;
+ NotifyParentNeedsLayout();
+
+ if (GetParent() != nullptr)
+ GetParent()->OnChildSizeChanged(this);
+
+ OnVisibleStateChange(inIsVisible);
+ OnParentVisibleStateChanged(inIsVisible);
+
+ this->Invalidate();
+ }
+}
+
+//=============================================================================
+/**
+ * Notification that the visible state of a control has changed
+ */
+void CControl::OnVisibleStateChange(bool inIsVisible)
+{
+ Q_UNUSED(inIsVisible);
+}
+
+void CControl::OnParentVisibleStateChanged(bool inIsVisible)
+{
+ NotifyParentNeedsLayout();
+ ControlGraph::SIterator thePos = GetChildren();
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ theChild->OnParentVisibleStateChanged(inIsVisible);
+ }
+}
+
+//=============================================================================
+/**
+ * Checks to see if this control is visible or not.
+ * If the control is not visible then it will not be drawn and will not
+ * get mouse clicks.
+ * @return true if this control is visible.
+ */
+bool CControl::IsVisible() const
+{
+ return m_ControlData->m_IsVisible;
+}
+
+//=============================================================================
+/**
+ * Sets whether or not this control is enabled.
+ * If the control is not enabled then it is still drawn and still intercepts
+ * mouse clicks, but it will not actually process them.
+ * @param inIsEnabled true if this control is to be enabled.
+ */
+void CControl::SetEnabled(bool inIsEnabled)
+{
+ if (inIsEnabled != m_ControlData->m_IsEnabled) {
+ NotifyParentNeedsLayout();
+ m_ControlData->m_IsEnabled = inIsEnabled;
+
+ ControlGraph::SIterator thePos = GetChildren();
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ theChild->SetParentEnabled(inIsEnabled);
+ }
+ Invalidate();
+ }
+}
+
+void CControl::SetParentEnabled(bool inParentEnabled)
+{
+ NotifyParentNeedsLayout();
+ m_ControlData->m_IsParentEnabled = inParentEnabled;
+ ControlGraph::SIterator thePos = GetChildren();
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ theChild->SetParentEnabled(inParentEnabled);
+ }
+ Invalidate();
+}
+
+//=============================================================================
+/**
+ * Gets whether or not this control is enabled.
+ * If the control is not enabled then it is still drawn and still intercepts
+ * mouse clicks, but it will not actually process them.
+ * @param inIsEnabled true if this control is to be enabled.
+ */
+bool CControl::IsEnabled() const
+{
+ return m_ControlData->IsEnabled();
+}
+
+//=============================================================================
+/**
+ * Gets teh value of the enabled flag without its parent's flag
+ */
+bool CControl::GetEnabledFlag()
+{
+ return m_ControlData->GetEnabledFlag();
+}
+
+//=============================================================================
+/**
+ * Sets the enabled flag...this is used when a control wants to override the
+ * SetEnabled function and does not necessarily want to pass enabled messages
+ * to its children no matter what
+ */
+void CControl::SetEnabledFlag(bool inIsEnabled)
+{
+ m_ControlData->SetEnabledFlag(inIsEnabled);
+ Invalidate();
+}
+
+//=============================================================================
+/**
+ * Notification that the size of a child has changed.
+ * @param inControl the control that has changed size.
+ */
+void CControl::OnChildSizeChanged(CControl *inControl)
+{
+ Q_UNUSED(inControl);
+}
+
+void CControl::SetName(const QString &inName)
+{
+ m_ControlData->SetName(Q3DStudio::CString::fromQString(inName));
+}
+
+QString CControl::GetName()
+{
+ return m_ControlData->GetName().toQString();
+}
+
+void CControl::BeginDrawChildren(CRenderer *inRenderer)
+{
+ Q_UNUSED(inRenderer);
+}
+
+long CControl::DoPopup(QMenu *inMenu, CPt inLocation)
+{
+ inLocation.Offset(GetPosition());
+ CControl *theParent(GetParent());
+ if (theParent)
+ return theParent->DoPopup(inMenu, inLocation);
+ else
+ return m_ControlData->m_WindowListener->DoPopup(inMenu, inLocation);
+}
+
+//=============================================================================
+/**
+ * Called when a control acquires focus
+ */
+void CControl::OnGainFocus()
+{
+}
+
+//=============================================================================
+/**
+ * Causes focus to be lost.
+ */
+void CControl::OnLoseFocus()
+{
+ if (m_ControlData->m_Focus)
+ m_ControlData->m_Focus->OnLoseFocus();
+ FireFocusEvent(false);
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+}
+
+//=============================================================================
+/**
+ * @return the parent control of this class
+ */
+CControl *CControl::GetParent()
+{
+ return m_ControlData->GetParent();
+}
+
+const CControl *CControl::GetParent() const
+{
+ return m_ControlData->GetParent();
+}
+
+//=============================================================================
+/**
+ * Removes a control from the top level control
+ */
+void CControl::RemoveUberControl(CControl *inControl)
+{
+ if (GetParent() != nullptr)
+ GetParent()->RemoveUberControl(inControl);
+ else
+ RemoveChild(inControl);
+ Invalidate();
+}
+
+long CControl::GetChildCount()
+{
+ return ControlGraph::GetNumChildren(*this);
+}
+
+Q3DStudio::Control::ControlGraph::SIterator CControl::GetChildren()
+{
+ return ControlGraph::GetChildren(*this);
+}
+
+Q3DStudio::Control::ControlGraph::SReverseIterator CControl::GetReverseChildren()
+{
+ return ControlGraph::GetRChildren(*this);
+}
+
+//=============================================================================
+/**
+ * Gets the global position of the point in regards to the top level control
+ */
+CPt CControl::GetGlobalPosition(CPt inChildPoint) const
+{
+ CPt thePosition(GetPosition());
+ CPt thePoint = CPt(inChildPoint.x + thePosition.x, inChildPoint.y + thePosition.y);
+ if (GetParent())
+ return GetParent()->GetGlobalPosition(thePoint);
+ else
+ return thePoint;
+}
+
+//=============================================================================
+/**
+ * Query the platform specific render device (window)
+ */
+Qt3DSRenderDevice CControl::GetPlatformDevice()
+{
+ if (GetParent())
+ return GetParent()->GetPlatformDevice();
+ return nullptr;
+}
+
+//=============================================================================
+/**
+ * Does self or child use this render device?
+ * @see CWndControl::OnKillFocus
+ */
+bool CControl::IsChildPlatformDevice(Qt3DSRenderDevice inDevice)
+{
+ if (GetPlatformDevice() == inDevice)
+ return true;
+
+ ControlGraph::SIterator thePos = GetChildren();
+ for (; thePos.HasNext(); ++thePos) {
+ std::shared_ptr<CControlData> theChild = (*thePos);
+ if (theChild->IsChildPlatformDevice(inDevice))
+ return true;
+ }
+
+ return false;
+}
+
+//=============================================================================
+/**
+ * Shows the window's moveable window with text
+ *
+ * @param inLocation the postion of hte center point of the window
+ * @param inText the text the window will display
+ */
+void CControl::ShowMoveableWindow(CPt inLocation, const Q3DStudio::CString &inText,
+ CRct inBoundingRct)
+{
+ CPt thePosition(GetPosition());
+ CPt thePoint = CPt(inLocation.x + thePosition.x, inLocation.y + thePosition.y);
+
+ if (GetParent())
+ GetParent()->ShowMoveableWindow(thePoint, inText, inBoundingRct);
+ else if (m_ControlData->m_WindowListener)
+ m_ControlData->m_WindowListener->ShowMoveableWindow(thePoint, inText, inBoundingRct);
+}
+
+//=============================================================================
+/**
+ * Hides the window's moveable window
+ */
+void CControl::HideMoveableWindow()
+{
+ if (GetParent() != nullptr)
+ GetParent()->HideMoveableWindow();
+ else if (m_ControlData->m_WindowListener)
+ m_ControlData->m_WindowListener->HideMoveableWindow();
+}
+
+//=============================================================================
+/**
+ * Offsets the position of this control... this is useful if you don't want to calculate
+ * the global position every time
+ */
+void CControl::OffsetPosition(CPt inOffset)
+{
+ CPt thePosition(GetPosition());
+ thePosition.Offset(inOffset);
+ SetPosition(thePosition);
+}
+
+//=============================================================================
+/**
+ * Gets the first child of this control
+ */
+CControl *CControl::GetFirstChild()
+{
+ std::shared_ptr<CControlData> theChild = std::shared_ptr<CControlData>();
+ ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this);
+ if (!thePos.IsDone())
+ theChild = (*thePos);
+ return ToControl(theChild);
+}
+
+//=============================================================================
+/**
+ * @return true if this control has focus
+ */
+bool CControl::HasFocus(CControl *inControl)
+{
+ return (ToControl(m_ControlData->m_Focus) == inControl);
+}
+
+//=============================================================================
+/**
+ * default is true for controls... override if the control cannot have focus
+ */
+bool CControl::CanGainFocus()
+{
+ if (IsVisible()) {
+ for (ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this); !thePos.IsDone();
+ ++thePos) {
+ if ((*thePos)->CanGainFocus())
+ return true;
+ }
+ }
+ return false;
+}
+
+//=============================================================================
+/**
+ * Returns true if this CControl is in focus.
+ * @return True if this CControl is in focus.
+ */
+bool CControl::IsInFocus()
+{
+ if (GetParent())
+ return GetParent()->HasFocus(this);
+
+ return false;
+}
+
+//=============================================================================
+/**
+ * Handles the tab button in controls
+ */
+void CControl::OnTab()
+{
+ // Go through the children... if there is a focus, then get the next control
+ ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this);
+ bool theFoundFlag = false;
+ if (m_ControlData->m_Focus) {
+ while (!thePos.IsDone() && !theFoundFlag) {
+ std::shared_ptr<CControlData> theCurrentControl = (*thePos);
+ if (theCurrentControl == m_ControlData->m_Focus) {
+ ++thePos;
+ while (!thePos.IsDone() && !theFoundFlag) {
+ std::shared_ptr<CControlData> theNextFocusCanidate = (*thePos);
+ if (theNextFocusCanidate->CanGainFocus()) {
+ m_ControlData->m_Focus->OnLoseFocus();
+ theFoundFlag = true;
+ m_ControlData->m_Focus = theNextFocusCanidate;
+ m_ControlData->m_Focus->SetFocusToFirstAvailable();
+ } else {
+ ++thePos;
+ }
+ }
+ } else {
+ ++thePos;
+ }
+ }
+ // If we didn't find it and we have a parent, then allow the parent to decide
+ if (!theFoundFlag) {
+ m_ControlData->m_Focus->OnLoseFocus();
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ if (GetParent())
+ GetParent()->OnTab();
+ else
+ SetFocusToFirstAvailable();
+ }
+ } else {
+ // If no focus, then go to first available control
+ SetFocusToFirstAvailable();
+ }
+}
+
+//=============================================================================
+/**
+ * Handles the shift tab button in controls
+ */
+void CControl::OnReverseTab()
+{
+ // Go through the children in reverse order... if there is a focus, then get the next control
+ ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ bool theFoundFlag = false;
+ if (m_ControlData->m_Focus) {
+ while (!thePos.IsDone() && !theFoundFlag) {
+ std::shared_ptr<CControlData> theCurrentControl = (*thePos);
+ if (theCurrentControl == m_ControlData->m_Focus) {
+ ++thePos;
+ while (!thePos.IsDone() && !theFoundFlag) {
+ std::shared_ptr<CControlData> theNextFocusCanidate = (*thePos);
+ if (theNextFocusCanidate->CanGainFocus()) {
+ m_ControlData->m_Focus->OnLoseFocus();
+ theFoundFlag = true;
+ m_ControlData->m_Focus = theNextFocusCanidate;
+ m_ControlData->m_Focus->SetFocusToLastAvailable();
+ } else {
+ ++thePos;
+ }
+ }
+ } else {
+ ++thePos;
+ }
+ }
+ // If we didn't find it and we have a parent, then allow the parent to decide
+ if (!theFoundFlag) {
+ m_ControlData->m_Focus->OnLoseFocus();
+ m_ControlData->m_Focus = std::shared_ptr<CControlData>();
+ if (GetParent())
+ GetParent()->OnReverseTab();
+ else
+ SetFocusToLastAvailable();
+ }
+ } else {
+ // If no focus, then go to last available control
+ SetFocusToLastAvailable();
+ }
+}
+
+//=============================================================================
+/**
+ * Sets the focus to the first available child control
+ */
+void CControl::SetFocusToFirstAvailable()
+{
+ bool theFlag = false;
+ OnGainFocus();
+
+ // if there are any child controls, then go through them
+ for (ControlGraph::SIterator thePos = ControlGraph::GetChildren(*this);
+ !thePos.IsDone() && !theFlag; ++thePos) {
+ std::shared_ptr<CControlData> theControl = (*thePos);
+ if (theControl->CanGainFocus()) {
+ m_ControlData->m_Focus = theControl;
+ m_ControlData->m_Focus->SetFocusToFirstAvailable();
+ theFlag = true;
+ }
+ }
+}
+
+//=============================================================================
+/**
+ * Sets the focus to the last available child control
+ */
+void CControl::SetFocusToLastAvailable()
+{
+ bool theFlag = false;
+ OnGainFocus();
+
+ // if there are any child controls, then go through them
+ for (ControlGraph::SReverseIterator thePos = ControlGraph::GetRChildren(*this);
+ !thePos.IsDone() && !theFlag; ++thePos) {
+ std::shared_ptr<CControlData> theControl = (*thePos);
+ if (theControl->CanGainFocus()) {
+ m_ControlData->m_Focus = theControl;
+ m_ControlData->m_Focus->SetFocusToLastAvailable();
+ theFlag = true;
+ }
+ }
+}
+
+//===============================================================================
+/**
+* Coverts the given point from local coordinates into global coordinates.
+* @param inPoint the point in local coordinates to be converted
+* @return The point translated to global coordinates
+*/
+CPt CControl::ClientToScreen(CPt inPoint)
+{
+ CPt theFinalPt = inPoint + m_ControlData->m_Position;
+
+ if (GetParent())
+ theFinalPt = GetParent()->ClientToScreen(theFinalPt);
+ else if (m_ControlData->m_WindowListener)
+ theFinalPt = m_ControlData->m_WindowListener->ClientToScreen(theFinalPt);
+
+ return theFinalPt;
+}
+
+//===============================================================================
+/**
+* Coverts the given point from screen coordinates into local space.
+* @param inPoint the point in screen coordinates to be converted
+* @return The point translated to local, client coordinates
+*/
+CPt CControl::ScreenToClient(CPt inPoint)
+{
+ CPt theFinalPt = inPoint - m_ControlData->m_Position;
+
+ if (GetParent())
+ theFinalPt = GetParent()->ScreenToClient(theFinalPt);
+ else if (m_ControlData->m_WindowListener)
+ theFinalPt = m_ControlData->m_WindowListener->ScreenToClient(theFinalPt);
+
+ return theFinalPt;
+}
+
+//===============================================================================
+/**
+* Adds a focus listener to this control
+*/
+void CControl::AddFocusListener(CChildFocusListener *inListener)
+{
+ m_ControlData->m_FocusListeners.AddListener(inListener);
+}
+
+//===============================================================================
+/**
+* Removes a focus listener to this control
+*/
+void CControl::RemoveFocusListener(CChildFocusListener *inListener)
+{
+ m_ControlData->m_FocusListeners.RemoveListener(inListener);
+}
+
+//===============================================================================
+/**
+* tells anyone listeneing that the focus of this control has changed
+*/
+void CControl::FireFocusEvent(bool inStatus)
+{
+ m_ControlData->m_FocusListeners.FireEvent(&CChildFocusListener::OnChildFocusChange, inStatus);
+}
+
+//===============================================================================
+/**
+ * Get the platform specific view that this is embedded into.
+ * Used for when platform dependent controls have to be embedded or used.
+ */
+TPlatformView CControl::GetPlatformView()
+{
+ if (GetParent())
+ return GetParent()->GetPlatformView();
+ else if (m_ControlData->m_WindowListener)
+ return m_ControlData->m_WindowListener->GetPlatformView();
+ return nullptr;
+}
+
+bool CControl::IsMouseDown()
+{
+ return m_ControlData->m_IsMouseDown;
+}
+
+void CControl::OnMouseClick(CPt, Qt::KeyboardModifiers)
+{
+}
+
+void CControl::GrabFocus(CControl *inControl)
+{
+ if (GetParent())
+ GetParent()->GrabFocus(this);
+
+ std::shared_ptr<CControlData> theNewFocus;
+ if (inControl)
+ theNewFocus = inControl->m_ControlData;
+
+ if (m_ControlData->m_Focus != theNewFocus) {
+ if (m_ControlData->m_Focus)
+ m_ControlData->m_Focus->OnLoseFocus();
+ m_ControlData->m_Focus = theNewFocus;
+ if (m_ControlData->m_Focus)
+ m_ControlData->m_Focus->OnGainFocus();
+ }
+}
+
+//===============================================================================
+/**
+ * Used to notify scrolling views that something should be visible.
+ */
+void CControl::EnsureVisible(CRct inRect)
+{
+ if (GetParent()) {
+ inRect.Offset(GetPosition());
+ GetParent()->EnsureVisible(inRect);
+ }
+}
+
+//===============================================================================
+/**
+ * Call to make this control visible.
+ */
+void CControl::EnsureVisible()
+{
+ CRct theRect(CPt(0, 0), GetSize());
+ EnsureVisible(theRect);
+}
+
+void CControl::OnParentChanged(CControl * /*inNewParent*/)
+{
+}
+
+void CControl::MarkChildrenNeedLayout()
+{
+ if (m_ControlData->m_ChildrenNeedLayout == false) {
+ for (ControlGraph::SIterator theIter = GetChildren(); theIter.IsDone() == false; ++theIter)
+ (*theIter)->MarkNeedsLayout();
+ m_ControlData->m_ChildrenNeedLayout = true;
+ }
+}
+
+void CControl::NotifyParentNeedsLayout()
+{
+ CControl *theParent(GetParent());
+ if (theParent)
+ theParent->MarkChildrenNeedLayout();
+}
+
+void CControl::MarkNeedsLayout()
+{
+ m_ControlData->m_NeedsLayout = true;
+}
+// Tell this control that one of its children need to be layed out.
+void CControl::SetLayout(CPt inSize, CPt inPosition)
+{
+ SetSize(inSize);
+ SetPosition(inPosition);
+ m_ControlData->m_NeedsLayout = false;
+}
+
+void CControl::LayoutChildren()
+{
+ for (ControlGraph::SIterator theIter = GetChildren(); theIter.IsDone() == false; ++theIter) {
+ std::shared_ptr<CControlData> theChild(*theIter);
+ // By default the children get the exact same layout that I do.
+ theChild->SetLayout(theChild->m_Size, theChild->m_Position);
+ }
+ m_ControlData->m_ChildrenNeedLayout = false;
+}
+
+void CControl::EnsureLayout()
+{
+ if (m_ControlData->m_NeedsLayout) {
+ if (IsVisible()) {
+ CControl *parent = GetParent();
+ if (parent)
+ parent->LayoutChildren();
+ }
+ m_ControlData->m_NeedsLayout = false;
+ }
+ if (m_ControlData->m_ChildrenNeedLayout) {
+ LayoutChildren();
+ m_ControlData->m_ChildrenNeedLayout = false;
+ }
+}
+
+void CControl::ChildrenChanged()
+{
+ MarkChildrenNeedLayout();
+ Invalidate();
+ m_ControlData->OnHierarchyChanged();
+}
+
+void CControl::setCursorIfNotSet(long cursor)
+{
+ if (cursor != m_cursorSet) {
+ if (m_cursorSet != -1)
+ qApp->changeOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor));
+ else
+ qApp->setOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor));
+ m_cursorSet = cursor;
+ }
+}
+
+void CControl::resetCursor()
+{
+ if (m_cursorSet != -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_cursorSet = -1;
+ }
+}
+
+QCursor CControl::getCursor() const
+{
+ if (m_cursorSet != -1)
+ return CResourceCache::GetInstance()->GetCursor(m_cursorSet);
+ return QCursor();
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/Control.h b/src/Authoring/Qt3DStudio/Controls/Control.h
new file mode 100644
index 00000000..570005fa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/Control.h
@@ -0,0 +1,259 @@
+/****************************************************************************
+**
+** 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_CONTROL_H
+#define INCLUDED_CONTROL_H 1
+
+#pragma once
+
+#include "Pt.h"
+#include "Rct.h"
+#include "SafeArray.h"
+#include "GenericFunctor.h"
+#include "PlatformTypes.h"
+
+#include "DropTarget.h"
+#include "ControlGraphIterators.h"
+#include "DocumentEditorEnumerations.h"
+
+class CRenderer;
+class CContextMenu;
+class Qt3DSFile;
+class IDragable;
+class CAsset;
+class CStudioApp;
+
+QT_FORWARD_DECLARE_CLASS(QMenu)
+
+namespace Q3DStudio {
+namespace Control {
+ class CControlData;
+ using std::pair;
+ using std::make_pair;
+ using std::vector;
+}
+}
+
+// this functor is used to tell when a child loses focus the boolean should be true if the child
+// gains focus
+GENERIC_FUNCTOR_1(CChildFocusListener, OnChildFocusChange, bool)
+
+#ifdef _WIN32
+typedef HWND TPlatformView;
+#else
+typedef void *TPlatformView;
+#endif
+
+class CControlWindowListener
+{
+public:
+ virtual void OnControlInvalidated() = 0;
+ virtual long DoPopup(QMenu *inMenu, CPt inLocation) = 0;
+ virtual CPt ClientToScreen(CPt inPoint) = 0;
+ virtual CPt ScreenToClient(CPt inPoint) = 0;
+ virtual TPlatformView GetPlatformView() = 0;
+ virtual void SetIsDragging(bool inIsDragging) = 0;
+ virtual void ShowTooltips(CPt inLocation, const QString &inText) = 0;
+ virtual void HideTooltips() = 0;
+ virtual void DoStartDrag(IDragable *inDragable) = 0;
+ virtual void DoStartDrag(std::vector<Q3DStudio::CString> &inDragFileList) = 0;
+ virtual void ShowMoveableWindow(CPt inLocation, const Q3DStudio::CString &inText,
+ CRct inBoundingRct) = 0;
+ virtual void HideMoveableWindow() = 0;
+};
+
+namespace Q3DStudio {
+namespace Control {
+ class CControlData;
+}
+}
+
+class CControl
+{
+
+ CControl(CControl &inOther);
+ CControl &operator=(CControl &inOther);
+
+public:
+ CControl();
+ virtual ~CControl();
+
+ DEFINE_OBJECT_COUNTER(CControl)
+
+ CControl *GetParent();
+ const CControl *GetParent() const;
+
+ virtual void OnDraw(CRenderer *inRenderer, CRct &inDirtyRect, bool inIgnoreValidation = false);
+ virtual void Draw(CRenderer *inRenderer);
+ virtual void NotifyNotInClipRect();
+
+ virtual CPt GetPosition() const;
+ virtual void SetPosition(CPt inPosition);
+ void SetPosition(long inX, long inY);
+
+ virtual CPt GetSize() const;
+ virtual void SetSize(CPt inSize);
+ virtual void SetSize(long inWidth, long inHeight);
+ virtual void OnSizeChanged(CPt inSize);
+
+ virtual CPt GetMinimumSize();
+ virtual void SetMinimumSize(CPt inSize);
+
+ virtual CPt GetMaximumSize();
+ virtual void SetMaximumSize(CPt inSize);
+
+ virtual CPt GetPreferredSize();
+ virtual void SetPreferredSize(CPt inSize);
+
+ virtual void SetAbsoluteSize(CPt inSize);
+
+ virtual void OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual void OnMouseOver(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual void OnMouseOut(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual bool OnMouseDown(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual bool OnMouseRDown(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual void OnMouseUp(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual void OnMouseRUp(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual void OnMouseClick(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual bool OnMouseDoubleClick(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ virtual bool OnMouseHover(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ /**
+ Note that just overriding this isn't enough, you need to call
+ m_ControlData->SetMouseWheelEnabled( true )
+ in order to receive mouse wheel events
+ */
+ virtual bool OnMouseWheel(CPt inPoint, long inAmount, Qt::KeyboardModifiers inFlags);
+ virtual bool OnKeyDown(unsigned int inChar, Qt::KeyboardModifiers inFlags);
+ virtual bool OnKeyUp(unsigned int inChar, Qt::KeyboardModifiers inFlags);
+ virtual bool OnChar(const QString &inChar, Qt::KeyboardModifiers inFlags);
+ virtual void OnLoseFocus();
+ virtual void OnGainFocus();
+ virtual bool CanGainFocus();
+ virtual bool IsInFocus();
+ void OnTab();
+ void OnReverseTab();
+ void SetFocusToFirstAvailable();
+ void SetFocusToLastAvailable();
+
+ virtual CDropTarget *FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType);
+
+ virtual void AddChild(CControl *inControl, CControl *inInsertBefore = NULL);
+ virtual void RemoveChild(CControl *inControl);
+ virtual void RemoveAllChildren();
+ virtual long GetChildIndex(CControl *inChildControl);
+ virtual CControl *FindChildByName(const Q3DStudio::CString &inName);
+ CControl *FocusedChild();
+
+ virtual bool IsMouseOver() const;
+
+ virtual bool HitTest(const CPt &inPoint) const;
+ virtual bool IsInRect(const CRct &inRect) const;
+
+ virtual void Invalidate(bool inInvalidate = true);
+ virtual void InvalidateRect(const CRct &inRect);
+ virtual bool IsInvalidated() const;
+
+ virtual void OnChildInvalidated();
+ virtual bool IsChildInvalidated() const;
+
+ virtual void SetVisible(bool inIsVisible);
+ virtual bool IsVisible() const;
+ virtual void OnVisibleStateChange(bool inIsVisible);
+ virtual void OnParentVisibleStateChanged(bool inIsVisible);
+
+ virtual void SetParentEnabled(bool inParentEnabled);
+ virtual void SetEnabled(bool inIsEnabled);
+ virtual bool IsEnabled() const;
+ bool GetEnabledFlag();
+ void SetEnabledFlag(bool inIsEnabled);
+
+ virtual void OnChildSizeChanged(CControl *inChild);
+ virtual void ResetMinMaxPref(){}
+
+ void SetName(const QString &inName);
+ QString GetName();
+
+ virtual void BeginDrawChildren(CRenderer *inRenderer);
+
+ virtual long DoPopup(QMenu *inContextMenu, CPt inPoint);
+ virtual void RemoveUberControl(CControl *inControl);
+ virtual void OffsetPosition(CPt inOffset);
+
+ virtual CPt GetGlobalPosition(CPt inChildPoint) const;
+ virtual Qt3DSRenderDevice GetPlatformDevice();
+ bool IsChildPlatformDevice(Qt3DSRenderDevice inDevice);
+ virtual void ShowMoveableWindow(CPt inLocation, const Q3DStudio::CString &inText,
+ CRct inBoundingRct);
+ virtual void HideMoveableWindow();
+ CControl *GetFirstChild();
+ bool HasFocus(CControl *inControl);
+ virtual void GrabFocus(CControl *inControl);
+
+ virtual CPt ClientToScreen(CPt inPoint);
+ virtual CPt ScreenToClient(CPt inPoint);
+
+ void AddFocusListener(CChildFocusListener *inListener);
+ void RemoveFocusListener(CChildFocusListener *inListener);
+ void FireFocusEvent(bool inStatus);
+
+ virtual void EnsureVisible(CRct inRect);
+ void EnsureVisible();
+
+ virtual void OnParentChanged(CControl *inNewParent);
+
+ virtual void MarkChildrenNeedLayout();
+ virtual void MarkNeedsLayout();
+ // Tell our parent that we (and all our siblings) need to be
+ // laid out.
+ virtual void NotifyParentNeedsLayout();
+ // Tell this control that one of its children need to be layed out.
+ virtual void SetLayout(CPt inSize, CPt inPosition);
+ virtual void LayoutChildren();
+ virtual void EnsureLayout();
+
+ virtual void ChildrenChanged();
+
+ Q3DStudio::Control::ControlGraph::SIterator GetChildren();
+ Q3DStudio::Control::ControlGraph::SReverseIterator GetReverseChildren();
+
+protected: ///< Current size of this control
+ // Member Functions
+
+ long GetChildCount();
+ TPlatformView GetPlatformView();
+ bool IsMouseDown();
+ void setCursorIfNotSet(long cursor);
+ void resetCursor();
+ QCursor getCursor() const;
+
+ std::shared_ptr<Q3DStudio::Control::CControlData> m_ControlData;
+ long m_cursorSet;
+};
+#endif // INCLUDED_CONTROL_H
diff --git a/src/Authoring/Qt3DStudio/Controls/ControlData.cpp b/src/Authoring/Qt3DStudio/Controls/ControlData.cpp
new file mode 100644
index 00000000..eb3bf0f2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ControlData.cpp
@@ -0,0 +1,765 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "ControlData.h"
+#include "ControlGraph.h"
+#include "Control.h"
+
+#include <QtWidgets/qmenu.h>
+
+using namespace Q3DStudio::Control;
+using Q3DStudio::CString;
+
+CControlData::CControlData(CControl &inControl)
+ : m_Control(&inControl)
+ , m_MinSize(0, 0)
+ , m_MaxSize(LONG_MAX, LONG_MAX)
+ , m_PrefSize(10, 10)
+ , m_IsMouseOver(false)
+ , m_IsInvalidated(true)
+ , m_IsChildInvalidated(false)
+ , m_IsVisible(true)
+ , m_IsEnabled(true)
+ , m_IsParentEnabled(true)
+ , m_HasFocus(false)
+ , m_IsMouseDown(false)
+ , m_ShowTooltips(false)
+ , m_NeedsLayout(true)
+ , m_ChildrenNeedLayout(true)
+ , m_WindowListener(NULL)
+{
+}
+
+CControlData::~CControlData()
+{
+}
+
+void CControlData::ReleaseControl()
+{
+ if (m_Control) {
+ ControlGraph::RemoveNode(*m_Control);
+ m_Control = nullptr;
+ }
+}
+
+std::shared_ptr<CControlData> CControlData::CreateControlData(CControl &inControl)
+{
+ std::shared_ptr<CControlData> retval =
+ std::make_shared<CControlData>(std::ref(inControl));
+ ControlGraph::AddNode(retval);
+ return retval;
+}
+
+CControl *CControlData::GetControl()
+{
+ return m_Control;
+}
+
+CControl *CControlData::GetParent()
+{
+
+ if (m_Control) {
+ std::shared_ptr<CControlData> theParent(ControlGraph::GetParent(*m_Control));
+ if (theParent)
+ return theParent->GetControl();
+ }
+ return nullptr;
+}
+
+void CControlData::OnDraw(CRenderer *inRenderer, CRct &inDirtyRect, bool inIgnoreValidation)
+{
+ if (m_Control)
+ m_Control->OnDraw(inRenderer, inDirtyRect, inIgnoreValidation);
+}
+
+void CControlData::Draw(CRenderer *inRenderer)
+{
+ if (m_Control)
+ m_Control->Draw(inRenderer);
+}
+
+void CControlData::NotifyNotInClipRect()
+{
+ if (m_Control)
+ m_Control->NotifyNotInClipRect();
+}
+
+CPt CControlData::GetPosition() const
+{
+ if (m_Control)
+ return m_Control->GetPosition();
+ return CPt();
+}
+
+void CControlData::SetPosition(CPt inPosition)
+{
+ if (m_Control)
+ m_Control->SetPosition(inPosition);
+}
+
+void CControlData::SetPosition(long inX, long inY)
+{
+ SetPosition(CPt(inX, inY));
+}
+
+CPt CControlData::GetSize() const
+{
+ if (m_Control)
+ return m_Control->GetSize();
+ return CPt();
+}
+
+void CControlData::SetSize(CPt inSize)
+{
+ if (m_Control)
+ m_Control->SetSize(inSize);
+}
+
+void CControlData::SetSize(long inWidth, long inHeight)
+{
+ SetSize(CPt(inWidth, inHeight));
+}
+
+CPt CControlData::GetMinimumSize()
+{
+ if (m_Control)
+ return m_Control->GetMinimumSize();
+ return CPt();
+}
+
+void CControlData::SetMinimumSize(CPt inSize)
+{
+ if (m_Control)
+ m_Control->SetMinimumSize(inSize);
+}
+
+CPt CControlData::GetMaximumSize()
+{
+ if (m_Control)
+ return m_Control->GetMaximumSize();
+ return CPt();
+}
+
+void CControlData::SetMaximumSize(CPt inSize)
+{
+ if (m_Control)
+ m_Control->SetMaximumSize(inSize);
+}
+
+CPt CControlData::GetPreferredSize()
+{
+ if (m_Control)
+ return m_Control->GetPreferredSize();
+ return CPt();
+}
+
+void CControlData::SetPreferredSize(CPt inSize)
+{
+ if (m_Control)
+ return m_Control->SetPreferredSize(inSize);
+}
+
+void CControlData::SetAbsoluteSize(CPt inSize)
+{
+ if (m_Control)
+ m_Control->SetAbsoluteSize(inSize);
+}
+
+void CControlData::SetMouseDown(bool inMouseDown)
+{
+ m_IsMouseDown = inMouseDown;
+}
+
+void CControlData::OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseMove(inPoint, inFlags);
+}
+
+void CControlData::OnMouseOver(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseOver(inPoint, inFlags);
+}
+
+void CControlData::OnMouseOut(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseOut(inPoint, inFlags);
+}
+
+bool CControlData::OnMouseDown(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnMouseDown(inPoint, inFlags);
+ return false;
+}
+
+bool CControlData::OnMouseRDown(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnMouseRDown(inPoint, inFlags);
+ return false;
+}
+
+void CControlData::OnMouseUp(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseUp(inPoint, inFlags);
+}
+
+void CControlData::OnMouseRUp(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseRUp(inPoint, inFlags);
+}
+
+void CControlData::OnMouseClick(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ m_Control->OnMouseClick(inPoint, inFlags);
+}
+
+bool CControlData::OnMouseDoubleClick(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnMouseDoubleClick(inPoint, inFlags);
+ return false;
+}
+
+bool CControlData::OnMouseHover(CPt inPoint, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnMouseHover(inPoint, inFlags);
+ return false;
+}
+
+bool CControlData::OnMouseWheel(CPt inPoint, long inAmount, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnMouseWheel(inPoint, inAmount, inFlags);
+ return false;
+}
+
+bool CControlData::OnKeyDown(unsigned int inChar, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnKeyDown(inChar, inFlags);
+ return false;
+}
+
+bool CControlData::OnKeyUp(unsigned int inChar, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnKeyUp(inChar, inFlags);
+ return false;
+}
+
+bool CControlData::OnChar(const QString &inChar, Qt::KeyboardModifiers inFlags)
+{
+ if (m_Control)
+ return m_Control->OnChar(inChar, inFlags);
+ return false;
+}
+
+void CControlData::OnLoseFocus()
+{
+ if (m_Control)
+ m_Control->OnLoseFocus();
+}
+
+void CControlData::OnGainFocus()
+{
+ if (m_Control)
+ m_Control->OnGainFocus();
+}
+
+bool CControlData::CanGainFocus()
+{
+ if (m_Control)
+ return m_Control->CanGainFocus();
+ return false;
+}
+
+bool CControlData::IsInFocus()
+{
+ if (m_Control)
+ return m_Control->IsInFocus();
+ return false;
+}
+
+void CControlData::OnTab()
+{
+ if (m_Control)
+ m_Control->OnTab();
+}
+
+void CControlData::OnReverseTab()
+{
+ if (m_Control)
+ m_Control->OnReverseTab();
+}
+
+void CControlData::SetFocusToFirstAvailable()
+{
+ if (m_Control)
+ m_Control->SetFocusToFirstAvailable();
+}
+
+void CControlData::SetFocusToLastAvailable()
+{
+ if (m_Control)
+ m_Control->SetFocusToLastAvailable();
+}
+
+void CControlData::ChildrenChanged()
+{
+ if (m_Control)
+ m_Control->ChildrenChanged();
+}
+
+void CControlData::SetMouseWheelEnabled(bool inEnabled)
+{
+ if (m_Control == nullptr)
+ return;
+
+ if (m_MouseWheelState.m_Enabled != inEnabled) {
+ m_MouseWheelState.m_Enabled = inEnabled;
+ ControlEventState::Enum theNewState;
+ if (m_MouseWheelState.m_Enabled == false)
+ theNewState = ControlEventState::Unknown;
+ else
+ theNewState = ControlEventState::Listening;
+ SetMouseWheelEventState(theNewState);
+ }
+}
+
+void CControlData::OnHierarchyChanged()
+{
+ ControlEventState::Enum theNewState;
+ if (m_MouseWheelState.m_Enabled)
+ theNewState = ControlEventState::Listening;
+ else
+ theNewState = ControlEventState::Unknown;
+ SetMouseWheelEventState(theNewState);
+}
+
+/**
+ Look at hierarchy information to figure out which events should
+ trickle down the tree to here (or deeper). Recursive call that
+ may take some time to complete.
+*/
+void CControlData::UpdateMouseWheelEventState()
+{
+ if (m_Control == nullptr)
+ return;
+ ControlEventState::Enum theNewState(ControlEventState::Ignoring);
+ if (m_MouseWheelState.m_Enabled == true)
+ theNewState = ControlEventState::Listening;
+ else if (m_MouseWheelState.m_EventState == ControlEventState::Unknown && m_Control != nullptr) {
+ for (ControlGraph::SIterator theIter(ControlGraph::GetChildren(*m_Control));
+ theIter.IsDone() == false; ++theIter) {
+ if (theIter->GetMouseWheelEventState() == ControlEventState::Listening) {
+ theNewState = ControlEventState::Listening;
+ break;
+ }
+ }
+ }
+ SetMouseWheelEventState(theNewState);
+}
+
+/**
+ Update our mouse wheel state and notify our parent if necessary. Note that we
+ can't set our event state to ignoring without checking all of our children
+ so the result of this function is that our event state is either listening
+ or unknown.
+*/
+void CControlData::SetMouseWheelEventState(ControlEventState::Enum inNewState)
+{
+ if (m_MouseWheelState.m_EventState != inNewState) {
+ m_MouseWheelState.m_EventState = inNewState;
+ std::shared_ptr<CControlData> theParent = ControlGraph::GetParent(*m_Control);
+ if (theParent)
+ theParent->ChildMouseWheelEventStateChanged(m_MouseWheelState.m_EventState);
+ }
+}
+
+/**
+ When a given child notifies us that its event state has changed then we can update
+ our state. We can't set the state to ignoring unless we *know* all of our
+ children are ignoring mouse wheel.
+ */
+void CControlData::ChildMouseWheelEventStateChanged(ControlEventState::Enum inNewState)
+{
+ ControlEventState::Enum theNewState;
+ if (inNewState == ControlEventState::Listening || m_MouseWheelState.m_Enabled == true)
+ theNewState = ControlEventState::Listening;
+ else
+ theNewState = ControlEventState::Unknown;
+
+ SetMouseWheelEventState(theNewState);
+}
+
+ControlEventState::Enum CControlData::GetMouseWheelEventState()
+{
+ if (m_MouseWheelState.m_EventState == ControlEventState::Unknown)
+ UpdateMouseWheelEventState();
+ assert(m_MouseWheelState.m_EventState != ControlEventState::Unknown);
+ return m_MouseWheelState.m_EventState;
+}
+
+CDropTarget *CControlData::FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType)
+{
+ if (m_Control)
+ return m_Control->FindDropCandidate(inMousePoint, inFlags, objectType, fileType);
+ return nullptr;
+}
+
+void CControlData::AddChild(CControl *inControl, CControl *inInsertBefore)
+{
+ if (m_Control)
+ m_Control->AddChild(inControl, inInsertBefore);
+}
+
+void CControlData::RemoveChild(CControl *inControl)
+{
+ if (m_Control)
+ m_Control->RemoveChild(inControl);
+}
+
+void CControlData::RemoveAllChildren()
+{
+ if (m_Control)
+ m_Control->RemoveAllChildren();
+}
+
+long CControlData::GetChildIndex(CControl *inChildControl)
+{
+ if (m_Control)
+ return m_Control->GetChildIndex(inChildControl);
+ return 0;
+}
+
+CControl *CControlData::FindChildByName(const Q3DStudio::CString &inName)
+{
+ if (m_Control)
+ m_Control->FindChildByName(inName);
+ return nullptr;
+}
+
+bool CControlData::IsMouseOver() const
+{
+ if (m_Control)
+ return m_Control->IsMouseOver();
+ return false;
+}
+
+bool CControlData::HitTest(const CPt &inPoint) const
+{
+ if (m_Control)
+ return m_Control->HitTest(inPoint);
+ return false;
+}
+bool CControlData::IsInRect(const CRct &inRect) const
+{
+ if (m_Control)
+ return m_Control->IsInRect(inRect);
+ return false;
+}
+
+void CControlData::Invalidate(bool inInvalidate)
+{
+ if (m_Control)
+ m_Control->Invalidate(inInvalidate);
+}
+
+void CControlData::InvalidateRect(const CRct &inRect)
+{
+ if (m_Control)
+ m_Control->InvalidateRect(inRect);
+}
+
+bool CControlData::IsInvalidated() const
+{
+ return m_IsInvalidated;
+}
+
+void CControlData::OnChildInvalidated()
+{
+ if (m_Control)
+ m_Control->OnChildInvalidated();
+}
+
+bool CControlData::IsChildInvalidated() const
+{
+ if (m_Control)
+ return m_Control->IsChildInvalidated();
+ return false;
+}
+
+void CControlData::SetVisible(bool inIsVisible)
+{
+ if (m_Control)
+ m_Control->SetVisible(inIsVisible);
+}
+
+bool CControlData::IsVisible() const
+{
+ if (m_Control)
+ return m_Control->IsVisible();
+ return false;
+}
+
+void CControlData::OnVisibleStateChange(bool inIsVisible)
+{
+ if (m_Control)
+ m_Control->OnVisibleStateChange(inIsVisible);
+}
+
+void CControlData::OnParentVisibleStateChanged(bool inIsVisible)
+{
+ if (m_Control)
+ m_Control->OnParentVisibleStateChanged(inIsVisible);
+}
+
+void CControlData::SetParentEnabled(bool inParentEnabled)
+{
+ if (m_Control)
+ m_Control->SetParentEnabled(inParentEnabled);
+}
+
+void CControlData::SetEnabled(bool inIsEnabled)
+{
+ if (m_Control)
+ m_Control->SetEnabled(inIsEnabled);
+}
+
+bool CControlData::IsEnabled() const
+{
+ return m_IsEnabled && m_IsParentEnabled;
+}
+
+bool CControlData::GetEnabledFlag()
+{
+ return m_IsEnabled;
+}
+
+void CControlData::SetEnabledFlag(bool inIsEnabled)
+{
+ m_IsEnabled = inIsEnabled;
+}
+
+void CControlData::OnChildSizeChanged(CControl *inChild)
+{
+ if (m_Control)
+ m_Control->OnChildSizeChanged(inChild);
+}
+
+void CControlData::ResetMinMaxPref()
+{
+ if (m_Control)
+ m_Control->ResetMinMaxPref();
+}
+
+void CControlData::SetName(CString inName)
+{
+ m_ControlName = inName;
+}
+
+CString CControlData::GetName()
+{
+ return m_ControlName;
+}
+
+void CControlData::BeginDrawChildren(CRenderer *inRenderer)
+{
+ if (m_Control)
+ m_Control->BeginDrawChildren(inRenderer);
+}
+
+long CControlData::DoPopup(QMenu *inContextMenu, CPt inPoint)
+{
+ if (m_Control)
+ return m_Control->DoPopup(inContextMenu, inPoint);
+ return 0;
+}
+
+void CControlData::RemoveUberControl(CControl *inControl)
+{
+ if (m_Control)
+ m_Control->RemoveUberControl(inControl);
+}
+
+void CControlData::OffsetPosition(CPt inOffset)
+{
+ if (m_Control)
+ m_Control->OffsetPosition(inOffset);
+}
+
+CPt CControlData::GetGlobalPosition(CPt inChildPoint) const
+{
+ if (m_Control)
+ return m_Control->GetGlobalPosition(inChildPoint);
+ return CPt();
+}
+
+Qt3DSRenderDevice CControlData::GetPlatformDevice()
+{
+ if (m_Control)
+ return m_Control->GetPlatformDevice();
+ return nullptr;
+}
+
+bool CControlData::IsChildPlatformDevice(Qt3DSRenderDevice inDevice)
+{
+ if (m_Control)
+ return m_Control->IsChildPlatformDevice(inDevice);
+ return false;
+}
+
+void CControlData::ShowMoveableWindow(CPt inLocation, Q3DStudio::CString inText, CRct inBoundingRct)
+{
+ if (m_Control)
+ m_Control->ShowMoveableWindow(inLocation, inText, inBoundingRct);
+}
+
+void CControlData::HideMoveableWindow()
+{
+ if (m_Control)
+ m_Control->HideMoveableWindow();
+}
+
+CControl *CControlData::GetFirstChild()
+{
+ if (m_Control)
+ m_Control->GetFirstChild();
+ return nullptr;
+}
+
+bool CControlData::HasFocus(CControl *inControl)
+{
+ if (m_Control)
+ return m_Control->HasFocus(inControl);
+ return false;
+}
+
+void CControlData::GrabFocus(CControl *inControl)
+{
+ if (m_Control)
+ m_Control->GrabFocus(inControl);
+}
+
+CPt CControlData::ClientToScreen(CPt inPoint)
+{
+ if (m_Control)
+ return m_Control->ClientToScreen(inPoint);
+ return CPt();
+}
+
+CPt CControlData::ScreenToClient(CPt inPoint)
+{
+ if (m_Control)
+ return m_Control->ScreenToClient(inPoint);
+ return CPt();
+}
+
+void CControlData::AddFocusListener(CChildFocusListener *inListener)
+{
+ if (m_Control)
+ m_Control->AddFocusListener(inListener);
+}
+
+void CControlData::RemoveFocusListener(CChildFocusListener *inListener)
+{
+ if (m_Control)
+ m_Control->RemoveFocusListener(inListener);
+}
+
+void CControlData::FireFocusEvent(bool inStatus)
+{
+ if (m_Control)
+ m_Control->FireFocusEvent(inStatus);
+}
+
+void CControlData::EnsureVisible(CRct inRect)
+{
+ if (m_Control)
+ m_Control->EnsureVisible(inRect);
+}
+
+void CControlData::EnsureVisible()
+{
+ if (m_Control)
+ m_Control->EnsureVisible();
+}
+
+void CControlData::OnParentChanged(CControl *inControl)
+{
+ if (m_Control)
+ m_Control->OnParentChanged(inControl);
+}
+
+void CControlData::NotifyParentNeedsLayout()
+{
+ if (m_Control)
+ m_Control->NotifyParentNeedsLayout();
+}
+
+void CControlData::MarkChildrenNeedLayout()
+{
+ if (m_Control)
+ m_Control->MarkChildrenNeedLayout();
+}
+
+void CControlData::MarkNeedsLayout()
+{
+ if (m_Control)
+ m_Control->MarkNeedsLayout();
+}
+
+// Tell this control that one of its children need to be layed out.
+void CControlData::SetLayout(CPt inSize, CPt inPosition)
+{
+ if (m_Control)
+ m_Control->SetLayout(inSize, inPosition);
+}
+
+void CControlData::LayoutChildren()
+{
+ if (m_Control)
+ m_Control->LayoutChildren();
+}
+
+void CControlData::EnsureLayout()
+{
+ if (m_Control)
+ m_Control->EnsureLayout();
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/ControlData.h b/src/Authoring/Qt3DStudio/Controls/ControlData.h
new file mode 100644
index 00000000..d2f6870e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ControlData.h
@@ -0,0 +1,273 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+#pragma once
+#ifndef CONTROLDATAH
+#define CONTROLDATAH
+
+#include "DropTarget.h"
+#include "Pt.h"
+#include "Rct.h"
+#include "Multicaster.h"
+#include "Qt3DSString.h"
+#include "DocumentEditorEnumerations.h"
+
+class CControl;
+class CControlWindowListener;
+class CChildFocusListener;
+class CRenderer;
+
+QT_FORWARD_DECLARE_CLASS(QMenu)
+
+namespace Q3DStudio {
+namespace Control {
+
+using namespace std;
+
+struct ControlEventState
+{
+ enum Enum {
+ Unknown = 0,
+ Listening,
+ Ignoring,
+ };
+};
+
+struct SControlEventData
+{
+ bool m_Enabled;
+ ControlEventState::Enum m_EventState;
+ SControlEventData()
+ : m_Enabled(false)
+ , m_EventState(ControlEventState::Ignoring)
+ {
+ }
+};
+
+// Smart pointer object that referees access to a control.
+class CControlData
+{
+ Q_DISABLE_COPY(CControlData)
+public:
+ friend class ::CControl;
+ friend class std::shared_ptr<CControlData>;
+
+private:
+ // The physical address of the control is the key in the graph
+ // that points to this data item.
+ CControl *m_Control;
+
+ CPt m_Size; ///< Current size of this control
+
+ CPt m_Position; ///< Position of this control, relative to the parent
+ CPt m_MinSize; ///< Minimum allowed size of this control
+ CPt m_MaxSize; ///< Maximum allowed size of this contrl
+ CPt m_PrefSize; ///< Preferred size of this control
+
+ bool m_IsMouseOver; ///< True if the mouse is over this control
+ bool m_IsInvalidated; ///< True if this control needs to be redrawn
+ bool m_IsChildInvalidated; ///< True if a child of this control is invalidated.
+ bool m_IsVisible; ///< True if this control is to be visible
+ bool m_IsEnabled; ///< True if this control is enabled.
+ bool m_IsParentEnabled;
+ bool m_HasFocus;
+ bool m_IsMouseDown;
+ bool m_ShowTooltips; ///< Specifies whether or not tooltips should be shown
+ bool m_NeedsLayout; ///< True if this control needs to be layed out.
+ bool m_ChildrenNeedLayout; ///< True if my children need layout
+ SControlEventData
+ m_MouseWheelState; ///< Tracks whether this object cares about mouse wheel.
+
+ QString m_TooltipText; ///< Text to be displayed for tooltips
+
+ std::shared_ptr<CControlData> m_Focus; ///< Child control that has the focus.
+ std::shared_ptr<CControlData> m_MouseFocus; ///< Child control that got the mouse down.
+ CControlWindowListener
+ *m_WindowListener; ///< External listener for when this control is invalidated.
+
+ Q3DStudio::CString m_ControlName;
+ CMulticaster<CChildFocusListener *> m_FocusListeners; ///< Used for focus changes
+
+public:
+ CControlData(CControl &inControl);
+ ~CControlData();
+
+ CControl *GetControl();
+
+ CControl *GetParent();
+
+ void OnDraw(CRenderer *inRenderer, CRct &inDirtyRect, bool inIgnoreValidation = false);
+ void Draw(CRenderer *inRenderer);
+ void NotifyNotInClipRect();
+
+ CPt GetPosition() const;
+ void SetPosition(CPt inPosition);
+ void SetPosition(long inX, long inY);
+
+ CPt GetSize() const;
+ void SetSize(CPt inSize);
+ void SetSize(long inWidth, long inHeight);
+
+ CPt GetMinimumSize();
+ void SetMinimumSize(CPt inSize);
+
+ CPt GetMaximumSize();
+ void SetMaximumSize(CPt inSize);
+
+ CPt GetPreferredSize();
+ void SetPreferredSize(CPt inSize);
+
+ void SetAbsoluteSize(CPt inSize);
+
+ void SetMouseDown(bool inMouseDown);
+ void OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ void OnMouseOver(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ void OnMouseOut(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ bool OnMouseDown(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ bool OnMouseRDown(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ void OnMouseUp(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ void OnMouseRUp(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ void OnMouseClick(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ bool OnMouseDoubleClick(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ bool OnMouseHover(CPt inPoint, Qt::KeyboardModifiers inFlags);
+ bool OnMouseWheel(CPt inPoint, long inAmount, Qt::KeyboardModifiers inFlags);
+ bool OnKeyDown(unsigned int inChar, Qt::KeyboardModifiers inFlags);
+ bool OnKeyUp(unsigned int inChar, Qt::KeyboardModifiers inFlags);
+ bool OnChar(const QString &inChar, Qt::KeyboardModifiers inFlags);
+ void OnLoseFocus();
+ void OnGainFocus();
+ bool CanGainFocus();
+ bool IsInFocus();
+ void OnTab();
+ void OnReverseTab();
+ void SetFocusToFirstAvailable();
+ void SetFocusToLastAvailable();
+ void ChildrenChanged();
+
+ // Mouse wheel event state.
+ void SetMouseWheelEnabled(bool inEnabled);
+ /**
+ Callback so we can update any event states to unknown
+ from ignoring forcing a refresh of hierarchy event information
+ the next time the event state is queried.
+ */
+ void OnHierarchyChanged();
+ /**
+ If our event state is unknown, force resolution by asking children what their
+ event state is. Else return our event state.
+ */
+ ControlEventState::Enum GetMouseWheelEventState();
+
+protected:
+ void UpdateMouseWheelEventState();
+ void ChildMouseWheelEventStateChanged(ControlEventState::Enum inNewState);
+ void SetMouseWheelEventState(ControlEventState::Enum inNewState);
+
+public:
+ CDropTarget *FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags,
+ EStudioObjectType objectType,
+ Q3DStudio::DocumentEditorFileType::Enum fileType);
+
+ void AddChild(CControl *inControl, CControl *inInsertBefore = NULL);
+ void RemoveChild(CControl *inControl);
+ void RemoveAllChildren();
+ long GetChildIndex(CControl *inChildControl);
+ CControl *FindChildByName(const Q3DStudio::CString &inName);
+
+ bool IsMouseOver() const;
+
+ bool HitTest(const CPt &inPoint) const;
+ bool IsInRect(const CRct &inRect) const;
+
+ void Invalidate(bool inInvalidate = true);
+ void InvalidateRect(const CRct &inRect);
+ bool IsInvalidated() const;
+
+ void OnChildInvalidated();
+ bool IsChildInvalidated() const;
+
+ void SetVisible(bool inIsVisible);
+ bool IsVisible() const;
+ void OnVisibleStateChange(bool inIsVisible);
+ void OnParentVisibleStateChanged(bool inIsVisible);
+
+ void SetParentEnabled(bool inParentEnabled);
+ void SetEnabled(bool inIsEnabled);
+ bool IsEnabled() const;
+ bool GetEnabledFlag();
+ void SetEnabledFlag(bool inIsEnabled);
+
+ void OnChildSizeChanged(CControl *inChild);
+ void ResetMinMaxPref();
+
+ void SetName(Q3DStudio::CString inName);
+ Q3DStudio::CString GetName();
+
+ void BeginDrawChildren(CRenderer *inRenderer);
+
+ long DoPopup(QMenu *inContextMenu, CPt inPoint);
+ void RemoveUberControl(CControl *inControl);
+ void OffsetPosition(CPt inOffset);
+
+ CPt GetGlobalPosition(CPt inChildPoint) const;
+ Qt3DSRenderDevice GetPlatformDevice();
+ bool IsChildPlatformDevice(Qt3DSRenderDevice inDevice);
+ void ShowMoveableWindow(CPt inLocation, Q3DStudio::CString inText, CRct inBoundingRct);
+ void HideMoveableWindow();
+ CControl *GetFirstChild();
+ bool HasFocus(CControl *inControl);
+ void GrabFocus(CControl *inControl);
+
+ CPt ClientToScreen(CPt inPoint);
+ CPt ScreenToClient(CPt inPoint);
+
+ void AddFocusListener(CChildFocusListener *inListener);
+ void RemoveFocusListener(CChildFocusListener *inListener);
+ void FireFocusEvent(bool inStatus);
+
+ void EnsureVisible(CRct inRect);
+ void EnsureVisible();
+
+ void OnParentChanged(CControl *inControl);
+
+ void NotifyParentNeedsLayout();
+ void MarkChildrenNeedLayout();
+ void MarkNeedsLayout();
+ // Tell this control that one of its children need to be layed out.
+ void SetLayout(CPt inSize, CPt inPosition);
+ void LayoutChildren();
+ void EnsureLayout();
+
+private:
+ static std::shared_ptr<CControlData> CreateControlData(CControl &inControl);
+ void ReleaseControl(); // set the control to null, remove our graph representation
+};
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Controls/ControlGraph.cpp b/src/Authoring/Qt3DStudio/Controls/ControlGraph.cpp
new file mode 100644
index 00000000..34b59b7c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ControlGraph.cpp
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "ControlGraph.h"
+#include "GraphImpl.h"
+#include "foundation/Qt3DSAssert.h"
+#include "Control.h"
+#include "ControlData.h"
+
+using namespace std;
+using namespace Q3DStudio;
+using namespace Q3DStudio::Graph;
+using namespace Q3DStudio::Control;
+using Q3DStudio::CString;
+
+namespace {
+typedef SGraphImpl<CControl *, std::shared_ptr<CControlData>> TGraphType;
+
+TGraphType g_ControlGraph;
+}
+
+namespace Q3DStudio {
+namespace Control {
+ namespace ControlGraph {
+
+ void AddNode(std::shared_ptr<CControlData> inData)
+ {
+ g_ControlGraph.AddRoot(inData->GetControl());
+ g_ControlGraph.SetData(inData->GetControl(), inData);
+ }
+
+ void RemoveNode(CControl &inData) { g_ControlGraph.RemoveChild(&inData, true); }
+
+ void AddChild(CControl &inParent, CControl &inChild, CControl *inNextSibling)
+ {
+ std::shared_ptr<CControlData> oldParent = GetParent(inChild);
+ TGraphType::TNodePtr theParent(g_ControlGraph.GetImpl(&inParent));
+
+ // It actually happens that sometimes inChild gets added with a sibling
+ // but the sibling hasn't actually been added yet.
+ if (inNextSibling != nullptr) {
+ TGraphType::TNodePtr theSibling(g_ControlGraph.GetImpl(inNextSibling));
+ if (theSibling->m_Parent == theParent)
+ g_ControlGraph.MoveBefore(&inChild, inNextSibling);
+ else
+ g_ControlGraph.AddChild(&inParent, &inChild, SGraphPosition::SEnd());
+ } else
+ g_ControlGraph.AddChild(&inParent, &inChild, SGraphPosition::SEnd());
+
+ if (!oldParent || oldParent->GetControl() != &inParent) {
+ inChild.OnParentChanged(&inParent);
+ if (oldParent)
+ oldParent->ChildrenChanged();
+ }
+ inChild.Invalidate();
+ inParent.ChildrenChanged();
+ }
+ // inParent is supplied for error checking purposes.
+ void RemoveChild(CControl &inParent, CControl &inChild)
+ {
+ inChild.OnParentChanged(nullptr);
+ g_ControlGraph.RemoveChild(&inParent, &inChild, false);
+ inChild.Invalidate();
+ inParent.ChildrenChanged();
+ }
+ void RemoveAllChildren(CControl &inParent)
+ {
+ TGraphType::TNodePtr theNode(g_ControlGraph.GetImpl(&inParent));
+ if (theNode == nullptr)
+ return;
+ while (theNode->m_Children.empty() == false) {
+ theNode->m_Children.back()->m_Data->OnParentChanged(nullptr);
+ theNode->m_Children.back()->m_Data->NotifyParentNeedsLayout();
+ g_ControlGraph.RemoveChild(&inParent, theNode->m_Children.back()->m_GraphableID,
+ false);
+ }
+ inParent.ChildrenChanged();
+ }
+ void MoveTo(CControl &inParent, CControl &inChild, const Graph::SGraphPosition &inPosition)
+ {
+ std::shared_ptr<CControlData> theOldParent(GetParent(inChild));
+ g_ControlGraph.MoveTo(&inParent, &inChild, inPosition);
+ if (!theOldParent || theOldParent->GetControl() != &inParent)
+ inChild.OnParentChanged(&inParent);
+ inChild.NotifyParentNeedsLayout();
+ inParent.MarkChildrenNeedLayout();
+ if (theOldParent) {
+ theOldParent->ChildrenChanged();
+ }
+ inParent.ChildrenChanged();
+ inChild.Invalidate();
+ }
+ long GetNumChildren(CControl &inControl)
+ {
+ return g_ControlGraph.GetChildCount(&inControl);
+ }
+
+ long GetChildIndex(CControl &inParent, CControl &inChild)
+ {
+ QT3DS_ASSERT(GetParent(inChild)->GetControl() == &inParent);
+
+ SGraphPosition thePos = g_ControlGraph.GetNodePosition(&inChild);
+ return thePos.GetIndex();
+ }
+
+ std::shared_ptr<CControlData> GetChild(CControl &inParent, long inIndex)
+ {
+ TGraphType::TNodePtr theNode(g_ControlGraph.GetImpl(&inParent));
+ if (theNode && inIndex < (long)theNode->m_Children.size() && inIndex > -1)
+ return theNode->m_Children[inIndex]->m_Data;
+ return std::shared_ptr<CControlData>();
+ }
+
+ std::shared_ptr<CControlData> GetParent(CControl &inControl)
+ {
+ TGraphType::TNodePtr theNode(g_ControlGraph.GetImpl(&inControl));
+ if (theNode && theNode->m_Parent)
+ return theNode->m_Parent->m_Data;
+ return std::shared_ptr<CControlData>();
+ }
+
+ // Dummy struct to hide graph implementation
+ struct SDummyGraphNode : public TGraphType::TNodeType
+ {
+ SDummyGraphNode(CControl *const &inIdentifier,
+ const std::shared_ptr<CControlData> &inNodeData =
+ std::shared_ptr<CControlData>())
+ : TGraphType::TNodeType(inIdentifier, inNodeData)
+ {
+ }
+ };
+
+ std::shared_ptr<CControlData> SIteratorBase::GetCurrent()
+ {
+ if (m_Children && m_Index < (long)m_Children->size())
+ return (*m_Children)[m_Index]->m_Data;
+
+ QT3DS_ASSERT(false);
+ return std::shared_ptr<CControlData>();
+ }
+
+ SReverseIterator GetRChildren(CControl &inControl)
+ {
+ TGraphType::TNodePtr theNode(g_ControlGraph.GetImpl(&inControl));
+ if (theNode)
+ return SReverseIterator(
+ theNode->m_Data,
+ reinterpret_cast<vector<SDummyGraphNode *> &>(theNode->m_Children));
+ return SReverseIterator();
+ }
+
+ SIterator GetChildren(CControl &inControl)
+ {
+ TGraphType::TNodePtr theNode(g_ControlGraph.GetImpl(&inControl));
+ if (theNode)
+ return SIterator(theNode->m_Data, reinterpret_cast<vector<SDummyGraphNode *> &>(
+ theNode->m_Children));
+ return SIterator();
+ }
+ }
+}
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/ControlGraph.h b/src/Authoring/Qt3DStudio/Controls/ControlGraph.h
new file mode 100644
index 00000000..0dfe2b30
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ControlGraph.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+#pragma once
+#ifndef CONTROLGRAPHH
+#define CONTROLGRAPHH
+#include "Multicaster.h"
+#include "ControlGraphIterators.h"
+
+class CControl;
+class CControlWindowListener;
+class CChildFocusListener;
+class CRenderer;
+
+namespace Q3DStudio {
+namespace Graph {
+
+ struct SGraphPosition; // GraphPosition.h"
+}
+
+namespace Control {
+ class ControlData;
+
+ namespace ControlGraph {
+ void AddNode(std::shared_ptr<CControlData> inData);
+ void RemoveNode(CControl &inData);
+ void AddChild(CControl &inParent, CControl &inChild, CControl *inNextSibling);
+ // inParent is supplied for error checking purposes.
+ void RemoveChild(CControl &inParent, CControl &inChild);
+ void RemoveAllChildren(CControl &inParent);
+ void MoveTo(CControl &inParent, CControl &inChild, const Graph::SGraphPosition &inPosition);
+ long GetNumChildren(CControl &inControl);
+ long GetChildIndex(CControl &inParent, CControl &inChild);
+ std::shared_ptr<CControlData> GetChild(CControl &inParent, long inIndex);
+ std::shared_ptr<CControlData> GetParent(CControl &inControl);
+
+ SReverseIterator GetRChildren(CControl &inControl);
+ SIterator GetChildren(CControl &inControl);
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Controls/ControlGraphIterators.h b/src/Authoring/Qt3DStudio/Controls/ControlGraphIterators.h
new file mode 100644
index 00000000..ae2ffcf6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/ControlGraphIterators.h
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+#pragma once
+#ifndef CONTROLGRAPHITERATORSH
+#define CONTROLGRAPHITERATORSH
+#include <vector>
+
+namespace Q3DStudio {
+namespace Control {
+ class CControlData;
+ using std::vector;
+
+ namespace ControlGraph {
+ struct SDummyGraphNode;
+
+ struct SIteratorBase
+ {
+ protected:
+ std::shared_ptr<CControlData> m_ControlData;
+ vector<SDummyGraphNode *> *m_Children;
+ long m_Index;
+
+ public:
+ SIteratorBase()
+ : m_Children(nullptr)
+ , m_Index(0)
+ {
+ }
+
+ SIteratorBase(std::shared_ptr<CControlData> data,
+ vector<SDummyGraphNode *> &inChildren)
+ : m_ControlData(data)
+ , m_Children(&inChildren)
+ , m_Index(0)
+ {
+ }
+
+ SIteratorBase(const SIteratorBase &inOther)
+ : m_ControlData(inOther.m_ControlData)
+ , m_Children(inOther.m_Children)
+ , m_Index(inOther.m_Index)
+ {
+ }
+
+ SIteratorBase &operator=(const SIteratorBase &inOther)
+ {
+ if (this != &inOther) {
+ m_ControlData = inOther.m_ControlData;
+ m_Children = inOther.m_Children;
+ m_Index = inOther.m_Index;
+ }
+ return *this;
+ }
+
+ bool operator==(const SIteratorBase &other) { return m_Index == other.m_Index; }
+ bool operator!=(const SIteratorBase &other) { return m_Index != other.m_Index; }
+
+ std::shared_ptr<CControlData> GetCurrent();
+
+ std::shared_ptr<CControlData> operator->() { return GetCurrent(); }
+ std::shared_ptr<CControlData> operator*() { return GetCurrent(); }
+ };
+
+ struct SReverseIterator : SIteratorBase
+ {
+ SReverseIterator() {}
+ SReverseIterator(std::shared_ptr<CControlData> data,
+ vector<SDummyGraphNode *> &inChildren)
+ : SIteratorBase(data, inChildren)
+ {
+ m_Index = (long)inChildren.size() - 1;
+ }
+ SReverseIterator(const SReverseIterator &inOther)
+ : SIteratorBase(inOther)
+ {
+ }
+ SReverseIterator &operator=(const SReverseIterator &inOther)
+ {
+ SIteratorBase::operator=(inOther);
+ return *this;
+ }
+
+ bool IsDone() { return m_Children == nullptr || m_Index < 0; }
+ bool HasNext() { return m_Children && m_Index > -1; }
+ SReverseIterator &operator++()
+ {
+ --m_Index;
+ return *this;
+ }
+ SReverseIterator &operator+=(long inAmount)
+ {
+ m_Index -= inAmount;
+ return *this;
+ }
+ };
+
+ struct SIterator : SIteratorBase
+ {
+ SIterator() {}
+ SIterator(std::shared_ptr<CControlData> data, vector<SDummyGraphNode *> &inChildren)
+ : SIteratorBase(data, inChildren)
+ {
+ }
+ SIterator(const SReverseIterator &inOther)
+ : SIteratorBase(inOther)
+ {
+ }
+ SIterator &operator=(const SIterator &inOther)
+ {
+ SIteratorBase::operator=(inOther);
+ return *this;
+ }
+
+ bool IsDone() { return m_Children == nullptr || m_Index >= (long)m_Children->size(); }
+ bool HasNext() { return m_Children && m_Index < (long)m_Children->size(); }
+ SIterator operator++()
+ {
+ ++m_Index;
+ return *this;
+ }
+ SIterator &operator+=(long inAmount)
+ {
+ m_Index += inAmount;
+ return *this;
+ }
+ };
+ }
+}
+}
+#endif \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.cpp b/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.cpp
new file mode 100644
index 00000000..e30797f7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "OffscreenRenderer.h"
+#include "AppFonts.h"
+
+/**
+ * Constructor: Overrides a protected constructor on the base class. Sets up
+ * all the requirements so that this renderer is valid.
+ */
+COffscreenRenderer::COffscreenRenderer(const QRect &inClippingRect)
+ : CWinRenderer()
+{
+ m_pixmap = QPixmap(inClippingRect.size());
+ m_painter = new QPainter(&m_pixmap);
+ QFont font = CAppFonts::GetInstance()->GetNormalFont();
+ m_painter->setFont(font);
+
+ // Set up the specified clipping region. Renderer is now valid for use.
+ PushClippingRect(inClippingRect);
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+COffscreenRenderer::~COffscreenRenderer()
+{
+ delete m_painter;
+ m_painter = nullptr;
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.h b/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.h
new file mode 100644
index 00000000..dccd259f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/OffscreenRenderer.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** 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_OFFSCREEN_RENDERER_H
+#define INCLUDED_OFFSCREEN_RENDERER_H 1
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "WinRenderer.h"
+
+//=============================================================================
+/**
+ * Class for creating a renderer compatible with the current display, but that
+ * does not actually draw to the display. Provided so that you controls can
+ * query text size outside of their draw functions. This class could be
+ * further extended to provide offscreen drawing and blitting, though it's
+ * not currently set up to do so.
+ */
+class COffscreenRenderer : public CWinRenderer
+{
+public:
+ COffscreenRenderer(const QRect &inClippingRect);
+ virtual ~COffscreenRenderer();
+
+ QPixmap pixmap() const override { return m_pixmap;}
+
+protected:
+ QPixmap m_pixmap;
+};
+
+#endif // INCLUDED_OFFSCREEN_RENDERER_H
diff --git a/src/Authoring/Qt3DStudio/Controls/Renderer.cpp b/src/Authoring/Qt3DStudio/Controls/Renderer.cpp
new file mode 100644
index 00000000..5b0a6296
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/Renderer.cpp
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+
+#include "Renderer.h"
+
+#include <QtCore/qpoint.h>
+#include <QtCore/qrect.h>
+
+/**
+ * Draws the outline of a rectangle in the specified colors.
+ *
+ * @param inRect Rectangle to be outlined
+ * @param inTop Color of the top line
+ * @param inRight Color of the line on the right side
+ * @param inBottom Color of the line on the bottom
+ * @param inLeft Color of the line on the left side
+ */
+void CRenderer::DrawRectOutline(const QRect &inRect, const QColor &inTop, const QColor &inRight,
+ const QColor &inBottom, const QColor &inLeft)
+{
+ QPoint theUpperLeft(inRect.topLeft());
+ QPoint theUpperRight(inRect.topRight());
+ QPoint theLowerRight(inRect.bottomRight());
+ QPoint theLowerLeft(inRect.bottomLeft());
+
+ // Top
+ PushPen(inTop, 1);
+ MoveTo(theUpperLeft.x(), theUpperLeft.y());
+ LineTo(theUpperRight.x(), theUpperRight.y());
+ PopPen();
+
+ // Right
+ PushPen(inRight, 1);
+ LineTo(theLowerRight.x(), theLowerRight.y());
+ PopPen();
+
+ // Bottom
+ PushPen(inBottom, 1);
+ LineTo(theLowerLeft.x(), theLowerLeft.y());
+ PopPen();
+
+ // Left
+ PushPen(inLeft, 1);
+ LineTo(theUpperLeft.x(), theUpperLeft.y());
+ PopPen();
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/Renderer.h b/src/Authoring/Qt3DStudio/Controls/Renderer.h
new file mode 100644
index 00000000..7ef77fe3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/Renderer.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** 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_RENDERER_H
+#define INCLUDED_RENDERER_H 1
+
+#pragma once
+
+#include <QColor>
+#include <QPoint>
+
+#include "CColor.h"
+
+QT_BEGIN_NAMESPACE
+class QPainter;
+class QPixmap;
+class QRect;
+class QSize;
+class QString;
+QT_END_NAMESPACE
+
+class CRenderer
+{
+ typedef std::vector<QPoint> TTranslationList;
+
+public:
+ virtual ~CRenderer() {}
+
+ virtual QPainter* GetPainter() = 0;
+ virtual void FillSolidRect(const QRect &inCoordinates, const QColor &inColor) = 0;
+ virtual void FillRoundedRect(const QRect &inCoordinates, const QColor &inColor,
+ bool vertical) = 0;
+ virtual void MoveTo(const QPoint &inPoint) = 0;
+ virtual void MoveTo(long inX, long inY) = 0;
+ virtual void LineTo(const QPoint &inPoint) = 0;
+ virtual void LineTo(long inX, long inY) = 0;
+
+ virtual void PushPen(const QColor &inColor,
+ int inWidth = 1) = 0; //, UINT inStyle = PS_SOLID );
+ virtual void PopPen() = 0;
+ virtual void BitBltFrom(const QRect &inRect, CRenderer *inRenderer, long inXSrc, long inYSrc) = 0;
+
+ virtual void DrawText(float inX, float inY, const QString &inText) = 0;
+ virtual void DrawText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingRect, const QColor &inColor = Qt::black) = 0;
+ virtual void DrawBoldText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingRect, const QColor &inColor = Qt::black) = 0;
+
+ virtual QSize GetTextSize(const QString &inText) = 0;
+
+ virtual void DrawBitmap(const QPoint &inPos, const QPixmap &inImage) = 0;
+ virtual void Draw3dRect(const QRect &inRect, const QColor &inTopLeftColor,
+ const QColor &inBottomRightColor) = 0;
+ virtual void DrawRectOutline(const QRect &inRect, const QColor &inTop, const QColor &inRight,
+ const QColor &inBottom, const QColor &inLeft);
+ virtual void DrawGradientBitmap(const QRect &inRct, const QColor &inBeginColor, bool inInverted,
+ double inScalingFactor = .99) = 0;
+
+ virtual void DrawGradient(const QRect &inRect, const QColor &inBeginColor,
+ const QColor &inEndColor) = 0;
+
+ virtual void PushTranslation(const QPoint &inTranslation) = 0;
+ virtual void PopTranslation() = 0;
+ virtual QPoint GetTranslation() = 0;
+
+ virtual QRect GetClippingRect() = 0;
+ virtual void PushClippingRect(const QRect &inRect) = 0;
+ virtual void PushAbsoluteClippingRect(const QRect &inRect) = 0;
+ virtual void PopClippingRect() = 0;
+
+ virtual void FillHashed(const QRect &inRect, const QColor &inForeGroundColor) = 0;
+
+ virtual QPixmap pixmap() const = 0;
+
+protected:
+ QPoint m_Translation;
+ TTranslationList m_Translations;
+};
+#endif // INCLUDED_RENDERER_H
diff --git a/src/Authoring/Qt3DStudio/Controls/WidgetControl.cpp b/src/Authoring/Qt3DStudio/Controls/WidgetControl.cpp
new file mode 100644
index 00000000..207abed0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/WidgetControl.cpp
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** 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 "WidgetControl.h"
+
+#include "Control.h"
+#include "DropSource.h"
+#include "IDragable.h"
+#include "OffscreenRenderer.h"
+#include "Pt.h"
+#include "Rct.h"
+#include "Qt3DSFile.h"
+
+#include <QtGui/qdrag.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qpainter.h>
+#include <QtWidgets/qmenu.h>
+
+WidgetControl::WidgetControl(CControl *control, QWidget *parent)
+ : QWidget(parent)
+ , m_control(control)
+{
+ Q_ASSERT(control);
+ setControlSize(sizeHint());
+}
+
+WidgetControl::~WidgetControl()
+{
+}
+
+bool WidgetControl::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride) {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (ke->key() == Qt::Key_C && ke->modifiers() == Qt::ControlModifier)
+ m_control->OnKeyDown(ke->key(), ke->modifiers());
+ }
+ return QWidget::event(event);
+}
+
+void WidgetControl::showEvent(QShowEvent *event)
+{
+ QWidget::showEvent(event);
+}
+
+void WidgetControl::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ const auto boundRect = QRect(QPoint(0,0), size());
+ CWinRenderer renderer(&painter, boundRect);
+ CRct rect(event->rect());
+ m_control->OnDraw(&renderer, rect, true);
+
+ QWidget::paintEvent(event);
+}
+
+void WidgetControl::resizeEvent(QResizeEvent *event)
+{
+ setControlSize(event->size());
+ QWidget::resizeEvent(event);
+}
+
+void WidgetControl::keyPressEvent(QKeyEvent *event)
+{
+ QWidget::keyPressEvent(event);
+ m_control->OnKeyDown(event->key(), event->modifiers());
+ m_control->OnChar(event->text(), event->modifiers());
+}
+
+void WidgetControl::keyReleaseEvent(QKeyEvent *event)
+{
+ m_control->OnKeyUp(event->key(), event->modifiers());
+ QWidget::keyReleaseEvent(event);
+}
+
+void WidgetControl::mousePressEvent(QMouseEvent *event)
+{
+ const auto pos = CPt(event->pos());
+ if (m_isLeftMouseDown)
+ m_control->OnMouseUp(pos, event->modifiers());
+
+ m_isLeftMouseDown = (event->button() == Qt::LeftButton);
+ if (m_isLeftMouseDown)
+ m_control->OnMouseDown(pos, event->modifiers());
+ else
+ m_control->OnMouseRDown(pos, event->modifiers());
+
+ setFocus();
+ QWidget::mousePressEvent(event);
+}
+
+void WidgetControl::mouseReleaseEvent(QMouseEvent *event)
+{
+ const auto pos = CPt(event->pos());
+ if (event->button() == Qt::LeftButton) {
+ m_isLeftMouseDown = false;
+ m_control->OnMouseUp(pos, event->modifiers());
+ } else {
+ m_control->OnMouseRUp(pos, event->modifiers());
+ }
+
+ QWidget::mouseReleaseEvent(event);
+}
+
+void WidgetControl::mouseMoveEvent(QMouseEvent *event)
+{
+ m_control->OnMouseMove(event->pos(), event->modifiers());
+ QWidget::mouseMoveEvent(event);
+}
+
+void WidgetControl::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ // call QWidget handler first to not deliver OnMouseDown after OnMouseDoubleClick
+ QWidget::mouseDoubleClickEvent(event);
+ m_control->OnMouseDoubleClick(event->pos(), event->modifiers());
+}
+
+void WidgetControl::wheelEvent(QWheelEvent *event)
+{
+ m_control->OnMouseWheel(event->pos(), event->angleDelta().y(), event->modifiers());
+ QWidget::wheelEvent(event);
+}
+
+void WidgetControl::enterEvent(QEvent *event)
+{
+ setMouseTracking(true);
+ m_control->OnMouseHover(mapFromGlobal(QCursor::pos()), {});
+ QWidget::enterEvent(event);
+}
+
+void WidgetControl::leaveEvent(QEvent *event)
+{
+ setMouseTracking(false);
+ m_control->OnMouseOut(mapFromGlobal(QCursor::pos()), {});
+ QWidget::leaveEvent(event);
+}
+
+void WidgetControl::focusInEvent(QFocusEvent *event)
+{
+ m_control->OnGainFocus();
+ QWidget::focusInEvent(event);
+}
+
+void WidgetControl::focusOutEvent(QFocusEvent *event)
+{
+ if (!m_isContextMenuShown)
+ m_control->OnLoseFocus();
+ QWidget::focusOutEvent(event);
+}
+
+QSize WidgetControl::sizeHint() const
+{
+ const auto preferredSize = m_control->GetPreferredSize();
+ return QSize(preferredSize.x, preferredSize.y);
+}
+
+/*
+ * CPaletteManager::GetTimelineControl() needs a way of accessing
+ * the CControl inside the widget
+ */
+CControl *WidgetControl::getControl() const
+{
+ return m_control;
+}
+
+void WidgetControl::setControlSize(const QSize &size)
+{
+ m_control->SetSize(size.width(), size.height());
+}
+
+bool WidgetControl::OnDragWithin(CDropSource &inSource)
+{
+ bool theReturn = false;
+ CPt thePoint = inSource.GetCurrentPoint();
+ Qt::KeyboardModifiers theFlags = inSource.GetCurrentFlags();
+ CDropTarget *theDropTarget = m_control->FindDropCandidate(
+ thePoint, theFlags, static_cast<EStudioObjectType>(inSource.GetObjectType()),
+ static_cast<Q3DStudio::DocumentEditorFileType::Enum>(inSource.getFileType()));
+
+ if (theDropTarget) {
+ theReturn = theDropTarget->Accept(inSource);
+ delete theDropTarget;
+ }
+ return theReturn;
+}
+
+bool WidgetControl::OnDragReceive(CDropSource &inSource)
+{
+ bool theReturn = false;
+ CPt thePoint = inSource.GetCurrentPoint();
+ Qt::KeyboardModifiers theFlags = inSource.GetCurrentFlags();
+
+ CDropTarget *theDropTarget = m_control->FindDropCandidate(
+ thePoint, theFlags, static_cast<EStudioObjectType>(inSource.GetObjectType()),
+ static_cast<Q3DStudio::DocumentEditorFileType::Enum>(inSource.getFileType()));
+
+ if (theDropTarget) {
+ theReturn = theDropTarget->Drop(inSource);
+ delete theDropTarget;
+ }
+ return theReturn;
+}
+
+void WidgetControl::OnDragLeave()
+{
+ m_control->OnMouseMove(CPt(-1, -1), 0);
+}
+
+void WidgetControl::OnReflectMouse(CPt &inPoint, Qt::KeyboardModifiers inFlags)
+{
+ // Notify the control that the mouse moved
+ m_control->OnMouseMove(inPoint, inFlags /*CHotKeys::GetCurrentKeyModifiers( )*/);
+
+ // If the control invalidated because of a mouse event then we want to do an immediate redraw.
+ // this ensures consistent visible feedback.
+ if (m_control->IsChildInvalidated())
+ repaint();
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/WidgetControl.h b/src/Authoring/Qt3DStudio/Controls/WidgetControl.h
new file mode 100644
index 00000000..277a2e17
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/WidgetControl.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** 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 WIDGETCONTROL_H
+#define WIDGETCONTROL_H
+
+#include "Control.h"
+#include "DropContainer.h"
+
+#include <QtWidgets/qwidget.h>
+
+class CRenderer;
+class WidgetControl;
+
+class WidgetControl : public QWidget, public CWinDropContainer
+{
+ Q_OBJECT
+public:
+ explicit WidgetControl(CControl *control, QWidget *parent = nullptr);
+ virtual ~WidgetControl();
+ void setContextMenuShown(bool shown) { m_isContextMenuShown = shown; }
+
+protected:
+ bool event(QEvent *event) override;
+ void showEvent(QShowEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+ void enterEvent(QEvent *event) override;
+ void leaveEvent(QEvent *event) override;
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+
+public:
+ QSize sizeHint() const override;
+ CControl *getControl() const;
+
+protected:
+ bool OnDragWithin(CDropSource &inSource) override;
+ bool OnDragReceive(CDropSource &inSource) override;
+ void OnDragLeave() override;
+ void OnReflectMouse(CPt &inPoint, Qt::KeyboardModifiers inFlags) override;
+
+private:
+ void setControlSize(const QSize &size);
+
+ CControl *m_control;
+ bool m_isLeftMouseDown = false;
+ bool m_isContextMenuShown = false;
+};
+
+#endif // WIDGETCONTROL_H
diff --git a/src/Authoring/Qt3DStudio/Controls/WinRenderer.cpp b/src/Authoring/Qt3DStudio/Controls/WinRenderer.cpp
new file mode 100644
index 00000000..1fd98734
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/WinRenderer.cpp
@@ -0,0 +1,518 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+
+#include "WinRenderer.h"
+#include "MasterP.h"
+#include "CoreUtils.h"
+
+//=============================================================================
+/**
+ * Leaves the Renderer in an invalid state, only for subclasses.
+ * In order for this renderer to be valid, you must do two things: (1) you must
+ * create a CDC and set m_DC equal to it, and (2) you must call PushClippingRect.
+ */
+CWinRenderer::CWinRenderer()
+ : m_painter(nullptr)
+{
+}
+
+//=============================================================================
+/**
+ * Leaves the Renderer in an invalid state, only for subclasses.
+ * PushClippingRect must be called in order for this to become valid.
+ */
+CWinRenderer::CWinRenderer(QPainter *inDC)
+{
+ m_painter = inDC;
+}
+
+CWinRenderer::CWinRenderer(QPainter *inDC, const QRect &inClippingRect)
+{
+ m_painter = inDC;
+
+ PushClippingRect(inClippingRect);
+}
+
+CWinRenderer::~CWinRenderer()
+{
+ m_Pens.clear();
+}
+
+//=============================================================================
+/**
+ * Draws a rectangle and fills it with inColor.
+ * The rectangle has no border and is solid.
+ * The coordinates are converted to global space using the current translation.
+ * @param inCoordinates the coordinates of the rectangle.
+ * @param inColor the color of the rectangle to draw.
+ */
+void CWinRenderer::FillSolidRect(const QRect &inCoordinates, const QColor &inColor)
+{
+ QRect theRect(inCoordinates.topLeft() + m_Translation,
+ inCoordinates.size());
+
+ m_painter->fillRect(theRect, inColor);
+}
+
+//=============================================================================
+/**
+ * Draws a rounded rectangle (which actually is just a line) and fills it with inColor.
+ * The coordinates are converted to global space using the current translation.
+ * @param inCoordinates the coordinates of the rectangle.
+ * @param inColor the color of the rectangle to draw.
+ */
+void CWinRenderer::FillRoundedRect(const QRect &inCoordinates, const QColor &inColor,
+ bool vertical)
+{
+ QPen previousPen = m_painter->pen();
+ QRect theRect(inCoordinates.topLeft() + m_Translation, inCoordinates.size());
+ QPointF startPoint = (theRect.bottomLeft() + theRect.topLeft()) / 2.0;
+ QPointF endPoint = (theRect.bottomRight() + theRect.topRight()) / 2.0;
+ qreal lineWidth = theRect.bottom() - theRect.top() + 1.0;
+
+ if (vertical) {
+ lineWidth = theRect.right() - theRect.left() + 1.0;
+ startPoint = (theRect.topRight() + theRect.topLeft()) / 2.0;
+ endPoint = (theRect.bottomRight() + theRect.bottomLeft()) / 2.0;
+ }
+
+ m_painter->setPen(QPen(inColor, lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
+ m_painter->drawLine(startPoint, endPoint);
+
+ // Restore previous pen
+ m_painter->setPen(previousPen);
+}
+
+//=============================================================================
+/**
+ * Move the pen to the position.
+ * This will put the pen at inPoint but will not draw a line there from the
+ * current location.
+ * @param inPoint the location to move to, in local coordinates.
+ */
+void CWinRenderer::MoveTo(const QPoint &inPoint)
+{
+ m_currentPos = inPoint + m_Translation;
+}
+
+//=============================================================================
+/**
+ * Move the pen to the position.
+ * This will put the pen to the point but will not draw a line there from the
+ * current location.
+ * @param inX the X location to move to, in local coordinates.
+ * @param inY the Y location to move to, in local coordinates.
+ */
+void CWinRenderer::MoveTo(long inX, long inY)
+{
+ MoveTo(QPoint(inX, inY));
+}
+
+//=============================================================================
+/**
+ * Draw a line from the current pen location to inPoint.
+ * This will draw a line to inPoint and move the current location of the pen
+ * to inPoint.
+ * @param inPoint the location to draw to, in local coordinates.
+ */
+void CWinRenderer::LineTo(const QPoint &inPoint)
+{
+ const QPoint point = inPoint + m_Translation;
+ m_painter->drawLine(m_currentPos, point);
+ m_painter->drawLine(point, QPoint(point.x(), point.y() + 1));
+ m_currentPos = point;
+}
+
+//=============================================================================
+/**
+ * Draw a line from the current pen location to the point.
+ * This will draw a line to the point and move the current location of the pen
+ * to inPoint.
+ * @param inX the X coordinate of the point to draw to, in local coordinates.
+ * @param inY the Y coordinate of the point to draw to, in local coordinates.
+ */
+void CWinRenderer::LineTo(long inX, long inY)
+{
+ LineTo(QPoint(inX, inY));
+}
+
+//=============================================================================
+/**
+ * Change the active pen color.
+ * This acts as a stack so that it does not interfere with previous components.
+ * Once the color is done being used PopPen should be called.
+ * @param inColor the color to make the current pen.
+ * @param inWidth the pen width, in pixels.
+ */
+void CWinRenderer::PushPen(const QColor &inColor, int inWidth)
+{
+ QPen thePen = GetPen(inColor, inWidth, Qt::SolidLine);
+
+ QPen theCurrentPen = m_painter->pen();
+ m_painter->setPen(thePen);
+ m_PenList.push_back(theCurrentPen);
+}
+
+void CWinRenderer::PopPen()
+{
+ QPen thePen = m_PenList.back();
+ m_PenList.pop_back();
+ m_painter->setPen(thePen);
+}
+
+//=============================================================================
+/**
+ * Copy the bits from another renderer into this one.
+ * This will copy the rect from inRenderer into this renderer.
+ * inRect will be offset by this renderer's current translation. inXSrc and inYSrc
+ * will be offset by inRenderer's current translation.
+ * @param inRect the position and size of the area to be copied into this renderer.
+ * @param inPixmap the renderer to copy from.
+ * @param xSrc the x location of the location to copy from in inRenderer.
+ * @param ySrc the y location of the location to copy from in inRenderer.
+ */
+void CWinRenderer::BitBltFrom(const QRect &inRect, CRenderer *inRenderer, long inXSrc, long inYSrc)
+{
+ const auto inTranslation = inRenderer->GetTranslation();
+ inXSrc += inTranslation.x();
+ inYSrc += inTranslation.y();
+
+ auto srcRect = inRect;
+ auto destRect = inRect;
+ destRect.translate(m_Translation);
+ srcRect.moveTo(inXSrc, inYSrc);
+ m_painter->save();
+ m_painter->setCompositionMode(QPainter::CompositionMode_DestinationOver);
+ m_painter->drawPixmap(destRect, inRenderer->pixmap(), srcRect);
+ m_painter->restore();
+}
+
+//=============================================================================
+/**
+ * Draws text out without clipping it.
+ * @param inPoint the point at which to draw the text. (upper left corner)
+ * @param inText the text to draw.
+ */
+void CWinRenderer::DrawText(float inX, float inY, const QString &inText)
+{
+ inX += m_Translation.x();
+ inY += m_Translation.y();
+ m_painter->drawText(QPointF(inX, inY), inText);
+}
+
+//=============================================================================
+/**
+ * Draws text out to a clipped rectangle.
+ * If any text occurs outside the bounding box then it will be clipped.
+ * @param inPoint the point at which to draw the text. (upper left corner)
+ * @param inText the text to draw.
+ * @param inBoundingBox the bounding box used to clip the text.
+ * @param inColor color to draw the text in
+ */
+void CWinRenderer::DrawText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingBox, const QColor &inColor)
+{
+ inX += m_Translation.x();
+ inY += m_Translation.y();
+
+ QRectF rect(inBoundingBox);
+ rect.translate(inX, inY);
+ m_painter->save();
+ QPen pen(inColor);
+ m_painter->setPen(pen);
+ m_painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, inText);
+ m_painter->restore();
+}
+
+//=============================================================================
+/**
+ * Draws BOLD text out to a clipped rectangle.
+ * If any text occurs outside the bounding box then it will be clipped.
+ * @param inPoint the point at which to draw the text. (upper left corner)
+ * @param inText the text to draw.
+ * @param inBoundingBox the bounding box used to clip the text.
+ * @param inColor color to draw the text in
+ */
+void CWinRenderer::DrawBoldText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingBox, const QColor &inColor)
+{
+ inX += m_Translation.x();
+ inY += m_Translation.y();
+
+ QRectF rect(inBoundingBox);
+ rect.translate(inX, inY);
+ m_painter->save();
+ QPen pen(inColor);
+ m_painter->setPen(pen);
+ QFont font = m_painter->font();
+ font.setBold(true);
+ m_painter->setFont(font);
+ m_painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, inText);
+ m_painter->restore();
+}
+
+//=============================================================================
+/**
+ * Gets the dimensions of the text string as it would be written out to the
+ * screen.
+ * @param inText the text to check the length on.
+ * @return the length of the text in pixels.
+ */
+QSize CWinRenderer::GetTextSize(const QString &inText)
+{
+ QFontMetrics fm = m_painter->fontMetrics();
+ return fm.size(Qt::TextSingleLine, inText);
+}
+
+//=============================================================================
+/**
+ * Draws a a three-dimensional rectangle with the top and left sides in the
+ * color specified by inTopLeftColor and the bottom and right sides in the color
+ * specified by inBottomRightColor.
+ *
+ * @param inRect The rectangle to draw
+ * @param inTopLeftColor Color for the top and left sides of the rect
+ * @param inBottomRightColor Color for the bottom and right sides of the rect
+ */
+void CWinRenderer::Draw3dRect(const QRect &inRect, const QColor &inTopLeftColor,
+ const QColor &inBottomRightColor)
+{
+ auto rect = inRect;
+ rect.translate(m_Translation);
+ m_painter->drawRect(rect);
+ m_painter->save();
+ m_painter->setPen(inTopLeftColor);
+ m_painter->drawLine(rect.topLeft(), rect.bottomLeft());
+ m_painter->drawLine(rect.topLeft(), rect.topRight());
+ m_painter->setPen(inBottomRightColor);
+ m_painter->drawLine(rect.bottomLeft(), rect.bottomRight());
+ m_painter->drawLine(rect.bottomRight(), rect.topRight());
+ m_painter->restore();
+}
+
+//==============================================================================
+/**
+ * DrawBitmap
+ *
+ * Draw a bitmap given a device context, position and HBITMAP handle.
+ *
+ * @param inDC Device context for drawing
+ * @param inPos CPoint position for drawing
+ * @param inBitmap Handle of the bitmap to draw
+ */
+//==============================================================================
+void CWinRenderer::DrawBitmap(const QPoint &inPos, const QPixmap &inImage)
+{
+ m_painter->save();
+ m_painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
+ m_painter->drawPixmap(inPos + m_Translation, inImage);
+ m_painter->restore();;
+}
+
+void CWinRenderer::PushTranslation(const QPoint &inTranslation)
+{
+ m_Translation += inTranslation;
+
+ m_Translations.push_back(inTranslation);
+}
+
+void CWinRenderer::PopTranslation()
+{
+ QPoint thePreviousTranslation = m_Translations.back();
+ m_Translation -= thePreviousTranslation;
+
+ m_Translations.pop_back();
+}
+
+QPoint CWinRenderer::GetTranslation()
+{
+ return m_Translation;
+}
+
+QPainter* CWinRenderer::GetPainter()
+{
+ return m_painter;
+}
+
+//==============================================================================
+/**
+ * Get the current clipping rect.
+ * The clipping rect is the boundary of pixels that will be drawn to the DC.
+ * This can be used to not draw non-visible objects.
+ * @return the clipping rect in local coordinates.
+ */
+QRect CWinRenderer::GetClippingRect()
+{
+ QRect theClippingRect = m_painter->clipBoundingRect().toRect();
+ theClippingRect.translate(-m_Translation);
+
+ return theClippingRect;
+}
+
+//==============================================================================
+/**
+ * Push a clipping rect onto the stack of clipping rects.
+ * This will cause any drawing outside of the clipping rect to be ignored. The
+ * Control class also uses this to not draw objects outside of this rect.
+ * @param inClippingRect the new clipping rect, in local coordinates.
+ */
+void CWinRenderer::PushClippingRect(const QRect &inClippingRect)
+{
+ QRect clippingRect(inClippingRect);
+ clippingRect.translate(m_Translation);
+
+ const QRegion currentRegion = m_painter->clipRegion();
+ m_painter->setClipRect(clippingRect);
+
+ m_ClippingRegions.push_back(currentRegion);
+}
+
+//==============================================================================
+/**
+ * Pop the current clipping rect.
+ * This will change the clipping rect to use the one previous to the current
+ * one.
+ */
+void CWinRenderer::PopClippingRect()
+{
+ if (m_ClippingRegions.size() > 1) {
+ QRegion thePreviousRegion = m_ClippingRegions.back();
+ m_painter->setClipRegion(thePreviousRegion);
+ m_ClippingRegions.pop_back();
+ }
+}
+
+//==============================================================================
+/**
+ * DrawGradiantBitmap
+ *
+ * Draws a gradiant based on the begin color
+ *
+ * @param inRct Rct tof the control
+ * @param inBeginColor color to start with
+ * @param inInverted true if this is inverted
+ */
+void CWinRenderer::DrawGradientBitmap(const QRect &inRct, const QColor &inBeginColor, bool inInverted,
+ double inScalingFactor)
+{
+ QRect rect(inRct);
+ QRect theClippingRect = GetClippingRect();
+ rect &= theClippingRect;
+ rect.translate(m_Translation);
+
+ long theHeight = rect.height();
+ long theWidth = rect.width();
+
+ QImage theCompatibleBitmap(1, theHeight, QImage::Format_RGB32);
+
+ int theR = inBeginColor.red();
+ int theG = inBeginColor.green();
+ int theB = inBeginColor.blue();
+
+ double theExtraRModifier = inScalingFactor * (1.0 - (theR / 255.0));
+ double theExtraGModifier = inScalingFactor * (1.0 - (theG / 255.0));
+ double theExtraBModifier = inScalingFactor * (1.0 - (theB / 255.0));
+
+ for (long thePixel = 0; thePixel < theHeight; ++thePixel) {
+ double theNormPixel = (double)thePixel / (theHeight * 4.8);
+ double theCos = 1.0 / (theNormPixel * theNormPixel + 1.0);
+ double theRValue = (double)theR * (theCos + theExtraRModifier);
+ double theGValue = (double)theG * (theCos + theExtraGModifier);
+ double theBValue = (double)theB * (theCos + theExtraBModifier);
+
+ QColor theTempColor(::dtoi(theRValue), ::dtoi(theGValue), ::dtoi(theBValue));
+ if (inInverted) {
+ theCompatibleBitmap.setPixelColor(0, qMax(0l, theHeight - thePixel - 2), theTempColor);
+ } else {
+ theCompatibleBitmap.setPixelColor(0, thePixel, theTempColor);
+ }
+ theExtraRModifier *= 0.3;
+ theExtraGModifier *= 0.3;
+ theExtraBModifier *= 0.3;
+ }
+
+ m_painter->save();
+ m_painter->drawImage(QRect(rect.x(), rect.y(), theWidth, theHeight), theCompatibleBitmap);
+ m_painter->restore();
+}
+
+//=============================================================================
+/**
+ * Draw a gradient over inRect.
+ * This will blend horizontally across the rect from inBeginColor into inEndColor.
+ * This does linear interpolation.
+ * @param inRect the rect to draw on.
+ * @param inBeginColor the start (left most) color.
+ * @param inEndColor the final (right most) color.
+ */
+void CWinRenderer::DrawGradient(const QRect &inRect, const QColor &inBeginColor, const QColor &inEndColor)
+{
+ const QRect rect = inRect.translated(m_Translation);
+ QLinearGradient gradient(rect.topLeft(), rect.topRight());
+ gradient.setColorAt(0, inBeginColor);
+ gradient.setColorAt(1, inEndColor);
+
+ QBrush brush(gradient);
+ m_painter->fillRect(rect, brush);
+}
+
+void CWinRenderer::FillHashed(const QRect &inRect, const QColor &inForegroundColor)
+{
+ m_painter->save();
+
+ QBrush theBrush(inForegroundColor, Qt::BDiagPattern);
+ QPen pen(inForegroundColor);
+ m_painter->setPen(pen);
+ m_painter->setBrush(theBrush);
+ m_painter->drawRect(inRect.translated(m_Translation));
+
+ m_painter->restore();
+}
+
+QPen CWinRenderer::GetPen(const QColor &inColor, int inWidth, Qt::PenStyle inStyle)
+{
+ QPen thePen;
+ SPenInfo theInfo = { inColor, inWidth, inStyle };
+ TPenMap::iterator thePos = m_Pens.find(theInfo);
+ if (thePos != m_Pens.end()) {
+ thePen = thePos->second;
+ } else {
+ thePen.setColor(inColor);
+ thePen.setWidth(inWidth);
+ m_Pens[theInfo] = thePen;
+ }
+ return thePen;
+}
+
+QPixmap CWinRenderer::pixmap() const
+{
+ return {};
+}
diff --git a/src/Authoring/Qt3DStudio/Controls/WinRenderer.h b/src/Authoring/Qt3DStudio/Controls/WinRenderer.h
new file mode 100644
index 00000000..3c56145b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Controls/WinRenderer.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** 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_WIN_RENDERER_H
+#define INCLUDED_WIN_RENDERER_H 1
+
+#pragma once
+
+#include <vector>
+#include <map>
+
+#include "Pt.h"
+#include "Rct.h"
+#include "Renderer.h"
+
+#include <QPainter>
+#include <QRegion>
+
+QT_BEGIN_NAMESPACE
+class QPixmap;
+QT_END_NAMESPACE
+
+class CWinRenderer : public CRenderer
+{
+ typedef std::vector<QPen> TPenList;
+ typedef std::vector<QRegion> TClippingRegions;
+
+protected:
+ CWinRenderer(); ///< Leaves CRenderer in an invalid state, only for subclasses
+ CWinRenderer(QPainter *inDC); ///< Leaves CRenderer in an invalid state, only for subclasses
+
+public:
+ CWinRenderer(QPainter *inDC, const QRect &inClippingRect);
+ virtual ~CWinRenderer();
+
+ void FillSolidRect(const QRect &inCoordinates, const QColor &inColor) override;
+ void FillRoundedRect(const QRect &inCoordinates, const QColor &inColor,
+ bool vertical) override;
+
+ void MoveTo(const QPoint &inPoint) override;
+ void MoveTo(long inX, long inY) override;
+ void LineTo(const QPoint &inPoint) override;
+ void LineTo(long inX, long inY) override;
+
+ void PushPen(const QColor &inColor, int inWidth = 1) override;
+ void PopPen() override;
+ void BitBltFrom(const QRect &inRect, CRenderer *inRenderer, long inXSrc, long inYSrc) override;
+
+ void DrawText(float inX, float inY, const QString &inText) override;
+ void DrawText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingRect, const QColor &inColor = Qt::black) override;
+ void DrawBoldText(float inX, float inY, const QString &inText,
+ const QRect &inBoundingRect, const QColor &inColor = Qt::black) override;
+
+ QSize GetTextSize(const QString &inText) override;
+
+ void DrawBitmap(const QPoint &inPos, const QPixmap &inImage) override;
+ void Draw3dRect(const QRect &inRect, const QColor &inTopLeftColor,
+ const QColor &inBottomRightColor) override;
+ void DrawGradientBitmap(const QRect &inRct, const QColor &inBeginColor, bool inInverted,
+ double inScalingFactor = .99) override;
+
+ void DrawGradient(const QRect &inRect, const QColor &inBeginColor, const QColor &inEndColor) override;
+
+ void PushTranslation(const QPoint &inTranslation) override;
+ void PopTranslation() override;
+ QPoint GetTranslation() override;
+
+ QRect GetClippingRect() override;
+ void PushClippingRect(const QRect &inRect) override;
+ void PopClippingRect() override;
+ void PushAbsoluteClippingRect(const QRect &) override {}
+ void FillHashed(const QRect &inRect, const QColor &inForeGroundColor) override;
+ QPainter *GetPainter() override;
+ QPen GetPen(const QColor &inColor, int inWidth, Qt::PenStyle inStyle);
+
+ QPixmap pixmap() const override;
+
+protected:
+ TPenList m_PenList;
+ TClippingRegions m_ClippingRegions;
+ QPainter *m_painter;
+ QPoint m_currentPos;
+
+protected:
+ struct SPenInfo
+ {
+ QColor Color;
+ long Width;
+ Qt::PenStyle Style;
+ };
+
+ class CPenInfoLessThan : public std::binary_function<const SPenInfo &, const SPenInfo &, bool>
+ {
+ public:
+ inline bool operator()(const SPenInfo &inValL, const SPenInfo &inValR) const
+ {
+ return memcmp(&inValL, &inValR, sizeof(SPenInfo)) < 0;
+ }
+ };
+
+ typedef std::map<SPenInfo, QPen, CPenInfoLessThan> TPenMap;
+ TPenMap m_Pens;
+};
+#endif // INCLUDED_WIN_RENDERER_H
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.cpp
new file mode 100644
index 00000000..3ad659d2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.cpp
@@ -0,0 +1,261 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "BasicObjectDropSource.h"
+#include "Doc.h"
+#include "DropTarget.h"
+
+#include "Dialogs.h"
+#include "Dispatch.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMDataCore.h"
+#include "IDocumentEditor.h"
+#include "ImportUtils.h"
+#include "BasicObjectsModel.h"
+#include "IDragable.h"
+#include "IDocSceneGraph.h"
+#include "Qt3DSTextRenderer.h"
+#include "HotKeys.h"
+#include "StudioUtils.h"
+
+#include <QtCore/qdir.h>
+
+//===============================================================================
+/**
+ *
+ */
+CBasicObjectDropSource::CBasicObjectDropSource(long inFlavor, IDragable *inDragable)
+ : CDropSource(inFlavor, 0)
+ , m_IsIndependent(false)
+{
+ auto item = dynamic_cast<BasicObjectItem *>(inDragable);
+ if (item) {
+ m_ObjectType = item->objectType();
+ m_PrimitiveType = item->primitiveType();
+ }
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CBasicObjectDropSource::ValidateTarget(CDropTarget *inTarget)
+{
+ using namespace Q3DStudio;
+
+ EStudioObjectType targetType = (EStudioObjectType)inTarget->GetObjectType();
+ bool theValidTarget = false;
+
+ // the only thing we want to do from here is check the type.
+ theValidTarget =
+ CStudioObjectTypes::AcceptableParent((EStudioObjectType)m_ObjectType, targetType);
+
+ if (!theValidTarget) {
+ SetHasValidTarget(theValidTarget);
+ return theValidTarget;
+ } else {
+ if (CHotKeys::IsKeyDown(Qt::AltModifier) && targetType != OBJTYPE_SCENE
+ && targetType != OBJTYPE_COMPONENT) {
+ qt3dsdm::Qt3DSDMInstanceHandle theTarget = inTarget->GetInstance();
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ IDocumentReader &theReader(theDoc->GetDocumentReader());
+ qt3dsdm::Qt3DSDMSlideHandle toSlide = theReader.GetAssociatedSlide(theTarget);
+ ;
+
+ if (!theReader.IsMasterSlide(toSlide))
+ theValidTarget = false;
+ }
+
+ SetHasValidTarget(theValidTarget);
+ return theValidTarget;
+ }
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CBasicObjectDropSource::CanMove()
+{
+ return true;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CBasicObjectDropSource::CanCopy()
+{
+ return true;
+}
+
+CCmd *CBasicObjectDropSource::GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide)
+{
+ using namespace Q3DStudio;
+ using qt3dsdm::ComposerObjectTypes;
+ using namespace std;
+
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ CPt thePoint;
+ // if ( CHotKeys::IsKeyDown( CHotKeys::KEY_MENU ) )
+ // thePoint = GetCurrentPoint();
+
+ long theStartTime = -1;
+ if (CHotKeys::IsKeyDown(Qt::ControlModifier))
+ theStartTime = theDoc->GetCurrentViewTime();
+
+ DocumentEditorInsertType::Enum theInsertType(ImportUtils::GetInsertTypeForDropType(inDestType));
+ ComposerObjectTypes::Enum theComposerType;
+ switch (m_ObjectType) {
+ case OBJTYPE_SCENE:
+ theComposerType = ComposerObjectTypes::Scene;
+ break;
+ case OBJTYPE_LAYER:
+ theComposerType = ComposerObjectTypes::Layer;
+ break;
+ case OBJTYPE_BEHAVIOR:
+ theComposerType = ComposerObjectTypes::Behavior;
+ break;
+ case OBJTYPE_MATERIAL:
+ theComposerType = ComposerObjectTypes::Material;
+ break;
+ case OBJTYPE_CAMERA:
+ theComposerType = ComposerObjectTypes::Camera;
+ break;
+ case OBJTYPE_LIGHT:
+ theComposerType = ComposerObjectTypes::Light;
+ break;
+ case OBJTYPE_MODEL:
+ theComposerType = ComposerObjectTypes::Model;
+ break;
+ case OBJTYPE_GROUP:
+ theComposerType = ComposerObjectTypes::Group;
+ break;
+ case OBJTYPE_IMAGE:
+ theComposerType = ComposerObjectTypes::Image;
+ break;
+ case OBJTYPE_TEXT:
+ theComposerType = ComposerObjectTypes::Text;
+ break;
+ case OBJTYPE_COMPONENT:
+ theComposerType = ComposerObjectTypes::Component;
+ break;
+ case OBJTYPE_ALIAS:
+ theComposerType = ComposerObjectTypes::Alias;
+ break;
+ case OBJTYPE_PATH:
+ theComposerType = ComposerObjectTypes::Path;
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ theComposerType = ComposerObjectTypes::Unknown;
+ break;
+ }
+ if (theComposerType != ComposerObjectTypes::Unknown) {
+ if (theComposerType == ComposerObjectTypes::Text) {
+ // For Text, we need to check if user already has font file inside fonts folder
+ CFilePath theFontFile;
+ CFilePath theFontDir = CFilePath::CombineBaseAndRelative(
+ CFilePath(g_StudioApp.GetCore()->getProjectFile().getProjectPath()),
+ CFilePath(L"fonts"));
+ if (!theFontDir.Exists()) {
+ // Create font dir if necessary
+ theFontDir.CreateDir(true);
+ } else {
+ // Recursively find the first font file in font dir
+ vector<CFilePath> theFiles;
+ theFontDir.RecursivelyFindFilesOfType(nullptr, theFiles, false);
+ for (size_t i = 0; i < theFiles.size(); ++i) {
+ if (CDialogs::IsFontFileExtension(theFiles[i].GetExtension())) {
+ // Reuse the font in fonts subdirectory
+ theFontFile = theFiles[i];
+ break;
+ }
+ }
+ }
+
+ if (theFontFile.filePath().isEmpty()) {
+ // If user doesn't have any font file, copy the default font file from Studio's res
+ // folder
+
+ CFilePath theResFontFile;
+
+ QDir theResFontDir(StudioUtils::resourcePath() + QStringLiteral("/Font"));
+ Q_FOREACH (QFileInfo fontFile, theResFontDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)) {
+ CString ext = CString::fromQString(fontFile.suffix());
+ if (CDialogs::IsFontFileExtension(ext)) {
+ theResFontFile = CString::fromQString(fontFile.absoluteFilePath());
+ theFontFile = CFilePath::CombineBaseAndRelative(theFontDir, CString::fromQString(fontFile.fileName()));
+ break;
+ }
+ }
+
+ if (theResFontFile.filePath().isEmpty()) {
+ QT3DS_ASSERT(false);
+ std::shared_ptr<IImportFailedHandler> theHandler(
+ theDoc->GetImportFailedHandler());
+ if (theHandler)
+ theHandler->DisplayImportFailed(
+ theResFontDir.absolutePath(),
+ QObject::tr("Default Font File Doesn't Exist in the Directory"),
+ false);
+ return nullptr;
+ }
+
+ // Copy the file to project's fonts folder
+ SFileTools::Copy(theFontFile,
+ Q3DStudio::FileOpenFlags(Q3DStudio::FileOpenFlagValues::Create),
+ theResFontFile);
+ // Force the text renderer to refresh
+ if (theDoc->GetSceneGraph() && theDoc->GetSceneGraph()->GetTextRenderer())
+ theDoc->GetSceneGraph()->GetTextRenderer()->ReloadFonts();
+ }
+
+ // Lastly, we use the font file to create the Text object. This is similar to drag-drop
+ // the font file from Project Palette to Scene.
+ SCOPED_DOCUMENT_EDITOR(*theDoc, QObject::tr("Add Text"))
+ ->ImportFile(DocumentEditorFileType::Font, theFontFile, inTarget, inSlide,
+ CDialogs::GetImportFileExtension(),
+ Q3DStudio::ImportUtils::GetInsertTypeForDropType(inDestType), thePoint,
+ theStartTime);
+ } else {
+ SCOPED_DOCUMENT_EDITOR(*theDoc, QObject::tr("Add Instance"))
+ ->CreateSceneGraphInstance(theComposerType, inTarget, inSlide, theInsertType,
+ thePoint, (EPrimitiveType)m_PrimitiveType, theStartTime,
+ false);
+ }
+ }
+ return nullptr;
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.h b/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.h
new file mode 100644
index 00000000..14b6e62d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/BasicObjectDropSource.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** 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 __BasicObjectDropSource_H__
+#define __BasicObjectDropSource_H__
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "DropSource.h"
+#include "StudioObjectTypes.h"
+
+class CStudioApp;
+class CDropTarget;
+
+class CBasicObjectDropSource : public CDropSource
+{
+public:
+ CBasicObjectDropSource(long inFlavor, IDragable *inDragable);
+
+ // CDropSource
+ bool CanMove() override;
+ bool CanCopy() override;
+ bool ValidateTarget(CDropTarget *inTarget) override;
+
+ CCmd *GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide) override;
+
+protected:
+ EPrimitiveType m_PrimitiveType;
+ bool m_IsIndependent;
+};
+
+#endif // #ifndef __BasicObjectDropSource_H__
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.cpp
new file mode 100644
index 00000000..9b1d4612
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.cpp
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "StudioApp.h"
+#include "DropContainer.h"
+#include "HotKeys.h"
+#include "MouseCursor.h"
+#include "FileDropSource.h"
+#include "ResourceCache.h"
+
+//===============================================================================
+/**
+ * A derived object will call this to subscribe to Drop Flavors.
+ * @param inMainFlavor the Flavor to add.
+ * @see CDropSource.h
+ */
+void CDropContainer::AddMainFlavor(long inMainFlavor)
+{
+ m_Flavors.push_back(inMainFlavor);
+}
+
+//===============================================================================
+/**
+ * This an accessor to get the begining of the iterator.
+ * @return the Iterator
+ */
+CDropContainer::TFlavorItr CDropContainer::GetFlavorBegin()
+{
+ return m_Flavors.begin();
+}
+
+//===============================================================================
+/**
+ * This is an iterator to the end.
+ * @return the Iterator
+ */
+CDropContainer::TFlavorItr CDropContainer::GetFlavorEnd()
+{
+ return m_Flavors.end();
+}
+
+//===============================================================================
+/**
+ * Constructor to build the container.
+ * This also sets up the DropProxy.
+ * @see CDropProxy
+ */
+CWinDropContainer::CWinDropContainer()
+ : m_DropProxy(this)
+{
+}
+//===============================================================================
+/**
+ * Destructor
+ */
+CWinDropContainer::~CWinDropContainer()
+{
+}
+
+//===============================================================================
+/**
+ * This is so the Window that is derived from CWinContainer can receive drags.
+ * @param inWindow the outer to drag.
+ */
+void CWinDropContainer::RegisterForDnd(QWidget *inWindow)
+{
+ // This passes the inWindow down to the COLEDropSource.
+ m_DropProxy.Register(inWindow);
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.h b/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.h
new file mode 100644
index 00000000..32faa27b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropContainer.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2003 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_DROPCONTAINER
+#define INCLUDED_DROPCONTAINER
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "DropProxy.h"
+#include "DropSource.h"
+#include <vector>
+
+class CStudioApp;
+
+class CDropContainer
+{
+public:
+ typedef std::vector<long> TFlavor;
+ typedef TFlavor::iterator TFlavorItr;
+
+protected:
+ TFlavor m_Flavors; ///< This is a list of flavors handled by this container.
+
+public:
+ CDropContainer() {}
+ virtual ~CDropContainer() {}
+
+ CDropContainer::TFlavorItr GetFlavorBegin();
+ CDropContainer::TFlavorItr GetFlavorEnd();
+
+ void AddMainFlavor(long inMainFlavor);
+
+ // These need to get implemented by the Cross platform Container.
+ virtual bool OnDragWithin(CDropSource &inSource) = 0;
+ virtual bool OnDragReceive(CDropSource &inSource) = 0;
+ virtual void OnDragLeave() = 0;
+ virtual void OnReflectMouse(CPt &inPoint, Qt::KeyboardModifiers inFlags) = 0;
+};
+
+class CWinDropContainer : public CDropContainer
+{
+public:
+ CWinDropContainer();
+ virtual ~CWinDropContainer();
+
+ void RegisterForDnd(QWidget *inWindow);
+ long ReflectMouse(long inX, long inY);
+
+protected:
+ // These are utility functions.
+ CDropProxy m_DropProxy; ///< The COLEDropSource pass through.
+};
+
+#endif \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.cpp
new file mode 100644
index 00000000..a8063105
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.cpp
@@ -0,0 +1,324 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "HotKeys.h"
+#include "DropProxy.h"
+#include "DropContainer.h"
+#include "DropSource.h"
+#include "Qt3DSFile.h"
+#include "Qt3DSString.h"
+#include "IDragable.h"
+
+#include <QtGui/qevent.h>
+#include <QtWidgets/qwidget.h>
+
+//=============================================================================
+/**
+ * Constructor
+ * @param inParent gesture listener that messages will be passed to
+ */
+CDropProxy::CDropProxy(CDropContainer *inParent)
+{
+ m_Parent = inParent;
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CDropProxy::~CDropProxy()
+{
+}
+
+//=============================================================================
+/**
+ * Called when a drag begins over a window.
+ */
+void CDropProxy::dragEnterEvent(QDragEnterEvent *event)
+{
+ event->ignore();
+ if (GetDragItemCount(event->mimeData())) {
+ CDropContainer::TFlavorItr theItr = m_Parent->GetFlavorBegin();
+ CDropContainer::TFlavorItr theEnd = m_Parent->GetFlavorEnd();
+
+ for (; theItr != theEnd; ++theItr) {
+ if (HasMainFlavor(event->mimeData(), *theItr)) {
+ event->accept();
+ event->setDropAction(Qt::CopyAction);
+ break;
+ }
+ }
+ }
+}
+
+//===============================================================================
+/**
+ * This will extract the Data from the OLEDropObject and convert it to a CDropSource.
+ * @param inDataObject the Win specific dropSource.
+ * @param inFlavor the Flavor we need.
+ * @param inItem the position of the data in the DataObject.
+ * @return Newly extracted or created DropSource.
+ * Note: if we created it we need to make sure we destroy this object.
+ */
+CDropSource *CDropProxy::GetDropSource(const QMimeData *inDataObject, long inFlavor, long inItem)
+{
+ const CDropSource *theDropSource = nullptr;
+ switch (inFlavor) {
+ case QT3DS_FLAVOR_FILE:
+ // Check if we have a Drop File on our hands.
+ if (inDataObject->hasUrls()) {
+ // Get the number of files being dragged
+ short theFileCount = inDataObject->urls().count();
+
+ // Only allow single files to be dragged
+ if (theFileCount > inItem && !inDataObject->urls().isEmpty()) {
+ QString filePath = inDataObject->urls().at(inItem).toLocalFile();
+ theDropSource = CDropSourceFactory::Create(QT3DS_FLAVOR_FILE, filePath);
+ }
+ }
+ break;
+
+ case QT3DS_FLAVOR_TEXT:
+ case QT3DS_FLAVOR_BASIC_OBJECTS:
+ case QT3DS_FLAVOR_ASSET_LIB:
+ case QT3DS_FLAVOR_ASSET_TL:
+ case QT3DS_FLAVOR_ASSET_UICFILE:
+ // make an asset out of this
+ // Get a pointer to the object
+ theDropSource = dynamic_cast<const CDropSource *>(inDataObject);
+ if (theDropSource && theDropSource->GetFlavor() != inFlavor)
+ theDropSource = nullptr;
+ break;
+ }
+ return const_cast<CDropSource *>(theDropSource);
+}
+
+//===============================================================================
+/**
+ * This is to count the number of objects in the DataObject.
+ * @param inDataObject the WinSpecific dropobject
+ * @return The number of items found.
+ */
+long CDropProxy::GetDragItemCount(const QMimeData *inDataObject)
+{
+ long theCount = 0;
+
+ // Check if we have a Drop File on our hands.
+ if (inDataObject->hasUrls()) {
+ // Get the number of files being dragged
+ theCount = inDataObject->urls().count();
+ } else {
+ auto source = dynamic_cast<const CDropSource *>(inDataObject);
+ theCount = source != nullptr ? 1 : 0;
+ }
+
+ return theCount;
+}
+//===============================================================================
+/**
+ * This will check the DataObject for the Flavor.
+ * @param inDataObject the Win specific data object.
+ * @param inFlavor the requested Flavor.
+ * @param true if the DataObject contains the flavor.
+ */
+bool CDropProxy::HasMainFlavor(const QMimeData *inDataObject, long inFlavor)
+{
+ if (inFlavor == QT3DS_FLAVOR_FILE)
+ return inDataObject->hasUrls();
+ auto source = dynamic_cast<const CDropSource *>(inDataObject);
+ return source != nullptr && source->GetFlavor() == inFlavor;
+}
+
+//=============================================================================
+/**
+ * Called when a drag event leaves the window. May cause the drag to end if
+ * the mouse button is no longer down.
+ */
+void CDropProxy::dragLeaveEvent(QDragLeaveEvent *event)
+{
+ if (m_Parent) {
+ m_Parent->OnDragLeave();
+ event->accept();
+ }
+}
+
+//=============================================================================
+/**
+ * Called when an item is being dragged over this window. Passes the message
+ * on to the gesture listener.
+ */
+void CDropProxy::dragMoveEvent(QDragMoveEvent *event)
+{
+ bool theAcceptFlag = false;
+
+ if (m_Parent) {
+ long theCount = GetDragItemCount(event->mimeData());
+ for (long theIndex = 0; theIndex < theCount; ++theIndex) {
+ Qt::KeyboardModifiers theModifyerFlags = ReflectMouse(event->pos().x(), event->pos().y());
+
+ CDropContainer::TFlavorItr theItr = m_Parent->GetFlavorBegin();
+ CDropContainer::TFlavorItr theEnd = m_Parent->GetFlavorEnd();
+
+ for (; theItr != theEnd; ++theItr) {
+ // Find the Flavor of this Item.
+ if (HasMainFlavor(event->mimeData(), *theItr)) {
+ CDropSource *theDropSource = GetDropSource(event->mimeData(), *theItr, theIndex);
+ if (theDropSource) {
+ CPt thePoint(event->pos());
+ theModifyerFlags = CHotKeys::GetCurrentKeyModifiers();
+ theDropSource->SetCurrentPoint(thePoint);
+ theDropSource->SetCurrentFlags(theModifyerFlags);
+
+ theDropSource->InterpretKeyFlags(theModifyerFlags);
+ // This will be implemented in the cross platform code.
+ theAcceptFlag = m_Parent->OnDragWithin(*theDropSource);
+ event->setAccepted(theAcceptFlag);
+ if (theAcceptFlag) {
+ if (theDropSource->CanCopy())
+ event->setDropAction(Qt::CopyAction);
+ else
+ event->setDropAction(Qt::MoveAction);
+ // Breakout of the outer loop.
+ theIndex = theCount;
+ }
+
+ // delete the drop source if it was a file
+ if (QT3DS_FLAVOR_FILE == theDropSource->GetFlavor()) {
+ delete theDropSource;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+//=============================================================================
+/**
+ * Called when an OLE item is dropped on this window.
+ * @return TRUE if the drop was valid, otherwise FALSE
+ */
+void CDropProxy::dropEvent(QDropEvent *event)
+{
+ if (m_Parent) {
+ long theCount = GetDragItemCount(event->mimeData());
+ for (long theIndex = 0; theIndex < theCount; ++theIndex) {
+ Qt::KeyboardModifiers theModifyerFlags = ReflectMouse(event->pos().x(), event->pos().y());
+
+ theModifyerFlags = CHotKeys::GetCurrentKeyModifiers();
+
+ CDropContainer::TFlavorItr theItr = m_Parent->GetFlavorBegin();
+ CDropContainer::TFlavorItr theEnd = m_Parent->GetFlavorEnd();
+
+ for (; theItr != theEnd; ++theItr) {
+ // Find the Flavor of this Item.
+ if (HasMainFlavor(event->mimeData(), *theItr)) {
+ // This will convert all stuff into a DropSource
+ CDropSource *theDropSource = GetDropSource(event->mimeData(), *theItr, theIndex);
+
+ // This will be implemented in the cross platform code.
+ if (theDropSource) {
+ CPt thePoint(event->pos());
+ theDropSource->SetCurrentPoint(thePoint);
+ theDropSource->SetCurrentFlags(theModifyerFlags);
+
+ // Don't call the recieve if we did not have a valid droptarget to begin
+ // with.
+ if (theDropSource->GetHasValidTarget()) {
+ theDropSource->InterpretKeyFlags(theModifyerFlags);
+ // This will be implemented in the cross platform code.
+ m_Parent->OnDragReceive(*theDropSource);
+ }
+ }
+
+ // Don't delete the drop source here, the creator will destroy it
+ // delete theDropSource;
+ }
+ }
+ }
+ }
+
+ event->accept();
+}
+//===============================================================================
+/**
+ * This function is used to report back to the container where the mouse is during a drag.
+ * The container can send this to children as MouseOvers so the children can do mouseover
+ *things.
+ * @param inX the X position
+ * @param inY the Y position.
+ * @return the Key modifyers.
+ */
+Qt::KeyboardModifiers CDropProxy::ReflectMouse(long inX, long inY)
+{
+ Qt::KeyboardModifiers theModifierFlags = Qt::NoModifier;
+
+ if (m_Parent) {
+ // Get the mouse stuff
+ CPt theMouseLoc(inX, inY);
+
+ // Determine which modifier keys are down so that we can pass them along
+
+ theModifierFlags = CHotKeys::GetCurrentKeyModifiers();
+
+ // Pass the gesture into the studio control
+ m_Parent->OnReflectMouse(theMouseLoc, theModifierFlags);
+ }
+ return theModifierFlags;
+}
+
+void CDropProxy::Register(QWidget *widget)
+{
+ widget->installEventFilter(this);
+ widget->setAcceptDrops(true);
+}
+
+bool CDropProxy::eventFilter(QObject *watched, QEvent *event)
+{
+ Q_UNUSED(watched);
+
+ switch (event->type()) {
+ case QEvent::DragEnter:
+ dragEnterEvent(static_cast<QDragEnterEvent *>(event));
+ return event->isAccepted();
+ case QEvent::DragLeave:
+ dragLeaveEvent(static_cast<QDragLeaveEvent *>(event));
+ return event->isAccepted();
+ case QEvent::DragMove:
+ dragMoveEvent(static_cast<QDragMoveEvent *>(event));
+ return event->isAccepted();
+ case QEvent::Drop:
+ dropEvent(static_cast<QDropEvent *>(event));
+ return event->isAccepted();
+ default:
+ return false;
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.h b/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.h
new file mode 100644
index 00000000..ad47cddc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropProxy.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** 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_DROP_PROXY
+#define INCLUDED_DROP_PROXY 1
+
+#pragma once
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include <QObject>
+
+//==============================================================================
+// Forwards
+//==============================================================================
+
+class CDropContainer;
+class CStudioApp;
+class CDropSource;
+
+QT_FORWARD_DECLARE_CLASS(QDragEnterEvent)
+QT_FORWARD_DECLARE_CLASS(QDragLeaveEvent)
+QT_FORWARD_DECLARE_CLASS(QDragMoveEvent)
+QT_FORWARD_DECLARE_CLASS(QDropEvent)
+QT_FORWARD_DECLARE_CLASS(QMimeData)
+
+class CDropProxy : public QObject
+{
+ Q_OBJECT
+public:
+ CDropProxy(CDropContainer *inParent);
+ virtual ~CDropProxy();
+
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dragLeaveEvent(QDragLeaveEvent *event);
+ void dragMoveEvent(QDragMoveEvent *event);
+ void dropEvent(QDropEvent *event);
+
+ CDropSource *GetDropSource(const QMimeData *inDataObject, long inFlavor, long inItem);
+ long GetDragItemCount(const QMimeData *inDataObject);
+ bool HasMainFlavor(const QMimeData *inDataObject, long inFlavor);
+ Qt::KeyboardModifiers ReflectMouse(long inX, long inY);
+
+ void Register(QWidget* widget);
+
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+protected:
+ CDropContainer *m_Parent;
+};
+
+#endif // INCLUDED_DROP_PROXY
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.cpp
new file mode 100644
index 00000000..bf168d56
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.cpp
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2003 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 "IDragable.h"
+#include "FileDropSource.h"
+#include "ExplorerFileDropSource.h"
+#include "TimelineDropSource.h"
+#include "BasicObjectDropSource.h"
+#include "Views.h"
+#include "MainFrm.h"
+#include "TimelineWidget.h"
+
+CDropSource::CDropSource(long inFlavor, unsigned long inSize)
+ : m_Flavor(inFlavor)
+ , m_Size(inSize)
+{
+ g_StudioApp.GetViews()->getMainFrame()->getTimelineWidget()->enableDnD();
+}
+
+CDropSource::~CDropSource()
+{
+ g_StudioApp.GetViews()->getMainFrame()->getTimelineWidget()->enableDnD(false);
+}
+
+// Create a drop source using a file path. Used for files
+CDropSource *CDropSourceFactory::Create(long inFlavor, const QString &filePath)
+{
+ CDropSource *theDropSource(nullptr);
+ switch (inFlavor) {
+ case QT3DS_FLAVOR_FILE:
+ theDropSource = new CExplorerFileDropSource(inFlavor, filePath);
+ break;
+
+ case QT3DS_FLAVOR_ASSET_UICFILE:
+ theDropSource = new CFileDropSource(inFlavor, filePath);
+ break;
+
+ default:
+ break;
+ }
+
+ return theDropSource;
+}
+
+// Create a drop source using a draggable. Used for anything that implements IDragable
+CDropSource *CDropSourceFactory::Create(long inFlavor, IDragable *inDragable)
+{
+ CDropSource *theDropSource(nullptr);
+ switch (inFlavor) {
+ case QT3DS_FLAVOR_BASIC_OBJECTS:
+ theDropSource = new CBasicObjectDropSource(inFlavor, inDragable);
+ break;
+
+ case QT3DS_FLAVOR_ASSET_TL:
+ theDropSource = new CTimeLineDropSource(inFlavor, inDragable);
+ break;
+
+ default:
+ // unsupported flavor
+ break;
+ }
+
+ return theDropSource;
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.h b/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.h
new file mode 100644
index 00000000..7feefbc5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropSource.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2003 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
+//==============================================================================
+#ifndef INCLUDED_DROPSOURCE
+#define INCLUDED_DROPSOURCE
+
+#include "Qt3DSDMHandles.h"
+#include "Pt.h"
+#include "Cmd.h"
+#include <QMimeData>
+
+typedef enum _EDROPDESTINATION {
+ EDROPDESTINATION_ON, ///< drop occurs on the target asset
+ EDROPDESTINATION_ABOVE, ///< drop occurs above the target asset
+ EDROPDESTINATION_BELOW ///< drop occurs below the target asset
+} EDROPDESTINATION;
+
+class CDropTarget;
+class IDragable;
+class CStudioApp;
+
+class CDropSource : public QMimeData
+{
+protected:
+ long m_Flavor;
+ unsigned long m_Size;
+
+ qt3dsdm::TInstanceHandleList m_Instances;
+ long m_ObjectType = 0;
+ int m_FileType = 0;
+ bool m_HasValidTarget = false;
+ CPt m_CurrentPoint;
+ Qt::KeyboardModifiers m_CurrentFlags = 0;
+
+public:
+ CDropSource(long inFlavor, unsigned long inSize = 0);
+ virtual ~CDropSource();
+
+ virtual bool CanMove() = 0;
+ virtual bool CanCopy() = 0;
+ long GetObjectType() const { return m_ObjectType; }
+ long getFileType() const { return m_FileType; }
+ long GetFlavor() const { return m_Flavor; }
+ virtual bool ValidateTarget(CDropTarget *) = 0;
+
+ virtual bool GetHasValidTarget() const { return m_HasValidTarget; }
+ virtual void SetHasValidTarget(bool inValid) { m_HasValidTarget = inValid; }
+ virtual void InterpretKeyFlags(long) {}
+
+ virtual void SetCurrentPoint(CPt &inPoint) { m_CurrentPoint = inPoint; }
+ virtual CPt GetCurrentPoint() const { return m_CurrentPoint; }
+
+ virtual void SetCurrentFlags(Qt::KeyboardModifiers inFlags) { m_CurrentFlags = inFlags; }
+ virtual Qt::KeyboardModifiers GetCurrentFlags() const { return m_CurrentFlags; }
+
+ virtual CCmd *GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle, EDROPDESTINATION,
+ qt3dsdm::Qt3DSDMSlideHandle)
+ {
+ return nullptr;
+ }
+ virtual CCmd *GenerateSlideCommand(long) { return nullptr; }
+};
+
+class CDropSourceFactory
+{
+public:
+ static CDropSource *Create(long inFlavor, IDragable *inDragable);
+ static CDropSource *Create(long inFlavor, const QString &filePath);
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.cpp
new file mode 100644
index 00000000..611f242d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.cpp
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2003 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 "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Qt3DSDMSlides.h"
+#include "DropTarget.h"
+
+CDropTarget::CDropTarget()
+ : m_Instance(0)
+ , m_ObjectType(OBJTYPE_UNKNOWN)
+{
+}
+
+bool CDropTarget::CanAddToMaster()
+{
+ long theTargetObjectType = GetObjectType();
+ if (theTargetObjectType == OBJTYPE_SCENE || theTargetObjectType == OBJTYPE_COMPONENT)
+ return true;
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::ISlideSystem *theSlideSystem = theDoc->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theTargetSlide = theSlideSystem->GetAssociatedSlide(GetInstance());
+ return theTargetSlide && theSlideSystem->IsMasterSlide(theTargetSlide);
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.h b/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.h
new file mode 100644
index 00000000..c9b00c8f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/DropTarget.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2003 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_DROPTARGET
+#define INCLUDED_DROPTARGET
+
+#pragma once
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "StudioObjectTypes.h"
+#include "Qt3DSDMHandles.h"
+
+class CDropSource;
+class CStudioApp;
+
+class CDropTarget
+{
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle m_Instance;
+ long m_ObjectType;
+
+public:
+ CDropTarget();
+ virtual ~CDropTarget() {}
+
+ virtual bool Accept(CDropSource &inSource) = 0;
+ virtual bool Drop(CDropSource &inSource) = 0;
+ virtual long GetObjectType() = 0;
+
+ virtual void SetInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance) { m_Instance = inInstance; }
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstance() { return m_Instance; }
+
+ virtual bool IsRelative(qt3dsdm::Qt3DSDMInstanceHandle) { return false; }
+ virtual bool IsSelf(qt3dsdm::Qt3DSDMInstanceHandle) { return false; }
+ virtual bool IsMaster() { return false; }
+ virtual bool CanAddToMaster();
+};
+
+#endif \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.cpp
new file mode 100644
index 00000000..91e67227
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.cpp
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "qtAuthoring-config.h"
+#include "ExplorerFileDropSource.h"
+#include "Dialogs.h"
+#include "DropTarget.h"
+#include "StudioObjectTypes.h"
+#include "IDragable.h"
+#include "Qt3DSFileTools.h"
+#include "ImportUtils.h"
+
+bool CExplorerFileDropSource::s_FileHasValidTarget = false;
+
+//===============================================================================
+/**
+ *
+ */
+bool CExplorerFileDropSource::ValidateTarget(CDropTarget *inTarget)
+{
+ // Check the type is valid and if target can accept
+ bool theValidTarget =
+ ((m_ObjectType != OBJTYPE_UNKNOWN) && (inTarget->GetObjectType() == QT3DS_FLAVOR_FILE));
+ SetHasValidTarget(theValidTarget);
+
+ return theValidTarget;
+}
+
+//===============================================================================
+/**
+ *
+ */
+CExplorerFileDropSource::CExplorerFileDropSource(long inFlavor, const QString &filePath)
+ : CDropSource(inFlavor)
+ , m_FilePath(filePath)
+{
+ m_ObjectType = Q3DStudio::ImportUtils::GetObjectFileTypeForFile(filePath).m_IconType;
+
+ // Fix because DAE files are the *only* thing you can drop onto the project
+ if (filePath.endsWith(QLatin1String(".dae"), Qt::CaseInsensitive))
+ m_ObjectType = OBJTYPE_GROUP;
+
+#ifdef QT_3DSTUDIO_FBX
+ else if (filePath.endsWith(QLatin1String(".fbx"), Qt::CaseInsensitive))
+ m_ObjectType = OBJTYPE_GROUP;
+#endif
+}
+
+//===============================================================================
+/**
+ *
+ */
+void CExplorerFileDropSource::SetHasValidTarget(bool inValid)
+{
+ m_HasValidTarget = inValid;
+ CExplorerFileDropSource::s_FileHasValidTarget = inValid;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CExplorerFileDropSource::GetHasValidTarget()
+{
+ return CExplorerFileDropSource::s_FileHasValidTarget;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CExplorerFileDropSource::CanMove()
+{
+ return false;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CExplorerFileDropSource::CanCopy()
+{
+ return true;
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.h b/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.h
new file mode 100644
index 00000000..162c0101
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/ExplorerFileDropSource.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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 __EXPLORERFILEDROPSOURCE_H__
+#define __EXPLORERFILEDROPSOURCE_H__
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "DropSource.h"
+#include "Qt3DSFile.h"
+
+class CStudioApp;
+class CDropTarget;
+
+//==============================================================================
+/**
+ * @class CExplorerFileDropSource
+ * @brief Drop Source for QT3DS_FLAVOR_UICFILE
+ *
+ * This class is meant to handle drag and drop for QT3DS_FLAVOR_UICFILE,
+ * for example when user drags a file from Explorer Window (outside Studio).
+ */
+class CExplorerFileDropSource : public CDropSource
+{
+protected:
+ QString m_FilePath;
+ static bool s_FileHasValidTarget;
+
+public:
+ CExplorerFileDropSource(long inFlavor, const QString &filePath);
+
+ bool CanMove() override;
+ bool CanCopy() override;
+ bool ValidateTarget(CDropTarget *inTarget) override;
+ bool GetHasValidTarget();
+ void SetHasValidTarget(bool inValid) override;
+ QString getFilePath() const { return m_FilePath; }
+};
+
+#endif // #ifndef __EXPLORERFILEDROPSOURCE_H__
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.cpp
new file mode 100644
index 00000000..e88fb401
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.cpp
@@ -0,0 +1,322 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "Dialogs.h"
+#include "FileDropSource.h"
+#include "DropTarget.h"
+#include "StudioObjectTypes.h"
+#include "HotKeys.h"
+#include "Doc.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "IDocumentEditor.h"
+#include "Qt3DSFileTools.h"
+#include "ImportUtils.h"
+#include "ChooseImagePropertyDlg.h"
+#include "Dispatch.h"
+
+bool CFileDropSource::s_FileHasValidTarget = false;
+
+bool CFileDropSource::ValidateTarget(CDropTarget *inTarget)
+{
+ using namespace Q3DStudio;
+
+ const auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge();
+ EStudioObjectType targetType = (EStudioObjectType)inTarget->GetObjectType();
+ if (m_ObjectType & (OBJTYPE_PRESENTATION | OBJTYPE_QML_STREAM)) {
+ SetHasValidTarget(!bridge->isDefaultMaterial(inTarget->GetInstance())
+ && (targetType & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)));
+ return m_HasValidTarget;
+ }
+
+ if (m_ObjectType == OBJTYPE_MATERIALDATA) {
+ SetHasValidTarget(targetType & OBJTYPE_IS_MATERIAL);
+ return m_HasValidTarget;
+ }
+
+ // the only thing we want to do from here is check the type.
+ bool targetIsValid = CStudioObjectTypes::AcceptableParent((EStudioObjectType)m_ObjectType,
+ targetType);
+
+ // allow material, image, and scene as valid targets for image drops
+ if (!targetIsValid && m_FileType == DocumentEditorFileType::Image) {
+ if (targetType & (OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)) {
+ // Default material shouldn't be targeatable
+ targetIsValid = !bridge->isDefaultMaterial(inTarget->GetInstance());
+ } else {
+ // Image isn't normally acceptable child of a scene, but dropping image on scene
+ // will create a rect with image as texture on active layer
+ targetIsValid = targetType == OBJTYPE_SCENE;
+ }
+ }
+
+ if (!targetIsValid) {
+ SetHasValidTarget(false);
+ return false;
+ } else {
+ if (CHotKeys::IsKeyDown(Qt::AltModifier) && targetType != OBJTYPE_SCENE
+ && targetType != OBJTYPE_COMPONENT) {
+ qt3dsdm::Qt3DSDMInstanceHandle theTarget = inTarget->GetInstance();
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ IDocumentReader &theReader(theDoc->GetDocumentReader());
+ qt3dsdm::Qt3DSDMSlideHandle toSlide = theReader.GetAssociatedSlide(theTarget);
+
+ if (!theReader.IsMasterSlide(toSlide))
+ targetIsValid = false;
+ }
+
+ SetHasValidTarget(targetIsValid);
+ return targetIsValid;
+ }
+}
+
+CFileDropSource::CFileDropSource(long inFlavor, const QString &filePath)
+ : CDropSource(inFlavor)
+ , m_FilePath(filePath)
+{
+ const auto objFileType = Q3DStudio::ImportUtils::GetObjectFileTypeForFile(filePath);
+ m_ObjectType = objFileType.m_ObjectType;
+ m_FileType = objFileType.m_FileType;
+}
+
+void CFileDropSource::SetHasValidTarget(bool inValid)
+{
+ m_HasValidTarget = inValid;
+ CFileDropSource::s_FileHasValidTarget = inValid;
+}
+
+bool CFileDropSource::GetHasValidTarget()
+{
+ return CFileDropSource::s_FileHasValidTarget;
+}
+
+bool CFileDropSource::CanMove()
+{
+ return false;
+}
+
+bool CFileDropSource::CanCopy()
+{
+ return true;
+}
+
+CCmd *CFileDropSource::GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide)
+{
+ CDoc &theDoc(*g_StudioApp.GetCore()->GetDoc());
+ CPt thePoint;
+ // if ( CHotKeys::IsKeyDown( Qt::AltModifier ) )
+ // thePoint = GetCurrentPoint();
+
+ long theStartTime = -1;
+ if (CHotKeys::IsKeyDown(Qt::ControlModifier))
+ theStartTime = theDoc.GetCurrentViewTime();
+
+ if (QFileInfo(m_FilePath).isFile()) {
+ QString theCommandName;
+ // TODO: internationalize
+ switch (m_FileType) {
+ case Q3DStudio::DocumentEditorFileType::DAE:
+ theCommandName = QObject::tr("File Drop DAE File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Import:
+ theCommandName = QObject::tr("File Drop Import File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Image:
+ theCommandName = QObject::tr("File Drop Image File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Behavior:
+ theCommandName = QObject::tr("File Drop Behavior File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Mesh:
+ theCommandName = QObject::tr("File Drop Mesh File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Font:
+ theCommandName = QObject::tr("File Drop Font File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Effect:
+ theCommandName = QObject::tr("File Drop Effect File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Material:
+ theCommandName = QObject::tr("File Drop Material File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Path:
+ theCommandName = QObject::tr("File Drop Path File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Presentation:
+ theCommandName = QObject::tr("File Drop Subpresentation File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::QmlStream:
+ theCommandName = QObject::tr("File Drop QML Stream File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::FBX:
+ theCommandName = QObject::tr("File Drop FBX File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Sound:
+ theCommandName = QObject::tr("File Drop Sound File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::Project:
+ theCommandName = QObject::tr("File Drop Project File");
+ break;
+ case Q3DStudio::DocumentEditorFileType::MaterialData:
+ theCommandName = QObject::tr("File Drop Material Data File");
+ break;
+ default:
+ theCommandName = QObject::tr("File Drop Unknown File");
+ break;
+ }
+
+ auto &bridge(*theDoc.GetStudioSystem()->GetClientDataModelBridge());
+ bool isPres = m_FileType == Q3DStudio::DocumentEditorFileType::Presentation
+ || m_FileType == Q3DStudio::DocumentEditorFileType::QmlStream;
+ bool isImage = m_FileType == Q3DStudio::DocumentEditorFileType::Image
+ && inDestType == EDROPDESTINATION_ON;
+ bool isMatData = m_FileType == Q3DStudio::DocumentEditorFileType::MaterialData;
+ EStudioObjectType rowType = bridge.GetObjectType(inTarget);
+ if (isPres || isImage) { // set as a texture
+ Q3DStudio::CString src; // presentation id or image file name
+ if (isPres) {
+ QString pathFromRoot = QDir(theDoc.GetCore()->getProjectFile().getProjectPath())
+ .relativeFilePath(m_FilePath);
+ src = Q3DStudio::CString::fromQString(theDoc.GetCore()
+ ->getProjectFile().getPresentationId(pathFromRoot));
+ } else { // Image
+ src = Q3DStudio::CString::fromQString(QFileInfo(theDoc.GetDocumentPath()).dir()
+ .relativeFilePath(m_FilePath));
+ }
+
+ if (rowType == OBJTYPE_LAYER) { // Drop on a Layer
+ if (isPres) {
+ auto propHandle = bridge.GetSourcePathProperty();
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->SetInstancePropertyValueAsRenderable(inTarget, propHandle, src);
+ } else { // Image
+ ChooseImagePropertyDlg dlg(inTarget);
+ if (dlg.exec() == QDialog::Accepted) {
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = dlg.getSelectedPropertyHandle();
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->SetInstancePropertyValueAsRenderable(inTarget, propHandle, src);
+ }
+ }
+ } else if (rowType & OBJTYPE_IS_MATERIAL) { // Drop on a Material
+ // if this is a ref material, update the material it references
+ qt3dsdm::Qt3DSDMInstanceHandle refInstance = 0;
+ if (rowType == OBJTYPE_REFERENCEDMATERIAL) {
+ auto optValue = theDoc.getSceneEditor()->GetInstancePropertyValue(inTarget,
+ bridge.GetObjectDefinitions().m_ReferencedMaterial
+ .m_ReferencedMaterial.m_Property);
+ if (optValue.hasValue()) {
+ refInstance = bridge.GetInstance(theDoc.GetSceneInstance(),
+ optValue.getValue());
+ }
+ }
+ ChooseImagePropertyDlg dlg(refInstance ? refInstance : inTarget, refInstance != 0);
+ if (isImage)
+ dlg.setTextureTitle();
+
+ if (dlg.exec() == QDialog::Accepted) {
+ qt3dsdm::Qt3DSDMPropertyHandle propHandle = dlg.getSelectedPropertyHandle();
+ if (dlg.detachMaterial()) {
+ Q3DStudio::ScopedDocumentEditor editor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc,
+ tr("Set material diffuse map")));
+ editor->BeginAggregateOperation();
+ editor->SetMaterialType(inTarget, QStringLiteral("Standard Material"));
+ editor->setInstanceImagePropertyValue(inTarget, propHandle, src, isPres);
+ editor->EndAggregateOperation();
+ } else {
+ const auto finalTarget = refInstance ? refInstance : inTarget;
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->setInstanceImagePropertyValue(finalTarget, propHandle, src, isPres);
+ theDoc.getSceneEditor()->saveIfMaterial(finalTarget);
+ }
+ }
+ } else if (rowType == OBJTYPE_IMAGE) {
+ auto propHandle = isPres ? bridge.getSubpresentationProperty()
+ : bridge.GetSourcePathProperty();
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->SetInstancePropertyValueAsRenderable(inTarget, propHandle, src);
+ } else if (rowType == OBJTYPE_SCENE) { // dropping on the scene as a texture
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->addRectFromSource(src, inSlide, isPres, thePoint, theStartTime);
+ }
+ } else if (isMatData) {
+ if (rowType & OBJTYPE_IS_MATERIAL) {
+ if (!QFileInfo(m_FilePath).completeBaseName().contains(QLatin1Char('#'))) {
+ const auto doc = g_StudioApp.GetCore()->GetDoc();
+ { // Scope for the ScopedDocumentEditor
+ Q3DStudio::ScopedDocumentEditor sceneEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {}));
+ QString name;
+ QMap<QString, QString> values;
+ QMap<QString, QMap<QString, QString>> textureValues;
+ sceneEditor->getMaterialInfo(m_FilePath, name, values, textureValues);
+ const auto material = sceneEditor->getOrCreateMaterial(m_FilePath);
+ sceneEditor->setMaterialValues(material, 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
+ doc->GetCore()->GetCmdStack()->RemoveLastUndo();
+
+ Q3DStudio::ScopedDocumentEditor sceneEditor(
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(
+ *doc, tr("Drag and Drop Material")));
+ QString relPath = theDoc.GetRelativePathToDoc(m_FilePath);
+
+ sceneEditor->SetMaterialType(inTarget, QStringLiteral("Referenced Material"));
+ sceneEditor->setMaterialSourcePath(inTarget,
+ Q3DStudio::CString::fromQString(relPath));
+ sceneEditor->setMaterialReferenceByPath(inTarget, relPath);
+ theDoc.SelectDataModelObject(inTarget);
+ } else {
+ g_StudioApp.GetDialogs()->DisplayMessageBox(
+ tr("Error"), tr("The character '#' is not allowed in "
+ "the name of a material definition file."),
+ Qt3DSMessageBox::ICON_ERROR, false);
+ }
+ }
+ } else {
+ Q3DStudio::SCOPED_DOCUMENT_EDITOR(theDoc, theCommandName)
+ ->ImportFile(static_cast<Q3DStudio::DocumentEditorFileType::Enum>(m_FileType),
+ Q3DStudio::CString::fromQString(m_FilePath), inTarget, inSlide,
+ CDialogs::GetImportFileExtension(),
+ Q3DStudio::ImportUtils::GetInsertTypeForDropType(inDestType), thePoint,
+ theStartTime);
+ }
+ }
+ return nullptr;
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.h b/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.h
new file mode 100644
index 00000000..eff6bc10
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/FileDropSource.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** 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 __FILEDROPSOURCE_H__
+#define __FILEDROPSOURCE_H__
+
+#include "DropSource.h"
+
+class CDropTarget;
+
+/**
+ * @class CFileDropSource
+ * @brief Drop Source for QT3DS_FLAVOR_ASSET_UICFILE
+ *
+ * This class is meant to handle drag and drop for QT3DS_FLAVOR_ASSET_UICFILE,
+ * for example when user drags a file from within Studio.
+ */
+class CFileDropSource : public CDropSource
+{
+protected:
+ QString m_FilePath;
+
+ static bool s_FileHasValidTarget;
+
+public:
+ CFileDropSource(long inFlavor, const QString &filePath);
+
+ bool CanMove() override;
+ bool CanCopy() override;
+ bool ValidateTarget(CDropTarget *inTarget) override;
+ bool GetHasValidTarget();
+ void SetHasValidTarget(bool inValid) override;
+
+ CCmd *GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget, EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide) override;
+};
+
+#endif // #ifndef __FILEDROPSOURCE_H__
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.cpp
new file mode 100644
index 00000000..464c6919
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.cpp
@@ -0,0 +1,285 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "SceneDropTarget.h"
+#include "DropTarget.h"
+#include "DropSource.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "IDocumentEditor.h"
+#include "HotKeys.h"
+#include "IDropTargetHelper.h"
+#include "Core.h"
+#include "GraphUtils.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMDataCore.h"
+#include "Qt3DSDMSlides.h"
+#include "PresentationFile.h"
+#include "QtWidgets/qmessagebox.h"
+#include "QtWidgets/qpushbutton.h"
+#include "IDocSceneGraph.h"
+
+// Sceneview stuff
+//===============================================================================
+/**
+ * Constructor.
+ */
+CSceneViewDropTarget::CSceneViewDropTarget()
+{
+ m_ObjectType = OBJTYPE_LAYER;
+}
+
+//===============================================================================
+/**
+ * This will get the objec ttype from the Asset.
+ * Note: The asset can change all of the time, so i always ask the asset for its type.
+ * @return the Studio object type.
+ */
+long CSceneViewDropTarget::GetObjectType()
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (theInstance.Valid()) {
+ CClientDataModelBridge *theBridge =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ m_ObjectType = theBridge->GetObjectType(theInstance);
+ return m_ObjectType;
+ }
+ return OBJTYPE_UNKNOWN;
+}
+
+//===============================================================================
+/**
+ * This get called on every DragWithin.
+ * Note: the source will validate the target instead of the otherway around.
+ * This is because the DropSource knows how to get information from itself without
+ * creating an asset. This is mainly for DropSources that have a lazy creation idiom.
+ * like files.
+ * Dropping into "locked" layers are not allowed.
+ * @param inSource the DropSource in question.
+ * @return true if the DropSource likes the DropTarget.
+ */
+bool CSceneViewDropTarget::Accept(CDropSource &inSource)
+{
+ // We have to set this so we can adjust the Target to accept this source.
+ m_DropSourceObjectType = inSource.GetObjectType();
+ m_DropSourceFileType = inSource.getFileType();
+
+ // always allow DnD presentations and qml streams to the scene
+ if (m_DropSourceObjectType & (OBJTYPE_PRESENTATION | OBJTYPE_QML_STREAM)) {
+ inSource.SetHasValidTarget(true);
+ return true;
+ }
+
+ if (m_DropSourceObjectType == OBJTYPE_MATERIALDATA) {
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ const auto pickedObject = doc->GetSceneGraph()->getObjectAt(inSource.GetCurrentPoint());
+ if (pickedObject.Valid()) {
+ const auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge();
+ if (bridge->GetObjectType(pickedObject) == OBJTYPE_MODEL) {
+ inSource.SetHasValidTarget(true);
+ return true;
+ }
+ }
+ }
+
+ bool theAcceptable = false;
+ // We don't want to generate an asset right now so let the DropSource ask us if it can drop.
+ theAcceptable = inSource.ValidateTarget(this);
+
+ // The DropSource already generated the asset for this in the above.
+ CClientDataModelBridge *theBridge
+ = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ if (theAcceptable && m_Instance.Valid())
+ theAcceptable = !theBridge->IsLockedAtAll(m_Instance);
+
+ return theAcceptable;
+}
+
+//===============================================================================
+/**
+ * This is where is actually happens.
+ * Note: At this point either everything should be verified, and setup in the dropsource.
+ * Then the only thing left to do is to get the Assets and move/copy or connect them.
+ * Or the dropsource needs the target to perform the actual drop.
+ * Note that if the Control key is depressed, the start time follows the current view time(
+ *i.e. playhead position )
+ * And if the Alt key (KEY_MENU) is depressed, the object is dropped at the mouse location.
+ *
+ * @param inSource the Object in question.
+ * @return true if the drop was successful .
+ */
+bool CSceneViewDropTarget::Drop(CDropSource &inSource)
+{
+ // The Parent is a tree control item, so iwe know it can be converted to an Asset.
+ // We have to set this so we can adjust the Target to accept this source.
+ m_DropSourceObjectType = inSource.GetObjectType();
+ m_DropSourceFileType = inSource.getFileType();
+
+ CDoc *doc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle instance = GetInstance();
+ if (m_DropSourceObjectType & (OBJTYPE_PRESENTATION | OBJTYPE_QML_STREAM)) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(QObject::tr("Set Sub-presentation"));
+ msgBox.setText(QObject::tr("Set as sub-presentation to"));
+ QPushButton *layerButton = msgBox.addButton(QObject::tr("Layer"), QMessageBox::YesRole);
+ QPushButton *textureButton = msgBox.addButton(QObject::tr("Texture"), QMessageBox::NoRole);
+ msgBox.addButton(QMessageBox::Cancel);
+
+ msgBox.exec();
+ if (msgBox.clickedButton() == layerButton) {
+ instance = doc->GetActiveLayer();
+ // The GenerateAssetCommand below will take care of setting the subpresentation
+ } else if (msgBox.clickedButton() == textureButton) { // texture
+ instance = doc->GetActiveRootInstance();
+ // The GenerateAssetCommand below will take care of setting the subpresentation
+ } else {
+ return true; // cancel
+ }
+ }
+
+ if (m_DropSourceObjectType == OBJTYPE_MATERIALDATA) {
+ const auto pickedObject = doc->GetSceneGraph()->getObjectAt(inSource.GetCurrentPoint());
+ if (pickedObject.Valid()) {
+ const auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()
+ ->GetClientDataModelBridge();
+ if (bridge->GetObjectType(pickedObject) == OBJTYPE_MODEL) {
+ const auto sceneEditor = doc->getSceneEditor();
+ std::vector<qt3dsdm::Qt3DSDMInstanceHandle> children;
+ sceneEditor->GetChildren(sceneEditor->GetAssociatedSlide(pickedObject),
+ pickedObject, children);
+ for (auto &child : children) {
+ const auto childType = bridge->GetObjectType(child);
+ if (childType == OBJTYPE_REFERENCEDMATERIAL || childType == OBJTYPE_MATERIAL
+ || childType == OBJTYPE_CUSTOMMATERIAL) {
+ instance = child;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (instance.Valid()) {
+ qt3dsdm::ISlideSystem *theSlideSystem = doc->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theSlide = doc->GetActiveSlide();
+ if (!theSlideSystem->IsMasterSlide(theSlide)
+ && (inSource.GetCurrentFlags() & CHotKeys::MODIFIER_ALT)) {
+ if (CanAddToMaster()) {
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlideHandle =
+ theSlideSystem->GetMasterSlide(theSlide);
+ if (theMasterSlideHandle.Valid())
+ theSlide = theMasterSlideHandle;
+ }
+ }
+
+ CCmd *command = inSource.GenerateAssetCommand(instance, EDROPDESTINATION_ON, theSlide);
+ if (command)
+ doc->GetCore()->ExecuteCommand(command);
+ }
+
+ return true;
+}
+
+//===============================================================================
+/**
+ * @return the Asset that we would like the DropSource to drop on to.
+ */
+qt3dsdm::Qt3DSDMInstanceHandle CSceneViewDropTarget::GetInstance()
+{
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::Qt3DSDMInstanceHandle theRootObject = theDoc->GetActiveRootInstance();
+ EStudioObjectType theRootObjType =
+ theDoc->GetStudioSystem()->GetClientDataModelBridge()->GetObjectType(theRootObject);
+
+ // Check if the inObjectType can just go ahead and drop onto the Root object.
+ if (CStudioObjectTypes::AcceptableParent((EStudioObjectType)m_DropSourceObjectType,
+ theRootObjType)
+ || m_DropSourceFileType == Q3DStudio::DocumentEditorFileType::Image) {
+ m_Instance = theRootObject;
+ } else if (theRootObject == theDoc->GetSceneInstance()
+ && CStudioObjectTypes::AcceptableParent((EStudioObjectType)m_DropSourceObjectType,
+ OBJTYPE_LAYER)) {
+ m_Instance = theDoc->GetActiveLayer();
+ }
+
+ return m_Instance;
+}
+
+//===============================================================================
+/**
+ * Check to see if the Asset is a relative of our asset.
+ * @return true if the inAsset is a parent grandparent...etc. of this asset.
+ */
+bool CSceneViewDropTarget::IsRelative(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ bool theReturn = false;
+
+ qt3dsdm::Qt3DSDMInstanceHandle theThisInstance = GetInstance();
+ // This will check to see if the inAsset is already a parent, grandparent....etc.
+ if (theThisInstance.Valid())
+ theReturn = IsAscendant(theThisInstance, inInstance,
+ g_StudioApp.GetCore()->GetDoc()->GetAssetGraph());
+ return theReturn;
+}
+
+//===============================================================================
+/**
+ * Check to see if the inAsset is our asset.
+ * @param inAsset The Asset to check.
+ * @return true if we are the same.
+ */
+bool CSceneViewDropTarget::IsSelf(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theThisInstance = GetInstance();
+ return (theThisInstance == inInstance);
+}
+
+//===============================================================================
+/**
+ * Set the Drop time for all sources.
+ * @param inDropTime The time to drop the source.
+ */
+void CSceneViewDropTarget::SetDropTime(long inDropTime)
+{
+ m_DropTime = inDropTime;
+}
+
+//===============================================================================
+/**
+ * @return The time that all sources will be droped.
+ */
+long CSceneViewDropTarget::GetDropTime()
+{
+ return m_DropTime;
+}
+
+// Last Sceneview related stuff.
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.h b/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.h
new file mode 100644
index 00000000..f43322c7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/SceneDropTarget.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 __SCENEDROPTARGET_H__
+#define __SCENEDROPTARGET_H__
+
+#include "DropTarget.h"
+
+class CSceneViewDropTarget : public CDropTarget
+{
+protected:
+ long m_DropTime = -1; // The Time a drop happens
+ long m_DropSourceObjectType = 0;
+ int m_DropSourceFileType = 0;
+
+public:
+ CSceneViewDropTarget();
+ bool Accept(CDropSource &inSource) override;
+ bool Drop(CDropSource &inSource) override;
+ qt3dsdm::Qt3DSDMInstanceHandle GetInstance() override;
+
+ bool IsRelative(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ bool IsSelf(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ long GetObjectType() override;
+ void SetDropTime(long inDropTime);
+ long GetDropTime();
+};
+#endif // #ifndef __SCENEDROPTARGET_H__
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.cpp
new file mode 100644
index 00000000..ac4b5bd5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.cpp
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "TimelineDropSource.h"
+#include "FileDropSource.h"
+#include "Dispatch.h"
+#include "DropTarget.h"
+#include "StudioObjectTypes.h"
+#include "HotKeys.h"
+#include "Core.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMSlides.h"
+#include "Bindings/Qt3DSDMTimelineItemBinding.h"
+#include "IDocumentEditor.h"
+#include "ImportUtils.h"
+
+#pragma warning(disable : 4100)
+
+using namespace Q3DStudio;
+
+//===============================================================================
+/**
+ *
+ */
+CTimeLineDropSource::CTimeLineDropSource(long inFlavor, IDragable *inDraggable)
+ : CDropSource(inFlavor, sizeof(inDraggable))
+{
+ m_Copy = true;
+
+ m_Instances = g_StudioApp.GetCore()->GetDoc()->GetSelectedValue().GetSelectedInstances();
+ if (m_Instances.size())
+ m_ObjectType = g_StudioApp.GetCore()
+ ->GetDoc()
+ ->GetStudioSystem()
+ ->GetClientDataModelBridge()
+ ->GetObjectType(m_Instances[0]);
+ else
+ m_ObjectType = OBJTYPE_UNKNOWN;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CTimeLineDropSource::CanMove()
+{
+ return !m_Copy;
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CTimeLineDropSource::CanCopy()
+{
+ bool theReturn = false;
+ // This is here because some Assets can not be copied ( scene, material )
+ theReturn = m_Copy && g_StudioApp.GetCore()->GetDoc()->canCopyObjects(m_Instances);
+
+ return theReturn;
+}
+
+//===============================================================================
+/**
+ *
+ */
+void CTimeLineDropSource::InterpretKeyFlags(long inModifyerKeys)
+{
+ m_Copy = ((inModifyerKeys & CHotKeys::MODIFIER_CONTROL) != 0);
+}
+
+//===============================================================================
+/**
+ *
+ */
+bool CTimeLineDropSource::ValidateTarget(CDropTarget *inTarget)
+{
+ // the only thing we want to do from here is check the type.
+ bool theValidTarget = CStudioObjectTypes::AcceptableParent(
+ (EStudioObjectType)GetObjectType(), (EStudioObjectType)inTarget->GetObjectType());
+
+ for (size_t idx = 0, end = m_Instances.size(); idx < end && theValidTarget; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theHandle(m_Instances[idx]);
+
+ if (theValidTarget && theHandle.Valid()) {
+ theValidTarget &= (!inTarget->IsSelf(theHandle) && !inTarget->IsRelative(theHandle));
+ qt3dsdm::ISlideSystem *theSlideSystem =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theSlide = theSlideSystem->GetAssociatedSlide(theHandle);
+ bool theIsMaster = theSlideSystem->IsMasterSlide(theSlide);
+
+ theValidTarget &= !(theIsMaster && !inTarget->IsMaster());
+ }
+ }
+
+ SetHasValidTarget(theValidTarget);
+
+ return theValidTarget;
+}
+
+using namespace qt3dsdm;
+using namespace Q3DStudio;
+
+inline void Rearrange(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances,
+ Qt3DSDMInstanceHandle inTarget, DocumentEditorInsertType::Enum inInsertType)
+{
+ SCOPED_DOCUMENT_EDITOR(inDoc, QObject::tr("Rearrange Object"))
+ ->RearrangeObjects(inInstances, inTarget, inInsertType);
+}
+
+CCmd *CTimeLineDropSource::GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide)
+{
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ if (CanCopy()) {
+ SCOPED_DOCUMENT_EDITOR(*theDoc, QObject::tr("Duplicate Object"))
+ ->DuplicateInstances(m_Instances, inTarget,
+ ImportUtils::GetInsertTypeForDropType(inDestType));
+ } else {
+ // We can't do the rearrange inline because it deletes a timeline item.
+ // So we will effectively postmessage and do it out of line.
+ theDoc->GetCore()->GetDispatch()->FireOnAsynchronousCommand(
+ std::bind(Rearrange, std::ref(*theDoc), m_Instances, inTarget,
+ ImportUtils::GetInsertTypeForDropType(inDestType)));
+ }
+
+ return nullptr;
+}
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.h b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.h
new file mode 100644
index 00000000..9af1f960
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropSource.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** 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 __TIMELINEDROPSOURCE_H__
+#define __TIMELINEDROPSOURCE_H__
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "StudioApp.h"
+#include "DropSource.h"
+
+//==============================================================================
+// Forward
+//==============================================================================
+class CDropTarget;
+class IDragable;
+
+class CTimeLineDropSource : public CDropSource
+{
+protected:
+ bool m_Copy;
+
+public:
+ CTimeLineDropSource(long inFlavor, IDragable *inDraggable);
+
+ bool CanMove() override;
+ bool CanCopy() override;
+ bool ValidateTarget(CDropTarget *inTarget) override;
+ void InterpretKeyFlags(long inModifyerKeys) override;
+
+ CCmd *GenerateAssetCommand(qt3dsdm::Qt3DSDMInstanceHandle inTarget,
+ EDROPDESTINATION inDestType,
+ qt3dsdm::Qt3DSDMSlideHandle inSlide) override;
+};
+
+#endif // #ifndef __TIMELINEDROPSOURCE_H__
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.cpp b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.cpp
new file mode 100644
index 00000000..f0dc9c85
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.cpp
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "TimelineDropTarget.h"
+#include "StudioApp.h"
+#include "DropSource.h"
+#include "HotKeys.h"
+#include "Core.h"
+#include "Doc.h"
+#include "IDropTargetHelper.h"
+#include "GraphUtils.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Qt3DSDMSlides.h"
+
+// Timeline stuff
+
+//===============================================================================
+/**
+ * This get called on every DragWithin.
+ * Note: the source will validate the target instead of the otherway around.
+ * This is because the DropSource knows how to get information from itself without
+ * creating an asset. This is mainly for DropSources that have a lazy creation idiom.
+ * like files.
+ * @param the DropSource in question.
+ * @return true if the DropSource likes the DropTarget.
+ */
+bool CTimeLineDropTarget::Accept(CDropSource &inSource)
+{
+ bool theDropFlag = inSource.ValidateTarget(this);
+ return theDropFlag;
+}
+
+//===============================================================================
+/**
+ * This is where it actually happens.
+ * Note: At this point either everything should be verified, and setup in the dropsource.
+ * Then the only thing left to do is to get the Assets and move/copy or connect them.
+ * Or the dropsource needs the target to perform the actual drop.
+ * Note that if the Control key is depressed, the start time follows the current view time(
+ *i.e. playhead position )
+ *
+ * @param inSource the Object in question.
+ * @return true if the drop was successful .
+ */
+bool CTimeLineDropTarget::Drop(CDropSource &inSource)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theTargetInstance = GetInstance();
+
+ if (theTargetInstance.Valid()) {
+ CDoc *theDoc = g_StudioApp.GetCore()->GetDoc();
+ qt3dsdm::ISlideSystem *theSlideSystem = theDoc->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theSlide = theDoc->GetActiveSlide();
+ if (!theSlideSystem->IsMasterSlide(theSlide)
+ && (inSource.GetCurrentFlags() & CHotKeys::MODIFIER_ALT)) {
+ if (CanAddToMaster()) {
+ qt3dsdm::Qt3DSDMSlideHandle theMasterSlideHandle =
+ theSlideSystem->GetMasterSlide(theSlide);
+ if (theMasterSlideHandle.Valid())
+ theSlide = theMasterSlideHandle;
+ }
+ }
+ CCmd *theCmd = inSource.GenerateAssetCommand(theTargetInstance, m_Destination, theSlide);
+ if (theCmd)
+ g_StudioApp.GetCore()->ExecuteCommand(theCmd);
+ }
+
+ return true;
+}
+
+//===============================================================================
+/**
+ * This will get the objec ttype from the Asset.
+ * Note: The asset can change all of the time, so i always ask the asset for its type.
+ * @return the Studio object type.
+ */
+long CTimeLineDropTarget::GetObjectType()
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theTargetInstance = GetTargetInstance();
+ if (theTargetInstance.Valid()) {
+ CClientDataModelBridge *theBridge =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ return theBridge->GetObjectType(theTargetInstance);
+ }
+
+ return m_ObjectType;
+}
+
+//===============================================================================
+/**
+ * Check to see if the Asset is a relative of our asset.
+ * @return true if the inAsset is a parent grandparent...etc. of this asset.
+ */
+bool CTimeLineDropTarget::IsRelative(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ bool theReturn = false; ///< Default return value.
+ qt3dsdm::Qt3DSDMInstanceHandle theThisInstance = GetInstance();
+
+ // This will check to see if the inAsset is already some sort of parent grandparent....etc.
+ if (theThisInstance.Valid())
+ theReturn = IsAscendant(theThisInstance, inInstance,
+ g_StudioApp.GetCore()->GetDoc()->GetAssetGraph());
+
+ return theReturn;
+}
+
+//===============================================================================
+/**
+ * Check to see if the inAsset is our asset.
+ * @param inAsset The Asset to check.
+ * @return true if we are the same.
+ */
+bool CTimeLineDropTarget::IsSelf(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theThisInstance = GetInstance();
+ // true if self.....
+ return (theThisInstance == inInstance);
+}
+
+//===============================================================================
+/**
+ * This method is used to detirmine validity for dropping on Master items.
+ * We do not allow Master items to be dropped on non-master targets because
+ * it does not make any hierarchical sense. Also checks for NULL and Scene object.
+ * (The scene object reports as a master object so that we can re-arrange layers (On the master
+ *slide).)
+ * Changed checking for scene to checking for root object, which may be a component. This
+ * would allow for the root component (as in edit component) to re-arrange its children, even if
+ *either
+ * party is not a master.
+ * @param inAsset The Asset to check.
+ * @return true if we are the same.
+ */
+bool CTimeLineDropTarget::IsMaster()
+{
+ if (!m_Instance.Valid())
+ return true;
+ else if (m_Instance == g_StudioApp.GetCore()->GetDoc()->GetActiveRootInstance())
+ return true;
+ else {
+ qt3dsdm::ISlideSystem *theSlideSystem =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetSlideSystem();
+ qt3dsdm::Qt3DSDMSlideHandle theSlide = theSlideSystem->GetAssociatedSlide(m_Instance);
+ return theSlideSystem->IsMasterSlide(theSlide);
+ }
+}
+
+void CTimeLineDropTarget::SetDestination(EDROPDESTINATION inDestination)
+{
+ m_Destination = inDestination;
+}
+
+EDROPDESTINATION CTimeLineDropTarget::GetDestination() const
+{
+ return m_Destination;
+}
+
+//===============================================================================
+/**
+ * Figure out the destination (parent) asset that the drop is to occur.
+ * In the case that the drop occurs ON this asset, then the to-be-dropped asset becomes a child
+ *of m_Asset
+ * Otherwise, this m_Asset ends up being a sibling
+ */
+qt3dsdm::Qt3DSDMInstanceHandle CTimeLineDropTarget::GetTargetInstance()
+{
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance();
+ if (!theInstance.Valid())
+ return 0;
+
+ CClientDataModelBridge *theBridge =
+ g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge();
+ bool theIsActiveComponent = false;
+ bool theIsComponent = (theBridge->GetObjectType(theInstance) == OBJTYPE_COMPONENT);
+ if (theIsComponent)
+ theIsActiveComponent = theBridge->IsActiveComponent(theInstance);
+
+ // If the drop destination is ON, it will be valid if this is not a component or it's in the
+ // component timeline
+ if (m_Destination == EDROPDESTINATION_ON) {
+ if (!theIsComponent || theIsActiveComponent)
+ return theInstance;
+ else
+ return 0;
+ }
+
+ // if target is a component, and we want to insert it above/below, and we are viewing this
+ // component, then it's an invalid operation
+ // thus set the target to 0.
+ if (theIsActiveComponent)
+ return 0;
+
+ return theBridge->GetParentInstance(theInstance);
+}
+
+// LASTTIMELINE RELATED CODE
diff --git a/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.h b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.h
new file mode 100644
index 00000000..6f7f75b5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/DragAndDrop/TimelineDropTarget.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** 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 __TIMELINEDROPTARGET_H__
+#define __TIMELINEDROPTARGET_H__
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "DropTarget.h"
+#include "DropSource.h"
+
+class CControl;
+class CDropSource;
+
+//==============================================================================
+/**
+ * @class
+ * @brief LEONARD.PONCE needs to enter a brief description here.
+ *
+ * LEONARD.PONCE needs to enter a long description here.
+ */
+class CTimeLineDropTarget : public CDropTarget
+{
+
+public:
+ CTimeLineDropTarget()
+ : m_Destination(EDROPDESTINATION_ON)
+ , m_InsertionMarkedRow(nullptr)
+ , m_InsertionMarkedIndent(0)
+ {
+ }
+ bool Accept(CDropSource &inSource) override;
+ bool Drop(CDropSource &inSource) override;
+ long GetObjectType() override;
+
+ bool IsRelative(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ bool IsSelf(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override;
+ bool IsMaster() override;
+
+ void SetDestination(EDROPDESTINATION inDestination);
+ EDROPDESTINATION GetDestination() const;
+
+ // Return info where the insertion markers is supposed to be drawn at.
+ CControl *GetInsertionMarkerRow() const { return m_InsertionMarkedRow; }
+ void SetInsertionMarkerRow(CControl *inControl) { m_InsertionMarkedRow = inControl; }
+ long GetInsertionMarkerIndent() const { return m_InsertionMarkedIndent; }
+ void SetInsertionMarkerIndent(long inIndent) { m_InsertionMarkedIndent = inIndent; }
+
+protected:
+ qt3dsdm::Qt3DSDMInstanceHandle GetTargetInstance();
+
+protected:
+ EDROPDESTINATION m_Destination;
+ CControl *m_InsertionMarkedRow;
+ long m_InsertionMarkedIndent;
+};
+
+#endif // #ifndef __TIMELINEDROPTARGET_H__
diff --git a/src/Authoring/Qt3DStudio/Info.plist b/src/Authoring/Qt3DStudio/Info.plist
new file mode 100644
index 00000000..5dcc69d1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Info.plist
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>UTImportedTypeDeclarations</key>
+ <array>
+ <dict>
+ <key>UTTypeDescription</key>
+ <string>Qt 3D Studio Application File</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.source-code</string>
+ </array>
+ <key>UTTypeIdentifier</key>
+ <string>org.qt-project.uia</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>uia</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeDescription</key>
+ <string>Qt 3D Studio Presentation File</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.source-code</string>
+ </array>
+ <key>UTTypeIdentifier</key>
+ <string>org.qt-project.uip</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>uip</string>
+ </array>
+ </dict>
+ </dict>
+ </array>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>org.qt-project.uip</string>
+ <string>org.qt-project.uia</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Default</string>
+ </dict>
+ </array>
+ <key>NSHumanReadableCopyright</key>
+ <string>(C) 2019 The Qt Company Ltd</string>
+ <key>CFBundleExecutable</key>
+ <string>Qt3DStudio</string>
+ <key>CFBundleIconFile</key>
+ <string>studio.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.qt-project.qt3dstudio</string>
+ <key>CFBundleVersion</key>
+ <string>2.4.0</string>
+ <key>CFBundleShortVersionString</key>
+ <string>2.4</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/src/Authoring/Qt3DStudio/MainFrm.cpp b/src/Authoring/Qt3DStudio/MainFrm.cpp
new file mode 100644
index 00000000..3f2f946f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/MainFrm.cpp
@@ -0,0 +1,1968 @@
+/****************************************************************************
+**
+** 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 "MainFrm.h"
+#include "ui_MainFrm.h"
+#include "StudioConst.h"
+#include "SceneView.h"
+#include "StudioApp.h"
+#include "IKeyframesManager.h"
+#include "Dialogs.h"
+#include "StudioPreferencesPropSheet.h"
+#include "StudioPreferences.h"
+#include "HotKeys.h"
+#include "RecentItems.h"
+#include "PaletteManager.h"
+#include "Core.h"
+#include "ITickTock.h"
+#include "IStudioRenderer.h"
+#include "DataInputListDlg.h"
+#include "StudioTutorialWidget.h"
+#include "remotedeploymentsender.h"
+#include "InspectorControlView.h"
+#include "TimelineWidget.h"
+#include "ProjectView.h"
+#include "RowTree.h"
+#include "WidgetControl.h"
+#include "SlideView.h"
+#include "FilterVariantsDlg.h"
+#include "PreviewHelper.h"
+#include "StudioClipboard.h"
+
+#include <QtGui/qevent.h>
+#include <QtGui/qdesktopservices.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qprocess.h>
+#include <QtGui/qfontdatabase.h>
+
+// Constants
+const long PLAYBACK_TIMER_TIMEOUT = 10; // milliseconds
+
+CMainFrame::CMainFrame()
+ : m_ui(new Ui::MainFrame)
+ , m_remoteDeploymentSender(new RemoteDeploymentSender(this))
+ , m_updateUITimer(new QTimer)
+ , m_playbackTimer(new QTimer)
+{
+ m_ui->setupUi(this);
+
+ // allow styling this action
+ m_ui->m_PlaybackToolbar->widgetForAction(m_ui->actionFilterVariants)
+ ->setObjectName("actionFilterVariants");
+
+ // Register TitilliumWeb as application font in case user doesn't have it already installed
+ QFontDatabase::addApplicationFont(QStringLiteral(":/TitilliumWeb-Light.ttf"));
+ QFontDatabase::addApplicationFont(QStringLiteral(":/TitilliumWeb-Regular.ttf"));
+
+ OnCreate();
+
+ g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this);
+ g_StudioApp.GetCore()->GetDispatch()->AddFileOpenListener(this);
+ g_StudioApp.GetCore()->GetDispatch()->AddClientPlayChangeListener(this);
+ g_StudioApp.setupTimer(WM_STUDIO_TIMER, this);
+
+ // File Menu
+ connect(m_ui->action_New_Project, &QAction::triggered, this, &CMainFrame::OnProjectNew);
+ connect(m_ui->action_New_Presentation, &QAction::triggered, this, &CMainFrame::OnFileNew);
+ connect(m_ui->action_Open, &QAction::triggered, this, &CMainFrame::OnFileOpen);
+ connect(m_ui->action_Save, &QAction::triggered, this, &CMainFrame::OnFileSave);
+ connect(m_ui->action_Save_Project_As, &QAction::triggered, this, &CMainFrame::onProjectSaveAs);
+ connect(m_ui->action_Duplicate_Presentation, &QAction::triggered,
+ this, &CMainFrame::onDuplicatePresentation);
+ connect(m_ui->action_Revert, &QAction::triggered, this, &CMainFrame::OnFileRevert);
+ connect(m_ui->actionImportAssets, &QAction::triggered, this, &CMainFrame::OnFileImportAssets);
+ connect(m_ui->actionData_Inputs, &QAction::triggered, this, &CMainFrame::OnFileDataInputs);
+ connect(m_ui->actionData_InputsGenerate, &QAction::triggered, this,
+ &CMainFrame::OnFileGenerateDataInputCode);
+ connect(m_ui->action_Connect_to_Device, &QAction::triggered, this,
+ &CMainFrame::OnFileConnectToDevice);
+ m_recentItems.reset(new CRecentItems(m_ui->menuRecent_Projects));
+ connect(m_recentItems.data(), &CRecentItems::openRecent, this, &CMainFrame::OnFileOpenRecent);
+ connect(m_ui->action_Exit, &QAction::triggered, this, &CMainFrame::close);
+
+ // Edit Menu
+ connect(m_ui->action_Undo, &QAction::triggered, this, &CMainFrame::OnEditUndo);
+ connect(m_ui->action_Redo, &QAction::triggered, this, &CMainFrame::OnEditRedo);
+// connect(m_ui->actionRepeat, &QAction::triggered, this, &CMainFrame::onEditRepeat); // TODO: Implement
+ connect(m_ui->action_Cut, &QAction::triggered, this, &CMainFrame::OnEditCut);
+ connect(m_ui->action_Copy, &QAction::triggered, this, &CMainFrame::OnEditCopy);
+ connect(m_ui->action_Paste, &QAction::triggered, this, &CMainFrame::OnEditPaste);
+ connect(m_ui->actionPaste_to_Master_Slide, &QAction::triggered,
+ this, &CMainFrame::onEditPasteToMaster);
+ connect(m_ui->action_Duplicate_Object, &QAction::triggered, this, &CMainFrame::OnEditDuplicate);
+ connect(m_ui->actionDelete, &QAction::triggered, this, &CMainFrame::onEditDelete);
+ connect(m_ui->actionGroup, &QAction::triggered, this, &CMainFrame::onEditGroup);
+// connect(m_ui->actionParent, &QAction::triggered, this, &CMainFrame::onEditParent); // TODO: Implement
+// connect(m_ui->actionUnparent, &QAction::triggered, this, &CMainFrame::onEditUnparent); // TODO: Implement
+ connect(m_ui->actionStudio_Preferences, &QAction::triggered,
+ this, &CMainFrame::OnEditApplicationPreferences);
+ connect(m_ui->actionPresentation_Settings, &QAction::triggered,
+ this, &CMainFrame::OnEditPresentationPreferences);
+ connect(m_ui->menu_Edit, &QMenu::aboutToShow, [this]() {
+ // macOS doesn't block menubar while startup dialog is being shown, and that causes a
+ // crash on aboutToShow if it's called before everything is set
+ if (m_ui->menu_Edit->isEnabled()) {
+ QString type = g_StudioApp.getDuplicateType();
+ QString label = tr("Duplicate %1").arg(type);
+ m_ui->action_Duplicate_Object->setText(label);
+ m_ui->action_Duplicate_Object->setEnabled(!type.isEmpty());
+
+ type = g_StudioApp.getDeleteType();
+ label = tr("Delete %1").arg(type);
+ m_ui->actionDelete->setText(label);
+ m_ui->actionDelete->setEnabled(!type.isEmpty());
+
+ if (g_StudioApp.canUngroupSelectedObjects()) {
+ m_ui->actionGroup->setText(tr("Ungroup Objects"));
+ m_ui->actionGroup->setEnabled(true);
+ } else {
+ m_ui->actionGroup->setText(tr("Group Objects"));
+ m_ui->actionGroup->setEnabled(g_StudioApp.canGroupSelectedObjects());
+ }
+ }
+ });
+ connect(m_ui->menu_Edit, &QMenu::aboutToHide, [this]() {
+ // Enable potentially disabled items so hotkeys will work
+ m_ui->action_Duplicate_Object->setEnabled(true);
+ m_ui->actionDelete->setEnabled(true);
+ m_ui->actionGroup->setEnabled(true);
+ });
+
+ // View Menu
+ connect(m_ui->actionReset_layout, &QAction::triggered, this, &CMainFrame::onViewResetLayout);
+ connect(m_ui->actionFit_Selected, &QAction::triggered,
+ this, &CMainFrame::OnEditCameraZoomExtent);
+// connect(m_ui->actionFit_all, &QAction::triggered, this, &CMainFrame::onViewFitAll); // TODO: Implement
+ connect(m_ui->actionToggle_hide_unhide_selected, &QAction::triggered,
+ []() { g_StudioApp.toggleEyeball(); });
+// connect(m_ui->actionToggle_hide_unhide_unselected, &QAction::triggered,
+// []() { }); // TODO: Implement?
+ connect(m_ui->actionAction, &QAction::triggered, this, &CMainFrame::OnViewAction);
+ connect(m_ui->actionBasic_Objects, &QAction::triggered, this, &CMainFrame::OnViewBasicObjects);
+ connect(m_ui->actionInspector, &QAction::triggered, this, &CMainFrame::OnViewInspector);
+ connect(m_ui->actionProject, &QAction::triggered, this, &CMainFrame::OnViewProject);
+ connect(m_ui->actionSlide, &QAction::triggered, this, &CMainFrame::OnViewSlide);
+ connect(m_ui->actionTimeline, &QAction::triggered, this, &CMainFrame::OnViewTimeline);
+ connect(m_ui->actionSceneCamera, &QAction::triggered, this, &CMainFrame::onViewSceneCamera);
+ connect(m_ui->actionBounding_Boxes, &QAction::triggered,
+ this, &CMainFrame::OnViewBoundingBoxes);
+ connect(m_ui->actionPivot_Point, &QAction::triggered, this, &CMainFrame::OnViewPivotPoint);
+ connect(m_ui->actionWireframe, &QAction::triggered, this, &CMainFrame::OnViewWireframe);
+ connect(m_ui->actionTooltips, &QAction::triggered, this, &CMainFrame::OnViewTooltips);
+ connect(m_ui->actionCamera_Preview, &QAction::triggered, this, &CMainFrame::OnShowEditPreview);
+ connect(m_ui->actionEdit_Lighting, &QAction::triggered, this,
+ &CMainFrame::OnEditViewLightingEnabled);
+// connect(m_ui->actionFind, &QAction::triggered, this, &CMainFrame::onViewFind); // TODO: Implement
+
+ // Timeline Menu
+ connect(m_ui->actionSet_Changed_Keyframes, &QAction::triggered,
+ this, &CMainFrame::OnTimelineSetChangedKeyframe);
+ connect(m_ui->actionDelete_Selected_Keyframe_s, &QAction::triggered,
+ [](){ g_StudioApp.DeleteSelectedKeys(); });
+ connect(m_ui->actionSet_Interpolation, &QAction::triggered,
+ this, &CMainFrame::OnTimelineSetInterpolation);
+ connect(m_ui->actionChange_Time_Bar_Color, &QAction::triggered,
+ this, &CMainFrame::OnTimelineSetTimeBarColor);
+ connect(m_ui->actionAutoset_Keyframes, &QAction::triggered,
+ this, &CMainFrame::OnToolAutosetkeys);
+
+ // Help Menu
+ connect(m_ui->action_Reference_Manual, &QAction::triggered, this, &CMainFrame::OnHelpIndex);
+ connect(m_ui->action_Visit_Qt_Web_Site, &QAction::triggered, this, &CMainFrame::OnHelpVisitQt);
+ connect(m_ui->action_About_Qt_3D_Studio, &QAction::triggered,
+ []() { g_StudioApp.onAppAbout(); });
+ connect(m_ui->action_Open_Tutorial, &QAction::triggered, this, &CMainFrame::OnHelpOpenTutorial);
+
+ // Selection toolbar
+ connect(m_ui->actionItem_Select_Tool, &QAction::triggered,
+ m_sceneView.data(), &CSceneView::onToolItemSelection);
+ connect(m_ui->actionGroup_Select_Tool, &QAction::triggered,
+ m_sceneView.data(), &CSceneView::onToolGroupSelection);
+
+ // Playback toolbar
+ connect(m_ui->actionPreview, &QAction::triggered,
+ this, &CMainFrame::OnPlaybackPreviewOpenGLRuntime);
+ connect(m_ui->actionFilterVariants, &QAction::triggered, this, &CMainFrame::onFilterVariants);
+
+ connect(m_ui->actionRemote_Preview, &QAction::triggered,
+ this, &CMainFrame::OnPlaybackPreviewRemote);
+
+ // Only show Qt3D runtime preview if we have appropriate viewer and it's enabled
+ if (CStudioPreferences::IsLegacyViewerActive()
+ && QFileInfo(CPreviewHelper::getViewerFilePath(QStringLiteral("q3dsviewer"))).exists()) {
+ connect(m_ui->actionPreviewQt3DRuntime, &QAction::triggered,
+ this, &CMainFrame::OnPlaybackPreviewQt3DRuntime);
+ m_ui->actionPreviewQt3DRuntime->setVisible(true);
+ } else {
+ m_ui->actionPreviewQt3DRuntime->setVisible(false);
+ }
+
+ // Tool mode toolbar
+ connect(m_ui->actionPosition_Tool, &QAction::triggered, this,
+ std::bind(&CMainFrame::onTransformToolChanged, this, STUDIO_TOOLMODE_MOVE));
+ connect(m_ui->actionRotation_Tool, &QAction::triggered, this,
+ std::bind(&CMainFrame::onTransformToolChanged, this, STUDIO_TOOLMODE_ROTATE));
+ connect(m_ui->actionScale_Tool, &QAction::triggered, this,
+ std::bind(&CMainFrame::onTransformToolChanged, this, STUDIO_TOOLMODE_SCALE));
+ connect(m_ui->actionLocal_Global_Manipulators, &QAction::triggered,
+ this, &CMainFrame::OnToolGlobalManipulators);
+
+ // Edit Camera toolbar
+#if 0 // TODO: Disabled until UX decision is made if these buttons are needed at all or not
+ connect(m_ui->actionPan_Tool, &QAction::triggered, this, &CMainFrame::OnEditCameraPan);
+ connect(m_ui->actionOrbit_Tool, &QAction::triggered, this, &CMainFrame::OnEditCameraRotate);
+ connect(m_ui->actionZoom_Tool, &QAction::triggered, this, &CMainFrame::OnEditCameraZoom);
+#endif
+ connect(m_ui->actionShading_Mode, &QAction::triggered, this, &CMainFrame::OnEditViewFillMode);
+ connect(m_ui->actionRulers_Guides, &QAction::triggered, this, &CMainFrame::OnViewGuidesRulers);
+ connect(m_ui->actionClear_Guides, &QAction::triggered, this, &CMainFrame::OnClearGuides);
+ connect(m_ui->actionLock_Guides, &QAction::triggered, this, &CMainFrame::OnLockGuides);
+
+ // Others
+ connect(m_remoteDeploymentSender.data(), &RemoteDeploymentSender::connectionChanged,
+ this, &CMainFrame::OnConnectionChanged);
+
+ // Hide unimplemented menu items
+ m_ui->actionRepeat->setVisible(false);
+ m_ui->actionParent->setVisible(false);
+ m_ui->actionUnparent->setVisible(false);
+ m_ui->actionFit_all->setVisible(false);
+ m_ui->actionToggle_hide_unhide_unselected->setVisible(false);
+ m_ui->actionFind->setVisible(false);
+
+ // TODO: better solution?
+ m_updateUITimer->start(500);
+ connect(m_updateUITimer.data(), &QTimer::timeout, [&]() {
+ if (QApplication::activeWindow() != this)
+ return;
+
+ OnUpdateFileSave();
+ OnUpdateEditUndo();
+ OnUpdateEditRedo();
+ OnUpdateEditCopy();
+ OnUpdateEditCut();
+ OnUpdateToolAutosetkeys();
+ OnUpdateEditPaste();
+ OnUpdateTimelineDeleteSelectedKeyframes();
+ OnUpdateTimelineSetInterpolation();
+ OnUpdateTimelineSetTimeBarColor();
+ OnUpdateViewBoundingBoxes();
+ OnUpdateViewPivotPoint();
+ OnUpdateViewWireframe();
+ OnUpdateViewTooltips();
+ OnUpdateViewTimeline();
+ onUpdateViewSceneCamera();
+ OnUpdateViewInspector();
+ OnUpdateViewAction();
+ OnUpdateViewBasicObjects();
+ OnUpdateViewProject();
+ OnUpdateViewSlide();
+ OnUpdateHelpIndex();
+ OnUpdatePlaybackPlay();
+ OnUpdatePlaybackPreview();
+ OnUpdateToolGlobalManipulators();
+ OnUpdateToolGroupSelection();
+ OnUpdateToolItemSelection();
+ OnUpdateCameraZoomExtentAndAuthorZoom();
+ OnUpdateEditCameraPan();
+ OnUpdateEditCameraRotate();
+ OnUpdateEditCameraZoom();
+ OnUpdateEditViewFillMode();
+ OnUpdateViewGuidesRulers();
+ OnUpdateClearGuides();
+ OnUpdateLockGuides();
+ OnUpdateCameraPreview();
+ OnUpdateEditViewLightingEnabled();
+ });
+
+ m_playbackTimer->setInterval(PLAYBACK_TIMER_TIMEOUT);
+ connect(m_playbackTimer.data(), &QTimer::timeout, this, &CMainFrame::onPlaybackTimeout);
+ qApp->installEventFilter(this);
+}
+
+CMainFrame::~CMainFrame()
+{
+ qApp->removeEventFilter(this);
+ m_playbackTimer->stop();
+ m_updateUITimer->stop();
+}
+
+// Timer callback
+void CMainFrame::onPlaybackTimeout()
+{
+ // Timer callback that drives playback
+ Q_ASSERT(&g_StudioApp);
+ g_StudioApp.GetCore()->GetDoc()->ClientStep();
+}
+
+/**
+ * Called when the main frame is actually created. Sets up tool bars and default
+ * views.
+ */
+void CMainFrame::OnCreate()
+{
+ m_sceneView.reset(new CSceneView(this));
+ connect(m_sceneView.data(), &CSceneView::toolChanged, this, &CMainFrame::OnUpdateToolChange);
+
+ m_sceneView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ // tell the edit camera bar about this scene view
+ m_ui->m_EditCamerasBar->setSceneView(m_sceneView.data());
+
+ // Newly launched, the file dialog for open and import should default to more recent
+ // opened/imported
+ CDialogs *theDialogs = g_StudioApp.GetDialogs();
+ // this must NOT be in 'command line' mode
+ if (theDialogs) {
+ QString theMostRecentOpen;
+ if (m_recentItems && m_recentItems->GetItemCount() > 0)
+ theMostRecentOpen = m_recentItems->GetItem(0);
+ if (theMostRecentOpen.isEmpty()) // default to exe
+ theMostRecentOpen = Qt3DSFile::GetApplicationDirectory();
+
+ theDialogs->ResetSettings(theMostRecentOpen);
+ }
+
+ // create the variants filtering dialog, singleShot is used so that actionGeom is calculated
+ // correctly
+ QTimer::singleShot(0, this, [&] {
+ QRect actionGeom = m_ui->m_PlaybackToolbar->actionGeometry(m_ui->actionFilterVariants);
+ auto *actionWidget = m_ui->m_PlaybackToolbar->widgetForAction(m_ui->actionFilterVariants);
+ m_filterVariantsDlg.reset(new FilterVariantsDlg(this, m_ui->actionFilterVariants,
+ actionGeom.width(), actionWidget));
+ });
+
+ // Create the view manager
+ m_paletteManager.reset(new CPaletteManager(this));
+
+ // Remove basic toolbar (open, save, undo/redo, etc.)
+ // Kept in ui form in case it is going to be added back later on.
+ delete m_ui->toolBar;
+
+ // Disable toolbars and menus until we have a presentation
+ m_ui->m_ClientToolsBar->setEnabled(false);
+ m_ui->m_EditCamerasBar->setEnabled(false);
+ m_ui->m_PlaybackToolbar->setEnabled(false);
+ m_ui->menu_Edit->setEnabled(false);
+ m_ui->menu_Timeline->setEnabled(false);
+ m_ui->menu_View->setEnabled(false);
+ m_ui->action_Save_Project_As->setEnabled(false);
+ m_ui->action_Duplicate_Presentation->setEnabled(false);
+ m_ui->action_Connect_to_Device->setEnabled(false);
+ m_ui->action_Revert->setEnabled(false);
+ m_ui->actionImportAssets->setEnabled(false);
+ m_ui->actionRemote_Preview->setEnabled(false);
+ m_ui->action_New_Presentation->setEnabled(false);
+ m_ui->actionData_Inputs->setEnabled(false);
+ m_ui->actionData_InputsGenerate->setEnabled(false);
+#if 1 // TODO: Hidden until UX decision is made if these buttons are needed at all or not
+ m_ui->actionPan_Tool->setVisible(false);
+ m_ui->actionOrbit_Tool->setVisible(false);
+ m_ui->actionZoom_Tool->setVisible(false);
+#endif
+
+ // Show a message about opening or creating a presentation
+ m_sceneView.data()->setVisible(false);
+ setCentralWidget(m_ui->infoText);
+}
+
+/**
+ * Called when a new presenation is created. We have to wait to associate the
+ * scene object with the scene view until this point, because the scene object
+ * does not exist until this function gets called.
+ */
+void CMainFrame::OnNewPresentation()
+{
+ // Make sure scene is visible
+ showScene();
+
+ // Associate the scene object with the scene view
+ m_ui->m_EditCamerasBar->setupCameras();
+ // Enable dockables, toolbars, and menus
+ m_paletteManager->EnablePalettes();
+ m_ui->m_ClientToolsBar->setEnabled(true);
+ m_ui->m_EditCamerasBar->setEnabled(true);
+ m_ui->m_PlaybackToolbar->setEnabled(true);
+ m_ui->menu_Edit->setEnabled(true);
+ m_ui->menu_Timeline->setEnabled(true);
+ m_ui->menu_View->setEnabled(true);
+ m_ui->action_Save_Project_As->setEnabled(true);
+ m_ui->action_Duplicate_Presentation->setEnabled(true);
+ m_ui->action_Connect_to_Device->setEnabled(true);
+ m_ui->action_Revert->setEnabled(true);
+ m_ui->actionImportAssets->setEnabled(true);
+ m_ui->action_New_Presentation->setEnabled(true);
+ m_ui->actionData_Inputs->setEnabled(true);
+ m_ui->actionData_InputsGenerate->setEnabled(true);
+
+ // Clear data input list and sub-presentation list
+ g_StudioApp.m_subpresentations.clear();
+ g_StudioApp.m_dataInputDialogItems.clear();
+
+ // reset the variants filter and preview button's icon
+ m_filterVariantsDlg->clearFilter();
+ updateToolbarVariantsIcons(false);
+}
+
+/**
+ * Called when the current presentation is being closed.
+ * This will close all the editor windows that are open.
+ */
+void CMainFrame::OnClosingPresentation()
+{
+}
+
+/**
+ * Handles the Timeline | Set Interpolation menu item
+ * This is a temporary method that will display the Set Interpolation dialog.
+ */
+void CMainFrame::OnTimelineSetInterpolation()
+{
+ g_StudioApp.GetCore()->GetDoc()->SetKeyframeInterpolation();
+}
+
+/**
+ * OnEditRedo: calls handleRedoOperation
+ */
+void CMainFrame::OnEditRedo()
+{
+ g_StudioApp.GetCore()->GetCmdStack()->Redo();
+}
+
+/**
+ * OnEditUndo: calls HandleUndoOperation
+ */
+void CMainFrame::OnEditUndo()
+{
+ g_StudioApp.GetCore()->GetCmdStack()->Undo();
+}
+
+/**
+ * OnUpdateEditUndo: Handler for ID_EDIT_UNDO message
+ *
+ * @param pCmndUI The UI element that generated the message
+ */
+void CMainFrame::OnUpdateEditUndo()
+{
+ const QString undoDescription = QObject::tr("Undo %1\tCtrl+Z").arg(
+ g_StudioApp.GetCore()->GetCmdStack()->GetUndoDescription());
+ m_ui->action_Undo->setEnabled(g_StudioApp.CanUndo());
+ m_ui->action_Undo->setText(undoDescription);
+}
+
+/**
+ * OnUpdateEditRedo: handles the message ID_EDIT_REDO
+ *
+ * @param pCmndUI The UI element that generated the message
+ */
+void CMainFrame::OnUpdateEditRedo()
+{
+ const QString redoDescription = QObject::tr("Redo %1\tCtrl+Y").arg(
+ g_StudioApp.GetCore()->GetCmdStack()->GetRedoDescription());
+ m_ui->action_Redo->setEnabled(g_StudioApp.CanRedo());
+ m_ui->action_Redo->setText(redoDescription);
+}
+
+/**
+ * OnEditCopy: Handles the Copy message
+ *
+ * Tells the doc to copy the selected keyframes.
+ */
+void CMainFrame::OnEditCopy()
+{
+ g_StudioApp.OnCopy();
+}
+
+/**
+ * OnUpdateEditCopy: Handle the update UI command for the copy button and menu item
+ *
+ * If there are keyframes selected, the button is enabled, otherwise, it is
+ * disabled.
+ *
+ * @param pCmndUI The UI element that generated the message
+ */
+void CMainFrame::OnUpdateEditCopy()
+{
+ // TODO: Actions cannot currently be copied/cut/pasted via main edit menu
+ // ActionView handles action copy/cut/paste internally
+ if (g_StudioApp.CanCopy()) {
+ QString theDescription = tr("Copy %1\tCtrl+C").arg(g_StudioApp.GetCopyType());
+
+ m_ui->action_Copy->setText(theDescription);
+ m_ui->action_Copy->setEnabled(true);
+ } else {
+ m_ui->action_Copy->setEnabled(false);
+ }
+}
+
+/**
+ * OnEditCut: Handles the Cut message
+ *
+ * Tells the doc to cut the selected keyframes.
+ */
+void CMainFrame::OnEditCut()
+{
+ g_StudioApp.OnCut();
+}
+
+/**
+ * OnUpdateEditCut: Handle the update UI command for the cut button and menu item
+ *
+ * If there are keyframes selected, the button is enabled, otherwise, it is
+ * disabled.
+ *
+ * @param pCmndUI The UI element that generated the message
+ */
+void CMainFrame::OnUpdateEditCut()
+{
+ if (g_StudioApp.CanCut()) {
+ QString theDescription = tr("Cut %1\tCtrl+X").arg(g_StudioApp.GetCopyType());
+
+ m_ui->action_Cut->setText(theDescription);
+ m_ui->action_Cut->setEnabled(true);
+ } else {
+ m_ui->action_Cut->setEnabled(false);
+ }
+}
+
+/**
+ * OnEditPaste: Handles the Paste command
+ *
+ * Tells the doc to paste the copied keyframes at the current playhead time, on
+ * the currently selected object.
+ */
+void CMainFrame::OnEditPaste()
+{
+ g_StudioApp.OnPaste();
+}
+
+void CMainFrame::onEditPasteToMaster()
+{
+ g_StudioApp.GetCore()->GetDoc()->HandleMasterPaste();
+}
+
+/**
+ * OnUpdateEditPaste: Handle the update UI command for the paste button and menu item
+ *
+ * If there we can perform a keyframe paste, the button is enabled, otherwise, it is
+ * disabled.
+ *
+ * @param pCmndUI The UI element that generated the message
+ */
+void CMainFrame::OnUpdateEditPaste()
+{
+ if (g_StudioApp.CanPaste()) {
+ QString theUndoDescription = tr("Paste %1\tCtrl+V").arg(g_StudioApp.GetPasteType());
+
+ m_ui->action_Paste->setText(theUndoDescription);
+
+ m_ui->action_Paste->setEnabled(true);
+ m_ui->actionPaste_to_Master_Slide->setEnabled(true);
+ } else {
+ m_ui->action_Paste->setEnabled(false);
+ m_ui->actionPaste_to_Master_Slide->setEnabled(false);
+ }
+}
+
+/**
+ * Called when a tool mode changes from a modifier key
+ */
+void CMainFrame::OnUpdateToolChange()
+{
+ long theSelectMode = g_StudioApp.GetSelectMode();
+ m_ui->actionGroup_Select_Tool->setChecked(theSelectMode == STUDIO_SELECTMODE_GROUP);
+ m_ui->actionItem_Select_Tool->setChecked(theSelectMode == STUDIO_SELECTMODE_ENTITY);
+
+ // See what tool mode we are in and change checks accordingly
+ long theToolMode = g_StudioApp.GetToolMode();
+ m_ui->actionPosition_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_MOVE);
+ m_ui->actionRotation_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_ROTATE);
+ m_ui->actionScale_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_SCALE);
+ m_ui->actionLocal_Global_Manipulators->setChecked(g_StudioApp.GetManipulationMode()
+ == StudioManipulationModes::Global);
+
+#if 0 // TODO: Disabled until UX decision is made if these buttons are needed at all or not
+ m_ui->actionPan_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_CAMERA_PAN);
+ m_ui->actionOrbit_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_CAMERA_ROTATE);
+ m_ui->actionZoom_Tool->setChecked(theToolMode == STUDIO_TOOLMODE_CAMERA_ZOOM);
+#endif
+}
+
+/**
+ * OnTimelineSettimebarcolor: Handles the ID_TIMELINE_SETTIMEBARCOLOR message.
+ *
+ * Called when the user clicks on Timeline->Change Time Bar Color. Changes
+ * the currently selected timebar's color.
+ */
+void CMainFrame::OnTimelineSetTimeBarColor()
+{
+ getTimelineWidget()->openBarColorDialog();
+}
+
+/**
+ * OnUpdateTimelineSetTimeBarColor: Handles the update UI message for the
+ * "Change Time Bar Color" menu item.
+ *
+ * If the currently selected object is an item in the timeline and it has a
+ * time bar, this menu item is enabled. Otherwise, the menu item is disabled.
+ *
+ * @param pCmndUI Pointer to the ui object that generated this update message.
+ */
+void CMainFrame::OnUpdateTimelineSetTimeBarColor()
+{
+ m_ui->actionChange_Time_Bar_Color->setEnabled(g_StudioApp.CanChangeTimebarColor());
+}
+
+/**
+ * OnTimelineSetChangedKeyframe: Handles the ID_TIMELINE_SETCHANGEDKEYFRAME message.
+ *
+ * Calls the StudioDoc handler to insert keyframes for animatable properties that
+ * have changed.
+ */
+void CMainFrame::OnTimelineSetChangedKeyframe()
+{
+ g_StudioApp.HandleSetChangedKeys();
+}
+
+/**
+ * OnUpdateTimelineDeleteSelectedKeyframes: Handles the update UI message for
+ * the "Delete Selected Keyframe(s)" message.
+ *
+ * If there are currently keyframes selected, the menu item is enabled. Otherwise,
+ * the menu item is disabled.
+ *
+ * @param pCmdUI The UI element that generated this message
+ */
+void CMainFrame::OnUpdateTimelineDeleteSelectedKeyframes()
+{
+ m_ui->actionDelete_Selected_Keyframe_s->setEnabled(getTimelineWidget()->hasSelectedKeyframes());
+}
+
+/**
+ * OnUpdateTimelineSetInterpolation: Handles the update UI message for
+ * the "Set Interpolation" message.
+ *
+ * If there are currently keyframes selected, this menu item is enabled, otherwise
+ * it is disabled.
+ *
+ * @param pCmdUI The UI element that generated this message
+ */
+void CMainFrame::OnUpdateTimelineSetInterpolation()
+{
+ m_ui->actionSet_Interpolation->setEnabled(getTimelineWidget()->hasSelectedKeyframes());
+}
+
+/**
+ * OnEditDuplicate: Handles the ID_EDIT_DUPLICATE message.
+ *
+ * Pass through to the doc.
+ */
+void CMainFrame::OnEditDuplicate()
+{
+ g_StudioApp.HandleDuplicateCommand();
+}
+
+void CMainFrame::onEditDelete()
+{
+ g_StudioApp.DeleteSelectedObject();
+}
+
+void CMainFrame::onEditGroup()
+{
+ if (!g_StudioApp.ungroupSelectedObjects())
+ g_StudioApp.groupSelectedObjects();
+}
+
+/**
+ * Command handler for the File Open menu and toolbar options.
+ * This will save the file, if the file has not been saved before this will
+ * do a save as operation.
+ */
+void CMainFrame::OnFileOpen()
+{
+ g_StudioApp.OnFileOpen();
+}
+
+/**
+ * Command handler for the File Save menu and toolbar options.
+ * This will save the file, if the file has not been saved before this will
+ * do a save as operation.
+ */
+void CMainFrame::OnFileSave()
+{
+ g_StudioApp.OnSave();
+}
+
+void CMainFrame::OnUpdateFileSave()
+{
+ m_ui->action_Save->setEnabled(g_StudioApp.GetCore()->GetDoc()->isModified());
+}
+
+/**
+ * Command handler for the File Save Project As menu option.
+ */
+void CMainFrame::onProjectSaveAs()
+{
+ g_StudioApp.onProjectSaveAs();
+}
+
+/**
+ * Command handler for the File Duplicate Presentation menu option.
+ */
+void CMainFrame::onDuplicatePresentation()
+{
+ g_StudioApp.duplicatePresentation();
+}
+
+/**
+ * Command handler for the New Project menu option.
+ * This will also create a new default presentation.
+ */
+void CMainFrame::OnProjectNew()
+{
+ g_StudioApp.OnProjectNew();
+}
+
+void CMainFrame::OnFileNew()
+{
+ g_StudioApp.OnFileNew();
+}
+
+// Ctrl+<1..9> handler, change view
+void CMainFrame::onCtrlNPressed()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ Q_ASSERT(action);
+ QKeySequence shortcut = action->shortcut();
+ QChar c = shortcut.toString().back();
+ if (shortcut.matches(Qt::CTRL | static_cast<Qt::Key>(c.unicode())) == QKeySequence::ExactMatch)
+ m_ui->m_EditCamerasBar->setCameraIndex(c.digitValue() == 1 ? 9 : c.digitValue() - 2);
+}
+
+/**
+ * Overrides the close method to prompt if the document is modified.
+ */
+void CMainFrame::closeEvent(QCloseEvent *event)
+{
+ handleGeometryAndState(true);
+ QMainWindow::closeEvent(event);
+
+ if (g_StudioApp.GetCore()->GetDoc()->isModified()) {
+ CDialogs::ESavePromptResult theResult = g_StudioApp.GetDialogs()->PromptForSave();
+ if (theResult == CDialogs::SAVE_FIRST) {
+ // If the save was canceled or failed then do not exit.
+ if (!g_StudioApp.OnSave())
+ return;
+ } else if (theResult == CDialogs::CANCEL_OPERATION) {
+ // On cancel ditch out of here and abort exit. Abort! Abort!
+ event->ignore();
+ return;
+ }
+ }
+
+ // Tell the app to shutdown, do it here so it does not rely on static destructor.
+ QTimer::singleShot(0, &g_StudioApp, &CStudioApp::performShutdown);
+}
+
+/**
+ * Displays the preferences dialog and can change program settings.
+ */
+void CMainFrame::OnEditApplicationPreferences()
+{
+ EditPreferences(PAGE_STUDIOAPPPREFERENCES);
+}
+
+/**
+ * Displays the preferences dialog and can change program settings.
+ */
+void CMainFrame::OnEditPresentationPreferences()
+{
+ EditPreferences(PAGE_STUDIOPROJECTSETTINGS);
+}
+
+/**
+ * Displays the data input dialog.
+ */
+void CMainFrame::OnFileDataInputs()
+{
+ CDataInputListDlg dataInputDlg(&(g_StudioApp.m_dataInputDialogItems));
+ dataInputDlg.exec();
+
+ if (dataInputDlg.result() == QDialog::Accepted)
+ g_StudioApp.saveDataInputsToProjectFile();
+}
+
+/**
+ * Generates QML declarations for datainputs and outputs in this
+ * project and copies them to clipboard.
+ */
+void CMainFrame::OnFileGenerateDataInputCode()
+{
+ QString out;
+
+ for (const auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems)) {
+ out.append(QStringLiteral("DataInput {\n"));
+ out.append(QStringLiteral(" name: \"") + it->name + QStringLiteral("\"\n"));
+ out.append(QStringLiteral("}\n"));
+ // TODO QT3DS-3510 - each datainput also generates dataoutput section, as this is how
+ // dataoutputs behave now in runtime.
+ out.append(QStringLiteral("DataOutput {\n"));
+ out.append(QStringLiteral(" name: \"") + it->name + QStringLiteral("\"\n"));
+ out.append(QStringLiteral("}\n"));
+ }
+
+ CStudioClipboard::CopyTextToClipboard(out);
+}
+
+/**
+ * EditPreferences: Displays the presentation settings property sheet with
+ * the specified active page.
+ *
+ * Used for editing the application and project settings.
+ *
+ * @param inPageIndex The page index to select when displayed.
+ */
+void CMainFrame::EditPreferences(short inPageIndex)
+{
+ // Set the active page based on the inPageIndex
+ m_propSheet.reset(new CStudioPreferencesPropSheet(tr("Studio Preferences"), this, inPageIndex));
+
+ // Display the CStudioPreferencesPropSheet
+ int thePrefsReturn = m_propSheet->exec();
+
+ m_sceneView->onEditCameraChanged();
+
+ if (thePrefsReturn == PREFS_RESET_DEFAULTS) {
+ // Restore default values
+ g_StudioApp.SetAutosetKeyframes(true); // Sets the preference as well
+ CStudioPreferences::SetBoundingBoxesOn(true);
+ CStudioPreferences::SetDisplayPivotPoint(true);
+ CStudioPreferences::SetWireframeModeOn(true);
+ CStudioPreferences::SetShowTooltips(true);
+ CStudioPreferences::SetTimebarDisplayTime(false);
+ g_StudioApp.GetCore()->GetDoc()->SetDefaultKeyframeInterpolation(true);
+ CStudioPreferences::SetSnapRange(CStudioPreferences::DEFAULT_SNAPRANGE);
+ CStudioPreferences::SetDefaultObjectLifetime(CStudioPreferences::DEFAULT_LIFETIME);
+ CStudioPreferences::SetAdvancePropertyExpandedFlag(false);
+ CStudioPreferences::SetPreviewConfig("");
+ CStudioPreferences::SetPreviewProperty("", "");
+ CStudioPreferences::SetDontShowGLVersionDialog(false);
+ CStudioPreferences::SetDefaultClientSize(CStudioPreferences::DEFAULT_CLIENT_WIDTH,
+ CStudioPreferences::DEFAULT_CLIENT_HEIGHT);
+ CStudioPreferences::SetTimeAdvanceAmount(CStudioPreferences::DEFAULT_TIME_ADVANCE);
+ CStudioPreferences::SetBigTimeAdvanceAmount(CStudioPreferences::DEFAULT_BIG_TIME_ADVANCE);
+ CStudioPreferences::SetTimelineSnappingGridActive(true);
+ CStudioPreferences::SetTimelineSnappingGridResolution(SNAPGRID_SECONDS);
+ CStudioPreferences::SetLegacyViewerActive(true);
+ CStudioPreferences::SetEditViewFillMode(true);
+ CStudioPreferences::SetPreferredStartupView(
+ CStudioPreferences::PREFERREDSTARTUP_DEFAULTINDEX);
+ CStudioPreferences::SetAutoSaveDelay(CStudioPreferences::DEFAULT_AUTOSAVE_DELAY);
+ CStudioPreferences::SetAutoSavePreference(true);
+ CStudioPreferences::setSelectorLineWidth(
+ (float)CStudioPreferences::DEFAULT_SELECTOR_WIDTH / 10.0f);
+ CStudioPreferences::setSelectorLineLength(
+ (float)CStudioPreferences::DEFAULT_SELECTOR_LENGTH);
+
+ RecheckSizingMode();
+
+ // Save preferences, to make sure we do not lose them on a possible crash
+ QTimer::singleShot(0, [](){ CStudioPreferences::savePreferences(); });
+ } else if (thePrefsReturn == PREFS_RESET_LAYOUT) {
+ onViewResetLayout();
+ } else if (thePrefsReturn == PREFS_SETTINGS_RESTART) {
+ QTimer::singleShot(0, this, &CMainFrame::handleRestart);
+ } else if (thePrefsReturn != 0) {
+ // Save preferences, to make sure we do not lose them on a possible crash
+ QTimer::singleShot(0, [](){ CStudioPreferences::savePreferences(); });
+ }
+}
+
+/**
+ * OnToolAutosetkeys: Called when the Autoset Keyframe button is pressed.
+ * Calls the doc to turn off or on the Autoset Keyframe preference.
+ */
+void CMainFrame::OnToolAutosetkeys()
+{
+ // Toggle autoset keyframes to the opposite of what it's currently set as
+ g_StudioApp.SetAutosetKeyframes(!CStudioPreferences::IsAutosetKeyframesOn());
+
+ // Don't wait for regular update cycle to update the corresponding toolbar/menu checked status
+ m_ui->actionAutoset_Keyframes->setChecked(CStudioPreferences::IsAutosetKeyframesOn());
+}
+
+/**
+ * OnUpdateToolAutosetkeys: Updates the UI associated with this button.
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * tool mode, and whether or not the button is enabled.
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+void CMainFrame::OnUpdateToolAutosetkeys()
+{
+ // If autoset keyframes is on
+ m_ui->actionAutoset_Keyframes->setChecked(CStudioPreferences::IsAutosetKeyframesOn());
+}
+
+/**
+ * Called when the presentation is being played in Studio. Updates the play
+ * button on the main frame.
+ */
+void CMainFrame::OnPlayStart()
+{
+ // Update the play button since this doesn't always happen automatically
+ Q_EMIT playStateChanged(true);
+
+ if (m_playbackFlag == false) {
+ m_playbackFlag = true;
+ m_playbackTimer->start();
+ }
+}
+
+/**
+ * Called when the presentation stops being played in Studio. Updates the play
+ * button on the main frame.
+ */
+void CMainFrame::OnPlayStop()
+{
+ // Update the play button since this doesn't always happen automatically
+ Q_EMIT playStateChanged(false);
+
+ if (m_playbackFlag == true) {
+ m_playbackFlag = false;
+ m_playbackTimer->stop();
+ }
+}
+
+/**
+ * Called when the presentation time changes. Not handled by this class,
+ * but included because the base class requires it to be implemented.
+ */
+void CMainFrame::OnTimeChanged(long inTime)
+{
+}
+
+/**
+ * Handles pressing the play button.
+ */
+void CMainFrame::OnPlaybackPlay()
+{
+ g_StudioApp.PlaybackPlay();
+}
+
+/**
+ * Handles pressing of the stop button.
+ */
+void CMainFrame::OnPlaybackStop()
+{
+ g_StudioApp.PlaybackStopNoRestore();
+}
+
+/**
+ * Handles pressing the preview button.
+ */
+void CMainFrame::OnPlaybackPreview(const QString &viewerExeName, bool remote)
+{
+ if (remote && m_remoteDeploymentSender->isConnected()) {
+ g_StudioApp.GetCore()->GetDispatch()->FireOnProgressBegin(
+ QObject::tr("Deploying to remote device..."), {});
+ CPreviewHelper::OnDeploy(*m_remoteDeploymentSender);
+ g_StudioApp.GetCore()->GetDispatch()->FireOnProgressEnd();
+ } else {
+ CPreviewHelper::OnPreview(viewerExeName);
+ }
+}
+
+void CMainFrame::OnPlaybackPreviewQt3DRuntime()
+{
+ OnPlaybackPreview(QStringLiteral("q3dsviewer"));
+}
+
+void CMainFrame::OnPlaybackPreviewOpenGLRuntime()
+{
+ OnPlaybackPreview(QStringLiteral("Qt3DViewer"));
+}
+
+void CMainFrame::onFilterVariants()
+{
+ if (m_ui->actionFilterVariants->isChecked()) {
+ QRect actionGeom = m_ui->m_PlaybackToolbar->actionGeometry(m_ui->actionFilterVariants);
+ m_filterVariantsDlg->activateWindow();
+ m_filterVariantsDlg->raise();
+ m_filterVariantsDlg->move(m_ui->m_PlaybackToolbar->pos()
+ + QPoint(actionGeom.x(), actionGeom.bottom()));
+ m_filterVariantsDlg->setFocus();
+ m_filterVariantsDlg->show();
+ } else {
+ m_filterVariantsDlg->close();
+ }
+}
+
+QString CMainFrame::getVariantsFilterStr() const
+{
+ if (m_filterVariantsDlg)
+ return m_filterVariantsDlg->filterStr();
+
+ return {};
+}
+
+void CMainFrame::updateActionFilterEnableState()
+{
+ bool enable = false;
+ const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef();
+ const auto keys = variantsDef.keys();
+ for (auto &group : keys) {
+ if (!variantsDef[group].m_tags.isEmpty()) {
+ enable = true;
+ break;
+ }
+ }
+
+ m_ui->actionFilterVariants->setEnabled(enable);
+}
+
+void CMainFrame::updateToolbarVariantsIcons(bool isFiltered)
+{
+ if (isFiltered) {
+ m_ui->actionPreview->setIcon(QIcon(QStringLiteral(":/images/preview-variants.png")));
+ m_ui->actionFilterVariants->setIcon(QIcon(QStringLiteral(":/images/filter-colored.png")));
+ } else {
+ m_ui->actionPreview->setIcon(QIcon(QStringLiteral(":/images/preview.png")));
+ m_ui->actionFilterVariants->setIcon(QIcon(QStringLiteral(":/images/filter.png")));
+ }
+}
+
+void CMainFrame::OnPlaybackPreviewRemote()
+{
+ OnPlaybackPreview(QStringLiteral("q3dsviewer"), true);
+}
+
+//==============================================================================
+/**
+ * Handles the update ui message for the preview button.
+ * Adding more UI updating here would just be redundant.
+ * @param inCmdUI Pointer to the UI element that needs updating
+ */
+void CMainFrame::OnUpdatePlaybackPreview()
+{
+}
+
+//==============================================================================
+/**
+ * Handles the update ui message for the play button. Does nothing because the
+ * button state is being updated manually in OnPlayStart() and OnPlayStop.
+ * Adding more UI updating here would just be redundant.
+ * @param inCmdUI Pointer to the UI element that needs updating
+ */
+void CMainFrame::OnUpdatePlaybackPlay()
+{
+}
+
+//==============================================================================
+/**
+ * Handles pressing the rewind button.
+ */
+void CMainFrame::OnPlaybackRewind()
+{
+ g_StudioApp.PlaybackRewind();
+}
+
+//==============================================================================
+/**
+ * Registers all the keys it will need for shortcuts, also telsl children to register theirs
+ * @param inHotKeys the hotkeys to with which to register
+ */
+void CMainFrame::RegisterGlobalKeyboardShortcuts(CHotKeys *inHotKeys, QWidget *actionParent)
+{
+ // Default undo shortcut is Ctrl-Y, which is specified in main form. Let's add the common
+ // alternate shortcut for redo, CTRL-SHIFT-Z
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z),
+ CMainFrame::OnEditRedo);
+
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::Key_Q),
+ CMainFrame::toggleSelectMode);
+
+ for (int keyN = Qt::Key_1; keyN <= Qt::Key_9; keyN++) {
+ ADD_GLOBAL_SHORTCUT(actionParent,
+ QKeySequence(Qt::CTRL | static_cast<Qt::Key>(keyN)),
+ CMainFrame::onCtrlNPressed);
+ }
+}
+
+//==============================================================================
+/**
+ * OnUpdateToolScale: Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * tool mode, and whether or not the button is enabled.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+void CMainFrame::OnUpdateToolGlobalManipulators()
+{
+ StudioManipulationModes::Enum theMode = g_StudioApp.GetManipulationMode();
+
+ // If the current tool mode matches this button
+ // If the button is currently enabled
+ m_ui->actionLocal_Global_Manipulators->setChecked(
+ theMode == StudioManipulationModes::Global
+ && m_ui->actionLocal_Global_Manipulators->isEnabled());
+}
+
+//==============================================================================
+/**
+ * Move, Rotate, or Scale button clicked. Sets the current tool mode and changes the cursor.
+ *
+ * @param toolMode the selected tool (move, rotate, or scale)
+ */
+void CMainFrame::onTransformToolChanged(long toolMode)
+{
+ g_StudioApp.SetToolMode(toolMode);
+ m_sceneView->setToolMode(toolMode);
+
+ m_ui->actionPosition_Tool->setChecked(toolMode == STUDIO_TOOLMODE_MOVE);
+ m_ui->actionRotation_Tool->setChecked(toolMode == STUDIO_TOOLMODE_ROTATE);
+ m_ui->actionScale_Tool->setChecked(toolMode == STUDIO_TOOLMODE_SCALE);
+}
+
+void CMainFrame::OnToolGlobalManipulators()
+{
+ if (m_ui->actionLocal_Global_Manipulators->isChecked())
+ g_StudioApp.SetManipulationMode(StudioManipulationModes::Global);
+ else
+ g_StudioApp.SetManipulationMode(StudioManipulationModes::Local);
+
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+//==============================================================================
+/**
+ * OnUpdateToolGroupSelection: Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * tool mode, and whether or not the button is enabled.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+void CMainFrame::OnUpdateToolGroupSelection()
+{
+ long theCurrentSelectSettings = g_StudioApp.GetSelectMode();
+
+ // If the current tool mode matches this button
+ // If the button is currently enabled
+ m_ui->actionGroup_Select_Tool->setChecked(theCurrentSelectSettings == STUDIO_SELECTMODE_GROUP
+ && m_ui->actionGroup_Select_Tool->isEnabled());
+}
+
+//==============================================================================
+/**
+ * OnUpdateToolItemSelection: Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * tool mode, and whether or not the button is enabled.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+void CMainFrame::OnUpdateToolItemSelection()
+{
+ long theCurrentSelectSettings = g_StudioApp.GetSelectMode();
+
+ // If the current tool mode matches this button
+ // If the button is currently enabled
+ m_ui->actionItem_Select_Tool->setChecked(theCurrentSelectSettings == STUDIO_SELECTMODE_ENTITY
+ && m_ui->actionItem_Select_Tool->isEnabled());
+}
+
+//==============================================================================
+/**
+ * Called when the edit camera Zoom Extent button is pressed.
+ * Inform the current active edit camera to toggle itself.
+ */
+void CMainFrame::OnEditCameraZoomExtent()
+{
+ if (g_StudioApp.getRenderer().GetEditCamera() >= 0)
+ g_StudioApp.getRenderer().EditCameraZoomToFit();
+ else
+ g_StudioApp.SetAuthorZoom(!g_StudioApp.IsAuthorZoom());
+}
+
+//==============================================================================
+/**
+ * Called when the "Zoom Extent" keyboard shortcut is pressed.
+ * Inform the current active edit camera to toggle itself.
+ */
+//==============================================================================
+void CMainFrame::HandleEditCameraZoomExtent()
+{
+ OnEditCameraZoomExtent();
+}
+
+//==============================================================================
+/**
+ * Called when the Pan Edit Camera button is pressed.
+ * Inform the current active edit camera on their tool mode.
+ */
+void CMainFrame::OnEditCameraPan()
+{
+ g_StudioApp.SetToolMode(STUDIO_TOOLMODE_CAMERA_PAN);
+ m_sceneView->setViewCursor(); // Just set cursor, we don't want to update previous tool
+}
+
+//==============================================================================
+/**
+ * Called when the Rotate Edit Camera button is pressed.
+ * Inform the current active edit camera on their tool mode.
+ */
+void CMainFrame::OnEditCameraRotate()
+{
+ g_StudioApp.SetToolMode(STUDIO_TOOLMODE_CAMERA_ROTATE);
+ m_sceneView->setViewCursor(); // Just set cursor, we don't want to update previous tool
+}
+
+//==============================================================================
+/**
+ * Called when the Zoom Edit Camera button is pressed.
+ * Inform the current active edit camera on their tool mode.
+ */
+void CMainFrame::OnEditCameraZoom()
+{
+ g_StudioApp.SetToolMode(STUDIO_TOOLMODE_CAMERA_ZOOM);
+ m_sceneView->setViewCursor(); // Just set cursor, we don't want to update previous tool
+}
+
+//==============================================================================
+/**
+ * Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * settings of the current edit camera tool.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+//==============================================================================
+void CMainFrame::OnUpdateCameraZoomExtentAndAuthorZoom()
+{
+ if (m_sceneView.data() == GetActiveView() && !m_sceneView->isDeploymentView())
+ m_ui->actionFit_Selected->setChecked(false);
+ else
+ m_ui->actionFit_Selected->setChecked(g_StudioApp.IsAuthorZoom());
+}
+
+//==============================================================================
+/**
+ * Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * settings of the current edit camera tool.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+//==============================================================================
+void CMainFrame::OnUpdateEditCameraPan()
+{
+ if (m_sceneView.data() == GetActiveView() && !m_sceneView->isDeploymentView()) {
+ m_ui->actionPan_Tool->setEnabled(true);
+
+ long theCurrentToolSettings = g_StudioApp.GetToolMode();
+ m_ui->actionPan_Tool->setChecked(theCurrentToolSettings == STUDIO_TOOLMODE_CAMERA_PAN);
+ } else {
+ m_ui->actionPan_Tool->setEnabled(false);
+ m_ui->actionPan_Tool->setChecked(false);
+ }
+}
+
+//==============================================================================
+/**
+ * Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * settings of the current edit camera tool.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+//==============================================================================
+void CMainFrame::OnUpdateEditCameraRotate()
+{
+#if 0 // TODO: Disabled until UX decision is made if these buttons are needed at all or not
+ if (m_SceneView == GetActiveView() && !m_SceneView->IsDeploymentView()
+ && g_StudioApp.GetRenderer().DoesEditCameraSupportRotation(
+ g_StudioApp.GetRenderer().GetEditCamera())) {
+ m_ui->actionOrbit_Tool->setEnabled(true);
+
+ long theCurrentToolSettings = g_StudioApp.GetToolMode();
+ m_ui->actionOrbit_Tool->setChecked(theCurrentToolSettings == STUDIO_TOOLMODE_CAMERA_ROTATE);
+ } else {
+ m_ui->actionOrbit_Tool->setEnabled(false);
+ m_ui->actionOrbit_Tool->setChecked(false);
+ }
+#endif
+}
+
+//==============================================================================
+/**
+ * Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * settings of the current edit camera tool.
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+//==============================================================================
+void CMainFrame::OnUpdateEditCameraZoom()
+{
+ if (m_sceneView.data() == GetActiveView() && !m_sceneView->isDeploymentView()) {
+ m_ui->actionZoom_Tool->setEnabled(true);
+
+ long theCurrentToolSettings = g_StudioApp.GetToolMode();
+ m_ui->actionZoom_Tool->setChecked(theCurrentToolSettings == STUDIO_TOOLMODE_CAMERA_ZOOM);
+ } else {
+ m_ui->actionZoom_Tool->setEnabled(false);
+ m_ui->actionZoom_Tool->setChecked(false);
+ }
+}
+
+//==============================================================================
+/**
+ * Called when the "Render geometry as solid/wireframe in edit view" button is pressed.
+ * Toggle the mode and update the studio preferences, also cache it in EditCameraContainer
+ */
+//==============================================================================
+void CMainFrame::HandleEditViewFillModeKey()
+{
+ if (m_sceneView.data() == GetActiveView() && !m_sceneView->isDeploymentView()) {
+ OnEditViewFillMode();
+ bool theEditViewFillMode = g_StudioApp.getRenderer().IsPolygonFillModeEnabled();
+ m_ui->actionShading_Mode->setChecked(theEditViewFillMode);
+ }
+}
+
+//==============================================================================
+/**
+ * Called when the "Render geometry as solid/wireframe in edit view" button is pressed.
+ * Toggle the mode and update the studio preferences, also cache it in EditCameraContainer
+ */
+//==============================================================================
+void CMainFrame::OnEditViewFillMode()
+{
+ bool theEditViewFillMode = !g_StudioApp.getRenderer().IsPolygonFillModeEnabled();
+ g_StudioApp.getRenderer().SetPolygonFillModeEnabled(theEditViewFillMode);
+}
+
+//==============================================================================
+/**
+ * Updates the UI associated with this button.
+ *
+ * Checks or unchecks this button on the toolbar, depending on the current
+ * settings of the "Render geometry as solid/wireframe in edit view".
+ *
+ * @param pCmdUI Pointer to the button that generated the message.
+ */
+//==============================================================================
+void CMainFrame::OnUpdateEditViewFillMode()
+{
+ if (m_sceneView.data() == GetActiveView() && !m_sceneView->isDeploymentView()) {
+ m_ui->actionShading_Mode->setEnabled(true);
+ m_ui->actionShading_Mode->setChecked(g_StudioApp.getRenderer().IsPolygonFillModeEnabled());
+ } else {
+ m_ui->actionShading_Mode->setEnabled(false);
+ m_ui->actionShading_Mode->setChecked(false);
+ }
+}
+
+void CMainFrame::OnViewGuidesRulers()
+{
+ g_StudioApp.getRenderer().SetGuidesEnabled(!g_StudioApp.getRenderer().AreGuidesEnabled());
+ g_StudioApp.GetCore()->GetDispatch()->FireAuthorZoomChanged();
+ m_sceneView->onRulerGuideToggled();
+}
+
+void CMainFrame::OnUpdateViewGuidesRulers()
+{
+ m_ui->actionRulers_Guides->setEnabled(m_sceneView->isDeploymentView());
+ m_ui->actionRulers_Guides->setChecked(g_StudioApp.getRenderer().AreGuidesEnabled());
+}
+
+void CMainFrame::OnClearGuides()
+{
+ g_StudioApp.clearGuides();
+}
+
+void CMainFrame::OnUpdateClearGuides()
+{
+ bool enable = g_StudioApp.getRenderer().AreGuidesEnabled()
+ && g_StudioApp.getRenderer().AreGuidesEditable() && m_sceneView->isDeploymentView();
+
+ m_ui->actionClear_Guides->setEnabled(enable);
+}
+
+void CMainFrame::OnLockGuides()
+{
+ g_StudioApp.getRenderer().SetGuidesEditable(!g_StudioApp.getRenderer().AreGuidesEditable());
+}
+
+void CMainFrame::OnUpdateLockGuides()
+{
+ bool enable = g_StudioApp.getRenderer().AreGuidesEnabled() && m_sceneView->isDeploymentView();
+ m_ui->actionLock_Guides->setEnabled(enable);
+ // Set to the inverse of guides editable.
+ m_ui->actionLock_Guides->setChecked(!g_StudioApp.getRenderer().AreGuidesEditable());
+}
+
+void CMainFrame::OnUpdateCameraPreview()
+{
+ m_ui->actionCamera_Preview->setChecked(CStudioPreferences::showEditModePreview());
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+void CMainFrame::OnUpdateEditViewLightingEnabled()
+{
+ m_ui->actionEdit_Lighting->setChecked(CStudioPreferences::editModeLightingEnabled());
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+void CMainFrame::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == WM_STUDIO_TIMER)
+ g_StudioApp.getTickTock().ProcessMessages();
+ QMainWindow::timerEvent(event);
+}
+
+void CMainFrame::onViewResetLayout()
+{
+ // Ask for a restart
+ int theChoice = QMessageBox::question(this,
+ tr("Restart Needed"),
+ tr("Are you sure that you want to restore Qt 3D Studio "
+ "layout? \nYour current layout will be lost, and "
+ "Studio will restart."));
+
+ // If "Yes" is clicked, delete window geometry and window state keys from QSettings
+ if (theChoice == QMessageBox::Yes) {
+ QSettings settings;
+ QString geoKey = QStringLiteral("mainWindowGeometry") + QString::number(STUDIO_VERSION_NUM);
+ QString stateKey = QStringLiteral("mainWindowState") + QString::number(STUDIO_VERSION_NUM);
+ settings.remove(geoKey);
+ settings.remove(stateKey);
+ // Prevent saving geometry and state, and exit
+ m_resettingLayout = true;
+ QTimer::singleShot(0, this, &CMainFrame::handleRestart);
+ }
+}
+
+void CMainFrame::OnViewAction()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_ACTION);
+}
+
+void CMainFrame::OnUpdateViewAction()
+{
+ m_ui->actionAction->setChecked(
+ m_paletteManager->IsControlVisible(CPaletteManager::CONTROLTYPE_ACTION));
+}
+
+void CMainFrame::OnViewBasicObjects()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_BASICOBJECTS);
+}
+
+void CMainFrame::OnUpdateViewBasicObjects()
+{
+ m_ui->actionBasic_Objects->setChecked(m_paletteManager->IsControlVisible(
+ CPaletteManager::CONTROLTYPE_BASICOBJECTS));
+}
+
+void CMainFrame::OnViewInspector()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_INSPECTOR);
+}
+
+void CMainFrame::OnUpdateViewInspector()
+{
+ m_ui->actionInspector->setChecked(
+ m_paletteManager->IsControlVisible(CPaletteManager::CONTROLTYPE_INSPECTOR));
+}
+
+void CMainFrame::OnViewProject()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_PROJECT);
+}
+
+void CMainFrame::OnUpdateViewProject()
+{
+ m_ui->actionProject->setChecked(
+ m_paletteManager->IsControlVisible(CPaletteManager::CONTROLTYPE_PROJECT));
+}
+
+void CMainFrame::OnViewSlide()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_SLIDE);
+}
+
+void CMainFrame::OnUpdateViewSlide()
+{
+ m_ui->actionSlide->setChecked(
+ m_paletteManager->IsControlVisible(CPaletteManager::CONTROLTYPE_SLIDE)
+ ? TRUE : FALSE);
+}
+
+//==============================================================================
+/**
+ * Called when the View Inspector Palette menu item is chosen.
+ */
+void CMainFrame::OnViewTimeline()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_TIMELINE);
+}
+
+//==============================================================================
+/**
+ * Checks or unchecks the menu item depending on if the view is available or not.
+ * @param pCmdUI Pointer to the UI element that generated the message.
+ */
+void CMainFrame::OnUpdateViewTimeline()
+{
+ m_ui->actionTimeline->setChecked(
+ m_paletteManager->IsControlVisible(CPaletteManager::CONTROLTYPE_TIMELINE));
+}
+
+void CMainFrame::onViewSceneCamera()
+{
+ m_paletteManager->ToggleControl(CPaletteManager::CONTROLTYPE_SCENECAMERA);
+ onUpdateViewSceneCamera();
+}
+
+void CMainFrame::onUpdateViewSceneCamera()
+{
+ const bool cameraVisible = m_paletteManager->IsControlVisible(
+ CPaletteManager::CONTROLTYPE_SCENECAMERA);
+ m_ui->actionSceneCamera->setChecked(cameraVisible);
+ g_StudioApp.getRenderer().setFullSizePreview(cameraVisible);
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+//==============================================================================
+/**
+ * Called when the View Inspector Palette menu item is chosen.
+ */
+void CMainFrame::OnViewBoundingBoxes()
+{
+ CStudioPreferences::SetBoundingBoxesOn(!CStudioPreferences::IsBoundingBoxesOn());
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+//==============================================================================
+/**
+ * Checks or unchecks the menu item depending on if the view is available or not.
+ * @param pCmdUI Pointer to the UI element that generated the message.
+ */
+void CMainFrame::OnUpdateViewBoundingBoxes()
+{
+ m_ui->actionBounding_Boxes->setChecked(CStudioPreferences::IsBoundingBoxesOn());
+}
+
+//==============================================================================
+/**
+ * Called when the View Pivot Point menu item is chosen.
+ */
+void CMainFrame::OnViewPivotPoint()
+{
+ CStudioPreferences::SetDisplayPivotPoint(!CStudioPreferences::ShouldDisplayPivotPoint());
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+//==============================================================================
+/**
+ * Checks or unchecks the menu item depending on if the view is available or not.
+ * @param pCmdUI Pointer to the UI element that generated the message.
+ */
+void CMainFrame::OnUpdateViewPivotPoint()
+{
+ m_ui->actionPivot_Point->setChecked(CStudioPreferences::ShouldDisplayPivotPoint());
+}
+
+//==============================================================================
+/**
+ * Called when the View Wireframe menu item is chosen.
+ */
+void CMainFrame::OnViewWireframe()
+{
+ CStudioPreferences::SetWireframeModeOn(!CStudioPreferences::IsWireframeModeOn());
+
+ // Don't wait for regular update cycle to update the corresponding toolbar/menu checked status
+ m_ui->actionWireframe->setChecked(CStudioPreferences::IsWireframeModeOn());
+
+ g_StudioApp.getRenderer().RequestRender();
+}
+
+//==============================================================================
+/**
+ * Checks or unchecks the menu item depending on if the view is available or not.
+ * @param pCmdUI Pointer to the UI element that generated the message.
+ */
+void CMainFrame::OnUpdateViewWireframe()
+{
+ m_ui->actionWireframe->setChecked(CStudioPreferences::IsWireframeModeOn());
+}
+
+//==============================================================================
+/**
+ * Checks or unchecks the menu item depending whether tooltips should be shown
+ * or not.
+ * @param inCmdUI Pointer to the UI element that generated the message.
+ */
+void CMainFrame::OnUpdateViewTooltips()
+{
+ m_ui->actionTooltips->setChecked(CStudioPreferences::ShouldShowTooltips());
+}
+
+//==============================================================================
+/**
+ * Called when the "View->Tooltips" menu item is chosen. Toggles tooltips on
+ * and off for custom controls.
+ */
+void CMainFrame::OnViewTooltips()
+{
+ CStudioPreferences::SetShowTooltips(!CStudioPreferences::ShouldShowTooltips());
+}
+
+//==============================================================================
+/**
+ * Called when the update message occurs for the Help->Help Topics menu item.
+ * If the help file exists, the menu item is enabled, otherwise it's disabled.
+ * @param inCmdUI UI element that generated the message
+ */
+void CMainFrame::OnUpdateHelpIndex()
+{
+ QFile theFile(g_StudioApp.m_helpFilePath);
+ m_ui->action_Reference_Manual->setEnabled(theFile.exists());
+}
+
+//==============================================================================
+/**
+ * Handles the ID_HELP_INDEX command. Opens the online help for Studio.
+ */
+void CMainFrame::OnHelpIndex()
+{
+ QFile theFile(g_StudioApp.m_helpFilePath);
+ if (theFile.exists())
+ QDesktopServices::openUrl(QUrl::fromLocalFile(theFile.fileName()));
+}
+
+//==============================================================================
+/**
+ * Handles the ID_HELP_VISIT_QT command. Opens the Qt Web site.
+ */
+void CMainFrame::OnHelpVisitQt()
+{
+ QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.qt.io/3d-studio")));
+}
+
+//==============================================================================
+/**
+ * Opens the tutorial.
+ */
+void CMainFrame::OnHelpOpenTutorial()
+{
+ StudioTutorialWidget tutorial(this);
+ int welcomeRes = tutorial.exec();
+ g_StudioApp.handleWelcomeRes(welcomeRes, false);
+}
+
+//==============================================================================
+/**
+ * Handle the file revert menu option.
+ */
+void CMainFrame::OnFileRevert()
+{
+ g_StudioApp.OnRevert();
+}
+
+void CMainFrame::OnFileImportAssets()
+{
+ m_paletteManager->projectView()->assetImportAction(0);
+}
+
+void CMainFrame::OnFileConnectToDevice()
+{
+ if (m_remoteDeploymentSender->isConnected()) {
+ g_StudioApp.GetCore()->GetDispatch()->FireOnProgressBegin(
+ QObject::tr("Disconnecting from remote device..."), {});
+ m_remoteDeploymentSender->disconnect();
+ } else {
+ QPair<QString, int> info = m_remoteDeploymentSender->initConnection();
+ if (!info.first.isEmpty()) {
+ g_StudioApp.GetCore()->GetDispatch()->FireOnProgressBegin(
+ QObject::tr("Connecting to remote device..."), {});
+ m_remoteDeploymentSender->connect(info);
+ } else {
+ m_ui->action_Connect_to_Device->setChecked(false);
+ }
+ }
+}
+
+//==============================================================================
+/**
+ * Handles the recent list.
+ */
+void CMainFrame::OnFileOpenRecent(int nID)
+{
+ g_StudioApp.OnFileOpenRecent(m_recentItems->GetItem(nID));
+}
+
+//==============================================================================
+/**
+ * Tells the scene view to recheck its sizing mode and tells client to update
+ */
+void CMainFrame::RecheckSizingMode()
+{
+ m_sceneView->recheckSizingMode();
+}
+
+//==============================================================================
+/**
+ * Callback when a Core is opened or fails to open.
+ */
+void CMainFrame::OnOpenDocument(const QString &inFilename, bool inSucceeded)
+{
+ if (inSucceeded)
+ m_recentItems->AddRecentItem(inFilename);
+ else
+ m_recentItems->RemoveRecentItem(inFilename);
+}
+
+//==============================================================================
+/**
+ * Callback when a Core is saved or fails to save.
+ */
+void CMainFrame::OnSaveDocument(const QString &inFilename, bool inSucceeded, bool inSaveCopy)
+{
+ if (!inSaveCopy)
+ OnOpenDocument(inFilename, inSucceeded);
+}
+
+//==============================================================================
+/**
+ * Callback for when a the doc gets a new path
+ */
+void CMainFrame::OnDocumentPathChanged(const QString &inNewPath)
+{
+ QFileInfo info(inNewPath);
+
+ QString theTitle;
+ QFileInfo projectPathInfo(g_StudioApp.GetCore()->getProjectFile().getProjectPath());
+
+ QString projectDir = projectPathInfo.baseName();
+
+ if (!projectDir.isEmpty())
+ theTitle.append(QString("(%1) ").arg(projectDir));
+
+ if (!info.fileName().isEmpty())
+ theTitle.append(info.fileName());
+ else
+ theTitle.append(QObject::tr("Untitled"));
+
+ theTitle.append(QStringLiteral(" - ") + QObject::tr("Qt 3D Studio"));
+
+ // TODO: Move this whole pile to the studio app
+ setWindowTitle(theTitle);
+
+ if (info.exists())
+ m_recentItems->AddRecentItem(inNewPath);
+}
+
+void CMainFrame::OnShowSlide()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_SLIDE);
+}
+
+void CMainFrame::OnShowTimeline()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_TIMELINE);
+}
+
+void CMainFrame::OnShowBasic()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_BASICOBJECTS);
+}
+
+void CMainFrame::OnShowProject()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_PROJECT);
+}
+
+void CMainFrame::OnShowAction()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_ACTION);
+}
+
+void CMainFrame::OnShowInspector()
+{
+ m_paletteManager->ShowControl(CPaletteManager::CONTROLTYPE_INSPECTOR);
+}
+
+void CMainFrame::OnShowEditPreview()
+{
+ bool show = CStudioPreferences::showEditModePreview();
+ CStudioPreferences::setShowEditModePreview(!show);
+}
+
+void CMainFrame::OnEditViewLightingEnabled()
+{
+ bool enabled = CStudioPreferences::editModeLightingEnabled();
+ CStudioPreferences::setEditModeLightingEnabled(!enabled);
+}
+
+void CMainFrame::OnConnectionChanged(bool connected)
+{
+ g_StudioApp.GetCore()->GetDispatch()->FireOnProgressEnd();
+ m_ui->action_Connect_to_Device->setChecked(connected);
+ m_ui->actionRemote_Preview->setEnabled(connected);
+}
+
+TimelineWidget *CMainFrame::getTimelineWidget() const
+{
+ WidgetControl *control = static_cast<WidgetControl *>
+ (m_paletteManager->GetControl(CPaletteManager::CONTROLTYPE_TIMELINE)->widget());
+ return static_cast<TimelineWidget *>(control->getControl());
+}
+
+SlideView *CMainFrame::getSlideView() const
+{
+ return static_cast<SlideView *>(m_paletteManager->GetControl(CPaletteManager::CONTROLTYPE_SLIDE)
+ ->widget());
+}
+
+InspectorControlView *CMainFrame::getInspectorView() const
+{
+ return static_cast<InspectorControlView *>(m_paletteManager
+ ->GetControl(CPaletteManager::CONTROLTYPE_INSPECTOR)
+ ->widget());
+}
+
+CRecentItems *CMainFrame::GetRecentItems() const
+{
+ return m_recentItems.data();
+}
+
+QWidget *CMainFrame::GetActiveView() const
+{
+ return centralWidget();
+}
+
+CPlayerWnd *CMainFrame::GetPlayerWnd() const
+{
+ return m_sceneView->getPlayerWnd();
+}
+
+bool CMainFrame::eventFilter(QObject *obj, QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ToolTip: {
+ if (CStudioPreferences::ShouldShowTooltips())
+ event->ignore();
+ else
+ return true;
+ break;
+ }
+ case QEvent::KeyPress: {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (ke->key() == Qt::Key_Tab) {
+ if (m_paletteManager->tabNavigateFocusedWidget(true))
+ return true;
+ } else if (ke->key() == Qt::Key_Backtab) {
+ if (m_paletteManager->tabNavigateFocusedWidget(false))
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return QMainWindow::eventFilter(obj, event);
+}
+
+void CMainFrame::handleGeometryAndState(bool save)
+{
+ if (m_resettingLayout)
+ return;
+
+ QSettings settings;
+ QString geoKey = QStringLiteral("mainWindowGeometry") + QString::number(STUDIO_VERSION_NUM);
+ QString stateKey = QStringLiteral("mainWindowState") + QString::number(STUDIO_VERSION_NUM);
+ if (save) {
+ settings.setValue(geoKey, saveGeometry());
+ settings.setValue(stateKey, saveState(STUDIO_VERSION_NUM));
+ } else {
+ // Restoring geometry and state back to back results in errors in state restoration, so
+ // let's restore state asynchronously
+ restoreGeometry(settings.value(geoKey).toByteArray());
+ QTimer::singleShot(0, this, [this, stateKey]() {
+ QSettings settings;
+ restoreState(settings.value(stateKey).toByteArray(), STUDIO_VERSION_NUM);
+ });
+ }
+}
+
+void CMainFrame::handleRestart()
+{
+ QStringList presentationFile = QStringList(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath());
+ close();
+ QProcess::startDetached(qApp->arguments()[0], presentationFile);
+}
+
+void CMainFrame::initializeGeometryAndState()
+{
+ QSettings settings;
+ QString stateKey = QStringLiteral("mainWindowState") + QString::number(STUDIO_VERSION_NUM);
+ if (!settings.contains(stateKey)) {
+ // On first run, save and restore geometry and state. For some reason they are both needed
+ // to avoid a bug with palettes resizing to their original size when window is resized or
+ // something in a palette is edited.
+ handleGeometryAndState(true);
+ }
+ handleGeometryAndState(false);
+}
+
+void CMainFrame::toggleSelectMode()
+{
+ if (m_ui->actionItem_Select_Tool->isChecked())
+ m_sceneView->onToolGroupSelection();
+ else
+ m_sceneView->onToolItemSelection();
+}
+
+void CMainFrame::showScene()
+{
+ if (!m_sceneView.data()->isVisible()) {
+ setCentralWidget(m_sceneView.data());
+ m_sceneView.data()->setVisible(true);
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/MainFrm.h b/src/Authoring/Qt3DStudio/MainFrm.h
new file mode 100644
index 00000000..cf5d4768
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/MainFrm.h
@@ -0,0 +1,269 @@
+/****************************************************************************
+**
+** 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_MAIN_FRAME
+#define INCLUDED_MAIN_FRAME
+
+#include "DispatchListeners.h"
+
+#include <QtWidgets/qmainwindow.h>
+#include <QtCore/qtimer.h>
+
+class CHotKeys;
+class CPaletteManager;
+class CRecentItems;
+class CSceneView;
+class CStudioApp;
+class ITimelineTimebar;
+class RemoteDeploymentSender;
+class TimelineWidget;
+class CStudioPreferencesPropSheet;
+class SlideView;
+class InspectorControlView;
+class FilterVariantsDlg;
+class CPlayerWnd;
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+ class MainFrame;
+}
+QT_END_NAMESPACE
+
+
+class CMainFrame : public QMainWindow,
+ public CPresentationChangeListener,
+ public CFileOpenListener,
+ public CClientPlayChangeListener
+{
+ Q_OBJECT
+
+public:
+ CMainFrame();
+ virtual ~CMainFrame() override;
+
+ void OnNewPresentation() override;
+ void OnClosingPresentation() 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;
+
+ void RegisterGlobalKeyboardShortcuts(CHotKeys *inShortcutHandler, QWidget *actionParent);
+ void RecheckSizingMode();
+
+ // CClientPlayChangeListener
+ void OnPlayStart() override;
+ void OnPlayStop() override;
+ void OnTimeChanged(long inTime) override;
+
+ CRecentItems *GetRecentItems() const;
+
+ void OnCreate();
+
+ void onPlaybackTimeout();
+
+ void OnFileOpen();
+ void OnFileSave();
+ void OnUpdateFileSave();
+ void onProjectSaveAs();
+ void onDuplicatePresentation();
+ void OnProjectNew();
+ void OnFileNew();
+ void OnFileRevert();
+ void OnFileImportAssets();
+ void OnFileConnectToDevice();
+ void OnFileOpenRecent(int nID);
+ void OnFileDataInputs();
+ void OnFileGenerateDataInputCode();
+
+ void OnEditRedo();
+ void OnEditUndo();
+ void OnUpdateEditUndo();
+ void OnUpdateEditRedo();
+ void OnEditCopy();
+ void OnUpdateEditCopy();
+ void OnEditCut();
+ void OnUpdateEditCut();
+ void OnEditPaste();
+ void onEditPasteToMaster();
+ void OnUpdateEditPaste();
+ void OnEditDuplicate();
+ void onEditDelete();
+ void onEditGroup();
+
+ void timerEvent(QTimerEvent *event) override;
+
+ void OnUpdateTimelineSetTimeBarColor();
+ void OnTimelineSetTimeBarColor();
+ void OnTimelineSetChangedKeyframe();
+ void OnUpdateTimelineDeleteSelectedKeyframes();
+ void OnTimelineSetTimeBarText();
+ void OnUpdateTimelineSetTimeBarText();
+ void OnUpdateTimelineSetInterpolation();
+ void OnTimelineSetInterpolation();
+ void closeEvent(QCloseEvent *event) override;
+ void OnToolAutosetkeys();
+ void OnUpdateToolAutosetkeys();
+ void OnEditApplicationPreferences();
+ void OnEditPresentationPreferences();
+ void OnPlaybackPlay();
+ void OnUpdatePlaybackPlay();
+ void OnPlaybackRewind();
+ void OnPlaybackStop();
+ void OnPlaybackPreview(const QString &viewerExeName, bool remote = false);
+ void OnPlaybackPreviewQt3DRuntime();
+ void OnPlaybackPreviewOpenGLRuntime();
+ void OnPlaybackPreviewRemote();
+ void onFilterVariants();
+ void OnUpdatePlaybackPreview();
+ void OnUpdateToolMove();
+ void OnUpdateToolRotate();
+ void OnUpdateToolScale();
+ void OnUpdateToolGlobalManipulators();
+ void onTransformToolChanged(long toolMode);
+ void OnToolGlobalManipulators();
+ void OnUpdateToolChange();
+ void OnUpdateToolGroupSelection();
+ void OnUpdateToolItemSelection();
+
+ void OnViewBoundingBoxes();
+ void OnUpdateViewBoundingBoxes();
+ void OnViewPivotPoint();
+ void OnUpdateViewPivotPoint();
+ void OnViewWireframe();
+ void OnUpdateViewWireframe();
+ void OnViewHelpPalette();
+ void OnUpdateViewHelpPalette();
+ void OnUpdateViewTooltips();
+ void OnViewTooltips();
+ void OnUpdateHelpIndex();
+ void OnHelpIndex();
+ void OnHelpVisitQt();
+ void OnHelpOpenTutorial();
+
+ void onViewResetLayout();
+ void OnViewAction();
+ void OnUpdateViewAction();
+ void OnViewBasicObjects();
+ void OnUpdateViewBasicObjects();
+ void OnViewInspector();
+ void OnUpdateViewInspector();
+ void OnViewProject();
+ void OnUpdateViewProject();
+ void OnViewSlide();
+ void OnUpdateViewSlide();
+ void OnViewTimeline();
+ void OnUpdateViewTimeline();
+ void onViewSceneCamera();
+ void onUpdateViewSceneCamera();
+
+ void OnEditCameraZoomExtent();
+ void OnEditCameraPan();
+ void OnEditCameraRotate();
+ void OnEditCameraZoom();
+ void OnUpdateCameraZoomExtentAndAuthorZoom();
+ void OnUpdateEditCameraPan();
+ void OnUpdateEditCameraRotate();
+ void OnUpdateEditCameraZoom();
+ void OnEditViewFillMode();
+ void OnUpdateEditViewFillMode();
+ void OnEditViewLightingEnabled();
+ void OnUpdateEditViewLightingEnabled();
+
+ void OnViewGuidesRulers();
+ void OnUpdateViewGuidesRulers();
+ void OnClearGuides();
+ void OnUpdateClearGuides();
+ void OnLockGuides();
+ void OnUpdateLockGuides();
+
+ void OnShowSlide();
+ void OnShowTimeline();
+ void OnShowBasic();
+ void OnShowProject();
+ void OnShowAction();
+ void OnShowInspector();
+ void OnShowEditPreview();
+ void OnUpdateCameraPreview();
+
+ void OnConnectionChanged(bool);
+
+ void onCtrlNPressed();
+
+ TimelineWidget *getTimelineWidget() const;
+ SlideView *getSlideView() const;
+ InspectorControlView *getInspectorView() const;
+
+ void EditPreferences(short inPageIndex);
+
+ void HandleEditViewFillModeKey();
+ void HandleEditCameraZoomExtent();
+
+ QWidget *GetActiveView() const;
+ CPlayerWnd *GetPlayerWnd() const;
+
+ void initializeGeometryAndState();
+
+ void toggleSelectMode();
+ void showScene();
+ QString getVariantsFilterStr() const;
+ void updateActionFilterEnableState();
+ void updateToolbarVariantsIcons(bool isFiltered);
+
+Q_SIGNALS:
+ void playStateChanged(bool started);
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void handleGeometryAndState(bool save);
+ void handleRestart();
+
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::MainFrame)> m_ui;
+ QScopedPointer<RemoteDeploymentSender> m_remoteDeploymentSender;
+ QScopedPointer<CSceneView> m_sceneView;
+ QScopedPointer<CRecentItems> m_recentItems;
+ QScopedPointer<CPaletteManager> m_paletteManager;
+ QScopedPointer<QTimer> m_updateUITimer;
+ QScopedPointer<QTimer> m_playbackTimer;
+ QScopedPointer<CStudioPreferencesPropSheet> m_propSheet;
+
+ bool m_playbackFlag = false;
+ bool m_resettingLayout = false;
+
+private:
+ QScopedPointer<FilterVariantsDlg> m_filterVariantsDlg;
+};
+
+#endif // INCLUDED_MAIN_FRAME
diff --git a/src/Authoring/Qt3DStudio/MainFrm.qrc b/src/Authoring/Qt3DStudio/MainFrm.qrc
new file mode 100644
index 00000000..a2d5cf59
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/MainFrm.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/">
+ <file>style.qss</file>
+ <file>Palettes/Inspector/DataInputChooser.qml</file>
+ <file alias="TitilliumWeb-Light.ttf">fonts/titilliumweb/TitilliumWeb-Light.ttf</file>
+ <file alias="TitilliumWeb-Regular.ttf">fonts/titilliumweb/TitilliumWeb-Regular.ttf</file>
+ </qresource>
+</RCC>
diff --git a/src/Authoring/Qt3DStudio/MainFrm.ui b/src/Authoring/Qt3DStudio/MainFrm.ui
new file mode 100644
index 00000000..0a94407d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/MainFrm.ui
@@ -0,0 +1,1115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainFrame</class>
+ <widget class="QMainWindow" name="MainFrame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1800</width>
+ <height>1000</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Qt 3D Studio</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>:/images/icon_256x256</normaloff>:/images/icon_256x256</iconset>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <widget class="QLabel" name="infoText">
+ <property name="geometry">
+ <rect>
+ <x>500</x>
+ <y>190</y>
+ <width>301</width>
+ <height>71</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>You have no active presentation.
+Open or Create a presentation using the File menu.
+
+To start working after that, add assets to the
+Project palette using Import functionality.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1800</width>
+ <height>17</height>
+ </rect>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <widget class="QMenu" name="menuRecent_Projects">
+ <property name="title">
+ <string>Recent Presentations</string>
+ </property>
+ <addaction name="actionSomething"/>
+ </widget>
+ <widget class="QMenu" name="menu_New">
+ <property name="title">
+ <string>&amp;New</string>
+ </property>
+ <addaction name="action_New_Project"/>
+ <addaction name="action_New_Presentation"/>
+ </widget>
+ <addaction name="menu_New"/>
+ <addaction name="action_Duplicate_Presentation"/>
+ <addaction name="action_Open"/>
+ <addaction name="menuRecent_Projects"/>
+ <addaction name="action_Revert"/>
+ <addaction name="separator"/>
+ <addaction name="action_Save"/>
+ <addaction name="action_Save_Project_As"/>
+ <addaction name="separator"/>
+ <addaction name="actionImportAssets"/>
+ <addaction name="actionData_Inputs"/>
+ <addaction name="actionData_InputsGenerate"/>
+ <addaction name="separator"/>
+ <addaction name="action_Exit"/>
+ </widget>
+ <widget class="QMenu" name="menu_Edit">
+ <property name="title">
+ <string>&amp;Edit</string>
+ </property>
+ <addaction name="action_Undo"/>
+ <addaction name="action_Redo"/>
+ <addaction name="actionRepeat"/>
+ <addaction name="separator"/>
+ <addaction name="action_Cut"/>
+ <addaction name="action_Copy"/>
+ <addaction name="action_Paste"/>
+ <addaction name="actionPaste_to_Master_Slide"/>
+ <addaction name="action_Duplicate_Object"/>
+ <addaction name="actionDelete"/>
+ <addaction name="actionGroup"/>
+ <addaction name="actionParent"/>
+ <addaction name="actionUnparent"/>
+ <addaction name="separator"/>
+ <addaction name="action_Connect_to_Device"/>
+ <addaction name="separator"/>
+ <addaction name="actionStudio_Preferences"/>
+ <addaction name="actionPresentation_Settings"/>
+ </widget>
+ <widget class="QMenu" name="menu_View">
+ <property name="title">
+ <string>&amp;View</string>
+ </property>
+ <addaction name="actionReset_layout"/>
+ <addaction name="actionFit_Selected"/>
+ <addaction name="actionFit_all"/>
+ <addaction name="actionToggle_hide_unhide_selected"/>
+ <addaction name="actionToggle_hide_unhide_unselected"/>
+ <addaction name="separator"/>
+ <addaction name="actionAction"/>
+ <addaction name="actionBasic_Objects"/>
+ <addaction name="actionInspector"/>
+ <addaction name="actionProject"/>
+ <addaction name="actionSlide"/>
+ <addaction name="actionTimeline"/>
+ <addaction name="actionSceneCamera"/>
+ <addaction name="separator"/>
+ <addaction name="actionBounding_Boxes"/>
+ <addaction name="actionPivot_Point"/>
+ <addaction name="actionWireframe"/>
+ <addaction name="actionTooltips"/>
+ <addaction name="actionFind"/>
+ <addaction name="actionRulers_Guides"/>
+ <addaction name="actionLock_Guides"/>
+ <addaction name="actionClear_Guides"/>
+ <addaction name="actionCamera_Preview"/>
+ <addaction name="actionEdit_Lighting"/>
+ </widget>
+ <widget class="QMenu" name="menu_Timeline">
+ <property name="title">
+ <string>&amp;Timeline</string>
+ </property>
+ <addaction name="actionSet_Changed_Keyframes"/>
+ <addaction name="actionDelete_Selected_Keyframe_s"/>
+ <addaction name="actionSet_Interpolation"/>
+ <addaction name="actionChange_Time_Bar_Color"/>
+ <addaction name="separator"/>
+ <addaction name="actionAutoset_Keyframes"/>
+ </widget>
+ <widget class="QMenu" name="menu_Help">
+ <property name="title">
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="action_Reference_Manual"/>
+ <addaction name="action_Visit_Qt_Web_Site"/>
+ <addaction name="action_About_Qt_3D_Studio"/>
+ <addaction name="action_Open_Tutorial"/>
+ </widget>
+ <addaction name="menu_File"/>
+ <addaction name="menu_Edit"/>
+ <addaction name="menu_View"/>
+ <addaction name="menu_Timeline"/>
+ <addaction name="menu_Help"/>
+ </widget>
+ <widget class="QToolBar" name="toolBar">
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Tool Bar</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>15</height>
+ </size>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="action_Open"/>
+ <addaction name="action_Save"/>
+ <addaction name="separator"/>
+ <addaction name="action_Cut"/>
+ <addaction name="action_Copy"/>
+ <addaction name="action_Paste"/>
+ <addaction name="separator"/>
+ <addaction name="action_Undo"/>
+ <addaction name="action_Redo"/>
+ </widget>
+ <widget class="QToolBar" name="m_ClientToolsBar">
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Client Tools</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>true</bool>
+ </attribute>
+ <addaction name="actionGroup_Select_Tool"/>
+ <addaction name="actionItem_Select_Tool"/>
+ <addaction name="separator"/>
+ <addaction name="actionPosition_Tool"/>
+ <addaction name="actionRotation_Tool"/>
+ <addaction name="actionScale_Tool"/>
+ <addaction name="actionLocal_Global_Manipulators"/>
+ <addaction name="separator"/>
+ <addaction name="actionAutoset_Keyframes"/>
+ </widget>
+ <widget class="CEditCameraBar" name="m_EditCamerasBar">
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Edit Cameras</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="separator"/>
+ <addaction name="actionFit_Selected"/>
+ <addaction name="actionPan_Tool"/>
+ <addaction name="actionZoom_Tool"/>
+ <addaction name="actionOrbit_Tool"/>
+ <addaction name="separator"/>
+ <addaction name="actionShading_Mode"/>
+ <addaction name="actionWireframe"/>
+ </widget>
+ <widget class="QToolBar" name="m_PlaybackToolbar">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Playback</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionFilterVariants"/>
+ <addaction name="actionPreview"/>
+ <addaction name="actionRemote_Preview"/>
+ <addaction name="actionPreviewQt3DRuntime"/>
+ </widget>
+ <action name="action_Reference_Manual">
+ <property name="text">
+ <string>&amp;Reference Manual...</string>
+ </property>
+ <property name="toolTip">
+ <string>Reference Manual</string>
+ </property>
+ <property name="shortcut">
+ <string>F1</string>
+ </property>
+ </action>
+ <action name="action_Visit_Qt_Web_Site">
+ <property name="text">
+ <string>&amp;Visit Qt 3D Studio Page...</string>
+ </property>
+ </action>
+ <action name="action_About_Qt_3D_Studio">
+ <property name="text">
+ <string>&amp;About Qt 3D Studio...</string>
+ </property>
+ </action>
+ <action name="action_Open_Tutorial">
+ <property name="text">
+ <string>&amp;Show Welcome Screen...</string>
+ </property>
+ </action>
+ <action name="actionSet_Changed_Keyframes">
+ <property name="text">
+ <string>Set Changed Keyframes</string>
+ </property>
+ <property name="toolTip">
+ <string>Set Changed Keyframes</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+K</string>
+ </property>
+ </action>
+ <action name="actionDelete_Selected_Keyframe_s">
+ <property name="text">
+ <string>Delete Selected Keyframe(s)</string>
+ </property>
+ <property name="shortcut">
+ <string>Del</string>
+ </property>
+ </action>
+ <action name="actionSet_Interpolation">
+ <property name="text">
+ <string>Set Interpolation...</string>
+ </property>
+ <property name="shortcut">
+ <string>I</string>
+ </property>
+ </action>
+ <action name="actionChange_Time_Bar_Color">
+ <property name="text">
+ <string>Change Time Bar Color...</string>
+ </property>
+ </action>
+ <action name="actionAction">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Action</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+A</string>
+ </property>
+ </action>
+ <action name="actionBasic_Objects">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Basic Objects</string>
+ </property>
+ <property name="toolTip">
+ <string>Show Basic Objects Palette</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+B</string>
+ </property>
+ </action>
+ <action name="actionInspector">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Inspector</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+I</string>
+ </property>
+ </action>
+ <action name="actionProject">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Project</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+P</string>
+ </property>
+ </action>
+ <action name="actionSlide">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Slide</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+D</string>
+ </property>
+ </action>
+ <action name="actionTimeline">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Timeline</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+T</string>
+ </property>
+ </action>
+ <action name="actionBounding_Boxes">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Bounding Boxes</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+B</string>
+ </property>
+ </action>
+ <action name="actionPivot_Point">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Pivot Point</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Alt+P</string>
+ </property>
+ </action>
+ <action name="actionTooltips">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Tooltips</string>
+ </property>
+ </action>
+ <action name="actionRulers_Guides">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Rulers &amp;&amp; Guides</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+R</string>
+ </property>
+ </action>
+ <action name="actionLock_Guides">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Lock Guides</string>
+ </property>
+ <property name="shortcut">
+ <string>Alt+Shift+R</string>
+ </property>
+ </action>
+ <action name="actionClear_Guides">
+ <property name="text">
+ <string>Clear Guides</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Alt+R</string>
+ </property>
+ </action>
+ <action name="action_Undo">
+ <property name="text">
+ <string>&amp;Undo</string>
+ </property>
+ <property name="toolTip">
+ <string>Undo</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Z</string>
+ </property>
+ </action>
+ <action name="action_Redo">
+ <property name="text">
+ <string>&amp;Redo</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Y</string>
+ </property>
+ </action>
+ <action name="action_Cut">
+ <property name="text">
+ <string>Cu&amp;t</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+X</string>
+ </property>
+ </action>
+ <action name="action_Copy">
+ <property name="text">
+ <string>&amp;Copy</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+C</string>
+ </property>
+ </action>
+ <action name="action_Paste">
+ <property name="text">
+ <string>P&amp;aste</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+V</string>
+ </property>
+ </action>
+ <action name="action_Duplicate_Object">
+ <property name="text">
+ <string>&amp;Duplicate</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+D</string>
+ </property>
+ </action>
+ <action name="actionStudio_Preferences">
+ <property name="text">
+ <string>Studio &amp;Preferences...</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+U</string>
+ </property>
+ <property name="menuRole">
+ <enum>QAction::PreferencesRole</enum>
+ </property>
+ </action>
+ <action name="actionPresentation_Settings">
+ <property name="text">
+ <string>Presentation &amp;Settings...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+U</string>
+ </property>
+ </action>
+ <action name="action_Open">
+ <property name="text">
+ <string>&amp;Open...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="action_Save">
+ <property name="text">
+ <string>&amp;Save Presentation</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="action_Save_Project_As">
+ <property name="text">
+ <string>Save Project &amp;As...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+S</string>
+ </property>
+ </action>
+ <action name="action_Duplicate_Presentation">
+ <property name="text">
+ <string>Duplicate Presentation...</string>
+ </property>
+ </action>
+ <action name="action_Revert">
+ <property name="text">
+ <string>&amp;Revert to Last Save</string>
+ </property>
+ </action>
+ <action name="action_Connect_to_Device">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Connect to Device...</string>
+ </property>
+ </action>
+ <action name="action_Exit">
+ <property name="text">
+ <string>E&amp;xit</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionSomething">
+ <property name="text">
+ <string>Something</string>
+ </property>
+ </action>
+ <action name="actionGroup_Select_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-00.png</normaloff>:/images/client_tools_hi_color-00.png</iconset>
+ </property>
+ <property name="text">
+ <string>Group Select Tool</string>
+ </property>
+ <property name="toolTip">
+ <string>Select Group (Q toggle)</string>
+ </property>
+ </action>
+ <action name="actionItem_Select_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-01.png</normaloff>:/images/client_tools_hi_color-01.png</iconset>
+ </property>
+ <property name="text">
+ <string>Item Select Tool</string>
+ </property>
+ <property name="toolTip">
+ <string>Select Item (Q toggle)</string>
+ </property>
+ </action>
+ <action name="actionRotation_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-04.png</normaloff>:/images/client_tools_hi_color-04.png</iconset>
+ </property>
+ <property name="text">
+ <string>Rotation Tool</string>
+ </property>
+ <property name="toolTip">
+ <string>Rotate current selection (E)</string>
+ </property>
+ <property name="shortcut">
+ <string>E</string>
+ </property>
+ </action>
+ <action name="actionPosition_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-02.png</normaloff>:/images/client_tools_hi_color-02.png</iconset>
+ </property>
+ <property name="text">
+ <string>Position Tool</string>
+ </property>
+ <property name="toolTip">
+ <string>Move current selection (W)</string>
+ </property>
+ <property name="shortcut">
+ <string>W</string>
+ </property>
+ </action>
+ <action name="actionScale_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-03.png</normaloff>:/images/client_tools_hi_color-03.png</iconset>
+ </property>
+ <property name="text">
+ <string>Scale Tool</string>
+ </property>
+ <property name="toolTip">
+ <string>Scale current selection (R)</string>
+ </property>
+ <property name="shortcut">
+ <string>R</string>
+ </property>
+ </action>
+ <action name="actionLocal_Global_Manipulators">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-05.png</normaloff>:/images/client_tools_hi_color-05.png</iconset>
+ </property>
+ <property name="text">
+ <string>Local/Global Manipulators</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle Manipulators to work in global/local space (Y)</string>
+ </property>
+ <property name="shortcut">
+ <string>Y</string>
+ </property>
+ </action>
+ <action name="actionFit_Selected">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-00.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-00_disabled.png</disabledoff>:/images/editcamera_tools_hi-00.png</iconset>
+ </property>
+ <property name="text">
+ <string>Fit selected</string>
+ </property>
+ <property name="toolTip">
+ <string>Fit Selected (F)</string>
+ </property>
+ <property name="shortcut">
+ <string>F</string>
+ </property>
+ <property name="iconVisibleInMenu">
+ <bool>false</bool>
+ </property>
+ </action>
+ <action name="actionPan_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-01.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-01_disabled.png</disabledoff>:/images/editcamera_tools_hi-01.png</iconset>
+ </property>
+ <property name="text">
+ <string>Pan Tool (Middle Mouse Drag)</string>
+ </property>
+ <property name="toolTip">
+ <string>Pan current edit camera</string>
+ </property>
+ </action>
+ <action name="actionZoom_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-02.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-02_disabled.png</disabledoff>:/images/editcamera_tools_hi-02.png</iconset>
+ </property>
+ <property name="text">
+ <string>Zoom Tool (Mouse Wheel)</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom current edit camera</string>
+ </property>
+ </action>
+ <action name="actionOrbit_Tool">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-03.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-03_disabled.png</disabledoff>:/images/editcamera_tools_hi-03.png</iconset>
+ </property>
+ <property name="text">
+ <string>Orbit Tool (Alt+Middle Mouse Drag)</string>
+ </property>
+ <property name="toolTip">
+ <string>Orbit current edit camera</string>
+ </property>
+ </action>
+ <action name="actionShading_Mode">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-04.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-04_disabled.png</disabledoff>:/images/editcamera_tools_hi-04.png</iconset>
+ </property>
+ <property name="text">
+ <string>Shading Mode</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle Shading Mode (L)</string>
+ </property>
+ <property name="shortcut">
+ <string>L</string>
+ </property>
+ </action>
+ <action name="actionPreview">
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/preview.png</normaloff>
+ <disabledoff>:/images/preview-disabled.png</disabledoff>
+ <disabledon>:/images/preview-disabled.png</disabledon>:/images/preview.png</iconset>
+ </property>
+ <property name="text">
+ <string>Preview with OpenGL Runtime Viewer</string>
+ </property>
+ <property name="toolTip">
+ <string>Preview with OpenGL Runtime Viewer (F5)</string>
+ </property>
+ <property name="shortcut">
+ <string>F5</string>
+ </property>
+ </action>
+ <action name="actionFilterVariants">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/filter.png</normaloff>
+ <disabledoff>:/images/filter-disabled.png</disabledoff>:/images/filter.png</iconset>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="toolTip">
+ <string>Filter variants (F7)</string>
+ </property>
+ <property name="shortcut">
+ <string>F7</string>
+ </property>
+ </action>
+ <action name="actionSubpresentations">
+ <property name="text">
+ <string>Sub-presentations...</string>
+ </property>
+ </action>
+ <action name="actionWireframe">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/editcamera_tools_hi-05.png</normaloff>
+ <disabledoff>:/images/editcamera_tools_hi-05_disabled.png</disabledoff>:/images/editcamera_tools_hi-05.png</iconset>
+ </property>
+ <property name="text">
+ <string>Wireframe</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle Wireframe (M)</string>
+ </property>
+ <property name="shortcut">
+ <string>M</string>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ <property name="iconVisibleInMenu">
+ <bool>false</bool>
+ </property>
+ </action>
+ <action name="actionAutoset_Keyframes">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/client_tools_hi_color-06.png</normaloff>:/images/client_tools_hi_color-06.png</iconset>
+ </property>
+ <property name="text">
+ <string>Toggle Autoset Keyframes</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle Autoset Keyframes (K)</string>
+ </property>
+ <property name="shortcut">
+ <string>K</string>
+ </property>
+ <property name="iconVisibleInMenu">
+ <bool>false</bool>
+ </property>
+ </action>
+ <action name="actionPreviewQt3DRuntime">
+ <property name="icon">
+ <iconset resource="images.qrc">
+ <normaloff>:/images/playback_tools_play.png</normaloff>:/images/playback_tools_play.png</iconset>
+ </property>
+ <property name="text">
+ <string>Preview with Qt3D Runtime Viewer</string>
+ </property>
+ <property name="iconText">
+ <string>Preview with Qt3D Runtime Viewer</string>
+ </property>
+ <property name="toolTip">
+ <string>Preview with Qt3D Runtime Viewer</string>
+ </property>
+ </action>
+ <action name="actionImportAssets">
+ <property name="text">
+ <string>Import...</string>
+ </property>
+ <property name="iconText">
+ <string>Import Assets or Presentations</string>
+ </property>
+ <property name="toolTip">
+ <string>Import Assets or Presentations</string>
+ </property>
+ </action>
+ <action name="actionData_Inputs">
+ <property name="text">
+ <string>Data Inputs...</string>
+ </property>
+ </action>
+ <action name="actionRemote_Preview">
+ <property name="icon">
+ <iconset>
+ <normalon>:/images/preview-remote.png</normalon>
+ <disabledoff>:/images/preview-remote-disabled.png</disabledoff>
+ <disabledon>:/images/preview-remote-disabled.png</disabledon>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Remote Preview</string>
+ </property>
+ <property name="toolTip">
+ <string>Preview on Remote Device (F6)</string>
+ </property>
+ <property name="shortcut">
+ <string>F6</string>
+ </property>
+ </action>
+ <action name="actionDelete">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete</string>
+ </property>
+ <property name="shortcut">
+ <string>Del</string>
+ </property>
+ <property name="shortcutContext">
+ <enum>Qt::ApplicationShortcut</enum>
+ </property>
+ </action>
+ <action name="actionGroup">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Group</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+G</string>
+ </property>
+ </action>
+ <action name="actionRepeat">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Repeat</string>
+ </property>
+ <property name="shortcut">
+ <string>G</string>
+ </property>
+ </action>
+ <action name="actionParent">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Parent</string>
+ </property>
+ <property name="shortcut">
+ <string>P</string>
+ </property>
+ </action>
+ <action name="actionUnparent">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Unparent</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+P</string>
+ </property>
+ </action>
+ <action name="actionPaste_to_Master_Slide">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Paste to Master Slide</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Alt+V</string>
+ </property>
+ <property name="shortcutContext">
+ <enum>Qt::ApplicationShortcut</enum>
+ </property>
+ </action>
+ <action name="actionReset_layout">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Reset layout</string>
+ </property>
+ </action>
+ <action name="actionFit_all">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Fit all</string>
+ </property>
+ <property name="shortcut">
+ <string>A</string>
+ </property>
+ </action>
+ <action name="actionToggle_hide_unhide_selected">
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Hide/unhide selected</string>
+ </property>
+ <property name="shortcut">
+ <string>H</string>
+ </property>
+ </action>
+ <action name="actionFind">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Find</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+F</string>
+ </property>
+ </action>
+ <action name="actionToggle_hide_unhide_unselected">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Hide/unhide unselected</string>
+ </property>
+ </action>
+ <action name="action_New_Presentation">
+ <property name="text">
+ <string>&amp;Presentation...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ <action name="action_New_Project">
+ <property name="text">
+ <string>Project...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+N</string>
+ </property>
+ </action>
+ <action name="actionCamera_Preview">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Scene Preview</string>
+ </property>
+ <property name="toolTip">
+ <string>Show Scene Camera Preview in Edit Mode</string>
+ </property>
+ </action>
+ <action name="actionEdit_Lighting">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Disable Scene Lighting</string>
+ </property>
+ <property name="toolTip">
+ <string>Disable Lighting from Scene Lights in Edit Views</string>
+ </property>
+ </action>
+ <action name="actionSceneCamera">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Scene Camera</string>
+ </property>
+ <property name="toolTip">
+ <string>Scene Camera</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+F</string>
+ </property>
+ </action>
+ <action name="actionData_InputsGenerate">
+ <property name="text">
+ <string>Copy QML code for DataInputs/Outputs</string>
+ </property>
+ <property name="toolTip">
+ <string>Copy QML code for DataInput and DataOutput elements to clipboard</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>CEditCameraBar</class>
+ <extends>QToolBar</extends>
+ <header>Qt3DStudio/UI/EditCameraBar.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="images.qrc"/>
+ </resources>
+ <connections/>
+</ui>
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>
diff --git a/src/Authoring/Qt3DStudio/PreviewHelper.cpp b/src/Authoring/Qt3DStudio/PreviewHelper.cpp
new file mode 100644
index 00000000..56d08804
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/PreviewHelper.cpp
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "PreviewHelper.h"
+#include "StudioApp.h"
+#include "Dialogs.h"
+#include "Dispatch.h"
+#include "Doc.h"
+#include "StudioPreferences.h"
+#include "StudioProjectSettings.h"
+#include "Core.h"
+#include "Views.h"
+#include "MainFrm.h"
+#include "Qt3DSFileTools.h"
+
+#include <QtWidgets/qinputdialog.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtCore/qprocess.h>
+#include <ProjectFile.h>
+
+#include "remotedeploymentsender.h"
+
+// Amount of open preview viewer processes
+int CPreviewHelper::s_previewViewerCount = 0;
+
+// Full path to uip document
+QString CPreviewHelper::s_documentPath;
+
+//=============================================================================
+/**
+ * Callback for previewing a presentation.
+ */
+void CPreviewHelper::OnPreview(const QString &viewerExeName)
+{
+ Q3DStudio::CBuildConfigurations &theConfigurations =
+ g_StudioApp.GetCore()->GetBuildConfigurations();
+ Q3DStudio::CBuildConfiguration *theBuildConfiguration =
+ theConfigurations.GetConfiguration(CStudioPreferences::GetPreviewConfig());
+ if (theBuildConfiguration)
+ PreviewViaConfig(theBuildConfiguration, EXECMODE_PREVIEW, viewerExeName);
+}
+
+//=============================================================================
+/**
+ * Callback for deploying a presentation.
+ */
+void CPreviewHelper::OnDeploy(RemoteDeploymentSender &project)
+{
+ Q3DStudio::CBuildConfigurations &theConfigurations =
+ g_StudioApp.GetCore()->GetBuildConfigurations();
+ Q3DStudio::CBuildConfiguration *theBuildConfiguration =
+ theConfigurations.GetConfiguration(CStudioPreferences::GetPreviewConfig());
+ if (theBuildConfiguration) {
+ // ItemDataPtr != nullptr ==> Build configurations specified NANT pipeline exporter
+ PreviewViaConfig(theBuildConfiguration, EXECMODE_DEPLOY, QString(), &project);
+ }
+}
+
+//=============================================================================
+/**
+ * Previewing a presentation using the build configurations loaded.
+ * This involves 2 steps:
+ * 1 Export the presentation using the specified exporter.
+ * 2 Viewing the exported content following the command specified in the configuration.
+ */
+void CPreviewHelper::PreviewViaConfig(Q3DStudio::CBuildConfiguration *inSelectedConfig,
+ EExecMode inMode, const QString &viewerExeName,
+ RemoteDeploymentSender *project)
+{
+ CCore *theCore = g_StudioApp.GetCore();
+ QString prvPath = theCore->getProjectFile().createPreview();
+ s_documentPath = theCore->GetDoc()->GetDocumentPath();
+ try {
+ DoPreviewViaConfig(inSelectedConfig, prvPath, inMode, viewerExeName, project);
+ } catch (...) {
+ theCore->GetDispatch()->FireOnProgressEnd();
+ g_StudioApp.GetDialogs()->DisplaySaveReadOnlyFailed(prvPath);
+ }
+}
+
+QString CPreviewHelper::getViewerFilePath(const QString &exeName)
+{
+ using namespace Q3DStudio;
+ CFilePath currentPath(Qt3DSFile::GetApplicationDirectory());
+ CFilePath viewerDir(QApplication::applicationDirPath());
+
+ QString viewerFile;
+#ifdef Q_OS_WIN
+ if (!viewerDir.IsDirectory())
+ viewerDir = currentPath.GetDirectory(); // Developing directory
+ viewerFile = QStringLiteral("%1.exe").arg(exeName);
+
+ QString viewer = viewerDir.filePath() + QStringLiteral("/") + viewerFile;
+ if (!QFileInfo(viewer).exists()
+ && exeName == QLatin1String("q3dsviewer")) {
+ viewer = viewerDir.filePath() + QStringLiteral("/../src/Runtime/qt3d-runtime/bin/")
+ + viewerFile;
+ }
+#else
+#ifdef Q_OS_MACOS
+ // Check if we're looking for Viewer 2.x that has a different development
+ // time path for the executable
+ QString viewerDevPath;
+ if (exeName == QLatin1String("q3dsviewer"))
+ viewerDevPath = QStringLiteral("../src/Runtime/qt3d-runtime/bin/");
+
+ // Name of the executable file on macOS
+ viewerFile = QStringLiteral("%1.app/Contents/MacOS/%1").arg(exeName);
+
+ // Executable directory is three steps above the directory of studio executable
+ QString executableDir = viewerDir.filePath() + QStringLiteral("/../../../");
+
+ // Formulate the expected path to the viewer in development environment
+ QString viewer = executableDir + viewerDevPath + viewerFile;
+
+ // If not in development environment, expect viewer to be in same directory
+ if (!QFileInfo(viewer).exists())
+ viewer = executableDir + viewerFile;
+
+#else
+ if (!viewerDir.IsDirectory())
+ viewerDir = currentPath.GetDirectory(); // Developing directory
+
+ viewerFile = exeName;
+
+ QString viewer = viewerDir.filePath() + QStringLiteral("/") + viewerFile;
+ if (!QFileInfo(viewer).exists()
+ && exeName == QLatin1String("q3dsviewer")) {
+ viewer = viewerDir.filePath() + QStringLiteral("/../src/Runtime/qt3d-runtime/bin/")
+ + viewerFile;
+ }
+#endif
+#endif
+
+ return viewer;
+}
+
+void CPreviewHelper::previewClosed()
+{
+ if (CPreviewHelper::s_previewViewerCount > 0)
+ CPreviewHelper::s_previewViewerCount--;
+}
+
+void CPreviewHelper::cleanupProcess(QProcess *p, QString *docPath)
+{
+ previewClosed();
+ p->disconnect();
+ if (CPreviewHelper::s_previewViewerCount == 0) {
+ // Delete preview files when no viewers are open
+ if (docPath->endsWith(QLatin1String("_@preview@.uia"))) {
+ QString uipPreviewPath = s_documentPath
+ .replace(QLatin1String(".uip"), QLatin1String("_@preview@.uip"));
+ QFile(uipPreviewPath).remove(); // remove uip preview (if exists)
+ QFile(*docPath).remove(); // remove uia preview
+ } else if (docPath->endsWith(QLatin1String("_@preview@.uip"))) {
+ QFile(*docPath).remove(); // remove uip preview (if exists)
+ }
+ }
+ if (p->state() == QProcess::Running) {
+ p->terminate();
+ p->waitForFinished(5000); // To avoid warning about deleting a running process
+ }
+ p->deleteLater();
+ delete docPath;
+}
+
+void CPreviewHelper::DoPreviewViaConfig(Q3DStudio::CBuildConfiguration * /*inSelectedConfig*/,
+ const QString &inDocumentFile,
+ EExecMode inMode, const QString &viewerExeName,
+ RemoteDeploymentSender *project)
+{
+ using namespace Q3DStudio;
+
+ if (inMode == EXECMODE_DEPLOY) {
+ Q_ASSERT(project);
+ project->streamProject(inDocumentFile);
+ } else if (inMode == EXECMODE_PREVIEW
+ && CStudioPreferences::GetPreviewProperty("PLATFORM") == "PC") {
+ // Quick Preview on PC without going via NANT
+ QString theCommandStr = getViewerFilePath(viewerExeName);
+ QString *pDocStr = new QString(inDocumentFile);
+
+ CPreviewHelper::s_previewViewerCount++;
+ QProcess *p = new QProcess;
+ QMetaObject::Connection *connection = new QMetaObject::Connection(
+ QObject::connect(qApp, &QApplication::aboutToQuit, [p, pDocStr](){
+ // connection object is never destroyed, but it doesn't matter as application is
+ // quitting anyway.
+ cleanupProcess(p, pDocStr);
+ }));
+ auto finished
+ = static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished);
+ QObject::connect(p, finished, [p, pDocStr, connection](){
+ // Disconnect the other connection to avoid duplicate cleanup
+ QObject::disconnect(*connection);
+ delete connection;
+ cleanupProcess(p, pDocStr);
+ });
+
+ QStringList args {*pDocStr};
+ QString variantsArg = g_StudioApp.GetViews()->getMainFrame()->getVariantsFilterStr();
+ if (!variantsArg.isEmpty())
+ args << "-v" << variantsArg;
+
+ p->start(theCommandStr, args);
+
+ if (!p->waitForStarted()) {
+ previewClosed();
+ QMessageBox::critical(nullptr, QObject::tr("Error Launching Viewer"),
+ QObject::tr("'%1' failed with error: '%2'")
+ .arg(theCommandStr).arg(p->errorString()));
+ delete p;
+ return;
+ }
+ }
+}
diff --git a/src/Authoring/Qt3DStudio/PreviewHelper.h b/src/Authoring/Qt3DStudio/PreviewHelper.h
new file mode 100644
index 00000000..7755b2d8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/PreviewHelper.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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_PREVIEW_HELPER
+#define INCLUDED_PREVIEW_HELPER 1
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "Qt3DSFile.h"
+#include "Qt3DSString.h"
+#include "remotedeploymentsender.h"
+
+//==============================================================================
+// Forwards
+//==============================================================================
+class CHotKeys;
+
+namespace Q3DStudio {
+class CBuildConfiguration;
+}
+QT_FORWARD_DECLARE_CLASS(QProcess)
+
+//==============================================================================
+/**
+ * @class CPreviewHelper
+ */
+class CPreviewHelper
+{
+public:
+ enum EExecMode {
+ EXECMODE_PREVIEW, ///< Preview
+ EXECMODE_DEPLOY, ///< Deploy
+ };
+
+public:
+ static void OnPreview(const QString &viewerExeName);
+ static void OnDeploy(RemoteDeploymentSender &project);
+ static void PreviewViaConfig(Q3DStudio::CBuildConfiguration *inSelectedConfig,
+ EExecMode inMode, const QString &viewerExeName,
+ RemoteDeploymentSender *project = 0);
+ static void DoPreviewViaConfig(Q3DStudio::CBuildConfiguration *inSelectedConfig,
+ const QString &inDocumentFile,
+ EExecMode inMode, const QString &viewerExeName,
+ RemoteDeploymentSender *project = 0);
+ static QString getViewerFilePath(const QString &exeName);
+
+protected:
+ static void cleanupProcess(QProcess *p, QString *docPath);
+ static void previewClosed();
+
+private:
+ static int s_previewViewerCount;
+ static QString s_documentPath;
+
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Qt3DStudio.pro b/src/Authoring/Qt3DStudio/Qt3DStudio.pro
new file mode 100644
index 00000000..5a7af2a3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Qt3DStudio.pro
@@ -0,0 +1,530 @@
+TEMPLATE = app
+TARGET = Qt3DStudio
+include(../commoninclude.pri)
+include($$OUT_PWD/../qtAuthoring-config.pri)
+include(../../shared/qtsingleapplication/qtsingleapplication.pri)
+
+BREAKPAD_SOURCE_DIR = $$(BREAKPAD_SOURCE_DIR)
+!isEmpty(BREAKPAD_SOURCE_DIR):exists($$BREAKPAD_SOURCE_DIR) {
+ include(../../shared/qt-breakpad/qtbreakpad.pri)
+}
+
+INCLUDEPATH += $$OUT_PWD/..
+
+CONFIG += nostrictstrings
+
+DEFINES += _UNICODE UNICODE QT3DS_AUTHORING _AFXDLL \
+ PCRE_STATIC EASTL_MINMAX_ENABLED=0 \
+ EASTL_NOMINMAX=0 DOM_DYNAMIC
+
+win: QMAKE_LFLAGS += /MANIFEST /ENTRY:"wWinMainCRTStartup"
+
+QT += core gui xml openglextensions
+QT += qml quick widgets quickwidgets network
+QT += quick-private
+
+# Hack to add module includes as syncqt doesn't work in our case
+INCLUDEPATH += $$RUNTIME_INCLUDEDIR
+
+INCLUDEPATH += \
+ Application \
+ Controls \
+ DragAndDrop \
+ Palettes \
+ Palettes/Action \
+ Palettes/Action/ActionParamRow \
+ Palettes/BasicObjects \
+ Palettes/Inspector \
+ Palettes/Master \
+ Palettes/Progress \
+ Palettes/Project \
+ Palettes/Slide \
+ Palettes/Timeline \
+ Palettes/TimelineGraphicsView \
+ Palettes/TimelineGraphicsView/ui \
+ Palettes/scenecamera \
+ Render \
+ UI \
+ Utils \
+ Workspace \
+ Workspace/Views \
+ . \
+ .. \
+ ../QT3DSIMP/Qt3DSImportLib \
+ ../QT3DSIMP/Qt3DSImportSGTranslation \
+ ../Common/Code/Thread \
+ ../Common/Code/IO \
+ ../Common/Code \
+ ../Common/Code/Exceptions \
+ ../Common/Code/_Win32/Include \
+ ../Common/Code/_Win32 \
+ ../Common/Code/Graph \
+ ../Common/Code/Report \
+ ../Common/Code/Memory \
+ ../Client/Code/Core/Utility \
+ ../Client/Code/Core/Types \
+ ../Client/Code/Core/Commands \
+ ../Client/Code/Core/Core \
+ ../Client/Code/Core \
+ ../Client/Code/Core/Doc \
+ ../Client/Code/Core/Doc/ClientDataModelBridge \
+ ../Client/Code/Shared \
+ ../Client/Code/Shared/Log \
+ ../../Runtime/ogl-runtime/src/importlib \
+ ../../Runtime/ogl-runtime/src/dm/systems \
+ ../../Runtime/ogl-runtime/src/render \
+ ../../Runtime/ogl-runtime/src/foundation \
+ ../../Runtime/ogl-runtime/src/runtimerender \
+ ../../Runtime/ogl-runtime/src/runtimerender/graphobjects \
+ ../../Runtime/ogl-runtime/src/runtimerender/resourcemanager \
+ ../../Runtime/ogl-runtime/src/event \
+ ../../Runtime/ogl-runtime/src/3rdparty/EASTL/UnknownVersion/include \
+ ../../Runtime/ogl-runtime/src/3rdparty/color \
+ ../../Runtime/ogl-runtime/src/qmlstreamer
+
+linux {
+ BEGIN_ARCHIVE = -Wl,--whole-archive
+ END_ARCHIVE = -Wl,--no-whole-archive
+}
+
+STATICRUNTIME = \
+ $$BEGIN_ARCHIVE \
+ -lEASTL$$qtPlatformTargetSuffix() \
+ -lpcre$$qtPlatformTargetSuffix() \
+ -lTinyXML$$qtPlatformTargetSuffix() \
+ -lColladaDOM$$qtPlatformTargetSuffix() \
+ -lQT3DSDM$$qtPlatformTargetSuffix() \
+ -lCommonLib$$qtPlatformTargetSuffix() \
+ -lCoreLib$$qtPlatformTargetSuffix() \
+ $$END_ARCHIVE
+
+# On non-windows systems link the whole static archives and do not put them
+# in the prl file to prevent them being linked again by targets that depend
+# upon this shared library
+!win32 {
+ QMAKE_LFLAGS += $$STATICRUNTIME
+} else {
+ DEFINES += WIN32_LEAN_AND_MEAN
+ LIBS += $$STATICRUNTIME
+ !mingw: QMAKE_LFLAGS += /NODEFAULTLIB:tinyxml.lib
+}
+
+!macos {
+ LIBS += -lQt5Studio3D$$qtPlatformTargetSuffix()
+}
+
+LIBS += \
+ -lqt3dsruntimestatic$$qtPlatformTargetSuffix() \
+ -lqt3dsqmlstreamer$$qtPlatformTargetSuffix() \
+ $$QMAKE_LIBS_FBX
+
+linux {
+ LIBS += \
+ -ldl \
+ -lEGL
+}
+
+win: PRECOMPILED_HEADER = ../Common/Code/Qt3DSCommonPrecompile.h
+
+HEADERS += \
+ MainFrm.h \
+ Application/AboutDlg.h \
+ Application/DataInputDlg.h \
+ Application/DataInputListDlg.h \
+ Application/DataInputSelectModel.h \
+ Application/DataInputSelectView.h \
+ Application/DurationEditDlg.h \
+ Application/StudioApp.h \
+ Application/StudioTutorialWidget.h \
+ Application/TimeEditDlg.h \
+ Application/TimeEnums.h \
+ Controls/ClickableLabel.h \
+ Controls/WidgetControl.h \
+ DragAndDrop/DropProxy.h \
+ Palettes/PaletteManager.h \
+ Palettes/Action/ActionContextMenu.h \
+ Palettes/Action/ActionModel.h \
+ Palettes/Action/ActionView.h \
+ Palettes/Action/EventsBrowserView.h \
+ Palettes/Action/EventsModel.h \
+ Palettes/Action/PropertyModel.h \
+ Palettes/BasicObjects/BasicObjectsModel.h \
+ Palettes/BasicObjects/BasicObjectsView.h \
+ Palettes/Inspector/ChooserModelBase.h \
+ Palettes/Inspector/FileChooserModel.h \
+ Palettes/Inspector/FileChooserView.h \
+ Palettes/Inspector/ImageChooserModel.h \
+ Palettes/Inspector/ImageChooserView.h \
+ Palettes/Inspector/InspectorControlModel.h \
+ Palettes/Inspector/InspectorControlView.h \
+ Palettes/Inspector/MeshChooserModel.h \
+ Palettes/Inspector/MeshChooserView.h \
+ Palettes/Inspector/MouseHelper.h \
+ Palettes/Inspector/ObjectBrowserView.h \
+ Palettes/Inspector/ObjectListModel.h \
+ Palettes/Inspector/TabOrderHandler.h \
+ Palettes/Inspector/TextureChooserView.h \
+ Palettes/Project/ProjectContextMenu.h \
+ Palettes/Project/ProjectFileSystemModel.h \
+ Palettes/Project/ProjectView.h \
+ Palettes/Slide/SlideContextMenu.h \
+ Palettes/Slide/SlideModel.h \
+ Palettes/Slide/SlideView.h \
+ Palettes/Timeline/Bindings/ITimelineItem.h \
+ Palettes/Timeline/Bindings/ITimelineItemBinding.h \
+ Palettes/Timeline/Bindings/ITimelineItemProperty.h \
+ Palettes/Timeline/Bindings/ITimelineTimebar.h \
+ Palettes/Timeline/Bindings/IBreadCrumbProvider.h \
+ Palettes/TimelineGraphicsView/Keyframe.h \
+ Palettes/TimelineGraphicsView/KeyframeManager.h \
+ Palettes/TimelineGraphicsView/RowManager.h \
+ Palettes/TimelineGraphicsView/RowMover.h \
+ Palettes/TimelineGraphicsView/SelectionRect.h \
+ Palettes/TimelineGraphicsView/TimelineConstants.h \
+ Palettes/TimelineGraphicsView/TimelineControl.h \
+ Palettes/TimelineGraphicsView/TimelineGraphicsScene.h \
+ Palettes/TimelineGraphicsView/TimelineSplitter.h \
+ Palettes/TimelineGraphicsView/TimelineWidget.h \
+ Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h \
+ Palettes/TimelineGraphicsView/ui/NavigationBar.h \
+ Palettes/TimelineGraphicsView/ui/NavigationBarItem.h \
+ Palettes/TimelineGraphicsView/ui/PlayHead.h \
+ Palettes/TimelineGraphicsView/ui/RowTimeline.h \
+ Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h \
+ Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h \
+ Palettes/TimelineGraphicsView/ui/RowTree.h \
+ Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h \
+ Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h \
+ Palettes/TimelineGraphicsView/ui/Ruler.h \
+ Palettes/TimelineGraphicsView/ui/TimelineItem.h \
+ Palettes/TimelineGraphicsView/ui/TimelineToolbar.h \
+ Palettes/TimelineGraphicsView/ui/TreeHeader.h \
+ Palettes/TimelineGraphicsView/ui/TreeHeaderView.h \
+ PreviewHelper.h \
+ remotedeploymentsender.h \
+ Render/StudioGradientWidget.h \
+ Render/StudioVisualAidWidget.h \
+ Render/StudioSubPresentationRenderer.h \
+ UI/EditCameraBar.h \
+ UI/GLVersionDlg.h \
+ UI/InterpolationDlg.h \
+ UI/PlayerContainerWnd.h \
+ UI/PlayerWnd.h \
+ UI/RecentItems.h \
+ UI/ResetKeyframeValuesDlg.h \
+ UI/SceneView.h \
+ UI/StartupDlg.h \
+ UI/StudioAppPrefsPage.h \
+ UI/StudioPreferencesPropSheet.h \
+ UI/StudioProjectSettingsPage.h \
+ Workspace/Dialogs.h \
+ ../Common/Code/Graph/GraphPosition.h \
+ Palettes/Project/EditPresentationIdDlg.h \
+ Application/ProjectFile.h \
+ Application/PresentationFile.h \
+ Palettes/Project/ChooseImagePropertyDlg.h \
+ Application/StudioTutorialPageIndicator.h \
+ Palettes/Inspector/MaterialRefView.h \
+ Palettes/scenecamera/scenecameraview.h \
+ Palettes/scenecamera/scenecamerascrollarea.h \
+ Palettes/scenecamera/scenecameraglwidget.h \
+ Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h \
+ Palettes/Inspector/VariantsGroupModel.h \
+ Palettes/Inspector/VariantsTagModel.h \
+ Palettes/Inspector/VariantTagDialog.h \
+ Application/FilterVariantsDlg.h \
+ Application/FilterVariantsModel.h \
+ Utils/QmlUtils.h
+
+FORMS += \
+ MainFrm.ui \
+ Application/TimeEditDlg.ui \
+ Application/DataInputDlg.ui \
+ Application/DataInputListDlg.ui \
+ Application/DurationEditDlg.ui \
+ Application/StudioTutorialWidget.ui \
+ Application/AboutDlg.ui \
+ Palettes/Progress/ProgressDlg.ui \
+ UI/StudioAppPrefsPage.ui \
+ UI/StudioPreferencesPropSheet.ui \
+ UI/StudioProjectSettingsPage.ui \
+ UI/InterpolationDlg.ui \
+ UI/ResetKeyframeValuesDlg.ui \
+ UI/GLVersionDlg.ui \
+ UI/StartupDlg.ui \
+ Palettes/Project/EditPresentationIdDlg.ui \
+ Palettes/Project/ChooseImagePropertyDlg.ui \
+ Palettes/scenecamera/scenecameraview.ui \
+ Palettes/Inspector/VariantTagDialog.ui
+
+SOURCES += \
+ Application/AboutDlg.cpp \
+ Application/DataInputDlg.cpp \
+ Application/DataInputListDlg.cpp \
+ Application/DataInputSelectModel.cpp \
+ Application/DataInputSelectView.cpp \
+ Application/DurationEditDlg.cpp \
+ Application/MsgRouter.cpp \
+ Application/StudioApp.cpp \
+ Application/StudioTutorialWidget.cpp \
+ Application/TimeEditDlg.cpp \
+ Controls/AppFonts.cpp \
+ Controls/BufferedRenderer.cpp \
+ Controls/ClickableLabel.cpp \
+ Controls/Control.cpp \
+ Controls/ControlData.cpp \
+ Controls/ControlGraph.cpp \
+ Controls/OffscreenRenderer.cpp \
+ Controls/Renderer.cpp \
+ Controls/WidgetControl.cpp \
+ Controls/WinRenderer.cpp \
+ DragAndDrop/BasicObjectDropSource.cpp \
+ DragAndDrop/DropContainer.cpp \
+ DragAndDrop/DropProxy.cpp \
+ DragAndDrop/DropSource.cpp \
+ DragAndDrop/DropTarget.cpp \
+ DragAndDrop/ExplorerFileDropSource.cpp \
+ DragAndDrop/FileDropSource.cpp \
+ DragAndDrop/SceneDropTarget.cpp \
+ DragAndDrop/TimelineDropSource.cpp \
+ DragAndDrop/TimelineDropTarget.cpp \
+ MainFrm.cpp \
+ Palettes/Action/ActionContextMenu.cpp \
+ Palettes/Action/ActionModel.cpp \
+ Palettes/Action/ActionView.cpp \
+ Palettes/Action/EventsBrowserView.cpp \
+ Palettes/Action/EventsModel.cpp \
+ Palettes/Action/PropertyModel.cpp \
+ Palettes/BasicObjects/BasicObjectsModel.cpp \
+ Palettes/BasicObjects/BasicObjectsView.cpp \
+ Palettes/Inspector/ChooserModelBase.cpp \
+ Palettes/Inspector/FileChooserModel.cpp \
+ Palettes/Inspector/FileChooserView.cpp \
+ Palettes/Inspector/GuideInspectable.cpp \
+ Palettes/Inspector/ImageChooserModel.cpp \
+ Palettes/Inspector/ImageChooserView.cpp \
+ Palettes/Inspector/InspectorControlModel.cpp \
+ Palettes/Inspector/InspectorControlView.cpp \
+ Palettes/Inspector/InspectorGroup.cpp \
+ Palettes/Inspector/MeshChooserModel.cpp \
+ Palettes/Inspector/MeshChooserView.cpp \
+ Palettes/Inspector/MouseHelper.cpp \
+ Palettes/Inspector/ObjectBrowserView.cpp \
+ Palettes/Inspector/ObjectListModel.cpp \
+ Palettes/Inspector/Qt3DSDMInspectable.cpp \
+ Palettes/Inspector/Qt3DSDMInspectorGroup.cpp \
+ Palettes/Inspector/Qt3DSDMInspectorRow.cpp \
+ Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp \
+ Palettes/Inspector/TabOrderHandler.cpp \
+ Palettes/Inspector/TextureChooserView.cpp \
+ Palettes/PaletteManager.cpp \
+ Palettes/Progress/ProgressView.cpp \
+ Palettes/Project/ProjectContextMenu.cpp \
+ Palettes/Project/ProjectFileSystemModel.cpp \
+ Palettes/Project/ProjectView.cpp \
+ Palettes/Slide/SlideContextMenu.cpp \
+ Palettes/Slide/SlideModel.cpp \
+ Palettes/Slide/SlideView.cpp \
+ Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp \
+ Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp \
+ Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp \
+ Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp \
+ Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp \
+ Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp \
+ Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp \
+ Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp \
+ Palettes/Timeline/Bindings/TimelineTranslationManager.cpp \
+ Palettes/TimelineGraphicsView/KeyframeManager.cpp \
+ Palettes/TimelineGraphicsView/RowManager.cpp \
+ Palettes/TimelineGraphicsView/RowMover.cpp \
+ Palettes/TimelineGraphicsView/SelectionRect.cpp \
+ Palettes/TimelineGraphicsView/TimelineControl.cpp \
+ Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp \
+ Palettes/TimelineGraphicsView/TimelineSplitter.cpp \
+ Palettes/TimelineGraphicsView/TimelineWidget.cpp \
+ Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp \
+ Palettes/TimelineGraphicsView/ui/NavigationBar.cpp \
+ Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp \
+ Palettes/TimelineGraphicsView/ui/PlayHead.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTimeline.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTree.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp \
+ Palettes/TimelineGraphicsView/ui/Ruler.cpp \
+ Palettes/TimelineGraphicsView/ui/TimelineItem.cpp \
+ Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp \
+ Palettes/TimelineGraphicsView/ui/TreeHeader.cpp \
+ Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp \
+ Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp \
+ PreviewHelper.cpp \
+ remotedeploymentsender.cpp \
+ Render/PathWidget.cpp \
+ Render/StudioGradientWidget.cpp \
+ Render/StudioRenderer.cpp \
+ Render/StudioRendererTranslation.cpp \
+ Render/StudioRotationWidget.cpp \
+ Render/StudioScaleWidget.cpp \
+ Render/StudioTranslationWidget.cpp \
+ Render/StudioVisualAidWidget.cpp \
+ Render/StudioWidget.cpp \
+ Render/WGLRenderContext.cpp \
+ Render/StudioSubPresentationRenderer.cpp \
+ UI/EditCameraBar.cpp \
+ UI/EditorPane.cpp \
+ UI/GLVersionDlg.cpp \
+ UI/InterpolationDlg.cpp \
+ UI/PlayerContainerWnd.cpp \
+ UI/PlayerWnd.cpp \
+ UI/RecentItems.cpp \
+ UI/ResetKeyframeValuesDlg.cpp \
+ UI/SceneView.cpp \
+ UI/StartupDlg.cpp \
+ UI/StudioAppPrefsPage.cpp \
+ UI/StudioPreferencesPropSheet.cpp \
+ UI/StudioProjectSettingsPage.cpp \
+ Utils/ImportUtils.cpp \
+ Utils/MouseCursor.cpp \
+ Utils/ResourceCache.cpp \
+ Utils/StudioUtils.cpp \
+ Utils/TickTock.cpp \
+ Workspace/Dialogs.cpp \
+ Workspace/Views.cpp \
+ Palettes/Project/EditPresentationIdDlg.cpp \
+ Application/ProjectFile.cpp \
+ Application/PresentationFile.cpp \
+ Palettes/Project/ChooseImagePropertyDlg.cpp \
+ Application/StudioTutorialPageIndicator.cpp \
+ Palettes/Inspector/MaterialRefView.cpp \
+ Palettes/scenecamera/scenecameraview.cpp \
+ Palettes/scenecamera/scenecamerascrollarea.cpp \
+ Palettes/scenecamera/scenecameraglwidget.cpp \
+ Palettes/Inspector/VariantsGroupModel.cpp \
+ Palettes/Inspector/VariantsTagModel.cpp \
+ Palettes/Inspector/VariantTagDialog.cpp \
+ Application/FilterVariantsDlg.cpp \
+ Application/FilterVariantsModel.cpp \
+ Utils/QmlUtils.cpp
+
+RESOURCES += \
+ MainFrm.qrc \
+ qml.qrc \
+ images.qrc
+
+# Static libraries Qt 3D Studio depends on from ogl-runtime
+RUNTIME_PREDEPS_LIBS += \
+ QT3DSDM \
+ qt3dsruntimestatic
+
+# Static libraries Qt 3D Studio depends on from qt3dstudio
+PREDEPS_LIBS += \
+ CommonLib \
+ CoreLib
+
+include(../../utils.pri)
+PRE_TARGETDEPS += $$fixLibPredeps($$RUNTIME_LIBDIR, RUNTIME_PREDEPS_LIBS)
+PRE_TARGETDEPS += $$fixLibPredeps($$LIBDIR, PREDEPS_LIBS)
+
+# Bundle FBX for macOS
+macos:!isEmpty(QMAKE_LIBS_FBX) {
+ fbxlibpath = $$last(QMAKE_LIBS_FBX)
+ fbxsdk.files = $$str_member($$fbxlibpath, 2, -1)/libfbxsdk.dylib
+ fbxsdk.path = Contents/MacOS
+ QMAKE_BUNDLE_DATA += fbxsdk
+}
+
+macos {
+# Link QtStudio3D framework manually as syncqt doesn't work on macOS
+QMAKE_CXXFLAGS += -F$$absolute_path($$RUNTIME_LIBDIR)
+QMAKE_LFLAGS += -F$$absolute_path($$RUNTIME_LIBDIR)
+LIBS += -framework QtStudio3D
+
+qtstudio3d.files = $$absolute_path($$RUNTIME_LIBDIR)/QtStudio3D.framework
+qtstudio3d.path = Contents/Frameworks
+
+studioruntime.files = $$absolute_path($$RUNTIME_LIBDIR)/libqt3dsopengl.$$section(MODULE_VERSION, '.', 0, 0).dylib
+studioruntime.path = Contents/Frameworks
+
+qmlstreamer.files = $$absolute_path($$RUNTIME_LIBDIR)/libqt3dsqmlstreamer.$$section(MODULE_VERSION, '.', 0, 0).dylib
+qmlstreamer.path = Contents/Frameworks
+
+QMAKE_BUNDLE_DATA += qtstudio3d studioruntime qmlstreamer
+
+# When building debug, copy the debug libs as well
+CONFIG(debug, debug|release) {
+studioruntimedbg.files = $$absolute_path($$RUNTIME_LIBDIR)/libqt3dsopengl_debug.$$section(MODULE_VERSION, '.', 0, 0).dylib
+studioruntimedbg.path = Contents/Frameworks
+
+qmlstreamerdbg.files = $$absolute_path($$RUNTIME_LIBDIR)/libqt3dsqmlstreamer_debug.$$section(MODULE_VERSION, '.', 0, 0).dylib
+qmlstreamerdbg.path = Contents/Frameworks
+}
+QMAKE_BUNDLE_DATA += studioruntimedbg qmlstreamerdbg
+}
+
+macos {
+ QMAKE_INFO_PLIST = Info.plist
+}
+
+# Copy necessary resources
+ABS_PRJ_ROOT = $$absolute_path($$PWD/../../..)
+macos:ABS_DEST_DIR = $$absolute_path($$BINDIR)/$${TARGET}.app/Contents/Resources
+!macos:ABS_DEST_DIR = $$absolute_path($$BINDIR)
+
+copy_content.files = $$PWD/../../Runtime/ogl-runtime/Studio/*
+copy_content.path = $$ABS_DEST_DIR
+COPIES += copy_content
+
+install_content.files = $$PWD/../../Runtime/ogl-runtime/Studio/*
+install_content.path = $$[QT_INSTALL_BINS]
+INSTALLS += install_content
+
+copy_buildconfig.files = $$PWD/../../../Studio/*
+copy_buildconfig.path = $$ABS_DEST_DIR
+COPIES += copy_buildconfig
+
+install_buildconfig.files = $$PWD/../../../Studio/*
+install_buildconfig.path = $$[QT_INSTALL_BINS]
+INSTALLS += install_buildconfig
+
+CONFIG += exceptions
+
+target.path = $$[QT_INSTALL_BINS]
+INSTALLS += target
+
+# Install FBX SDK library for Linux
+linux:!isEmpty(QMAKE_LIBS_FBX) {
+ fbxsdk.files = $$str_member($$last(QMAKE_LIBS_FBX), 2, -1)/libfbxsdk.so
+ fbxsdk.path = $$[QT_INSTALL_LIBS]
+ INSTALLS += fbxsdk
+}
+
+RC_ICONS = images/3D-studio.ico
+ICON = images/studio.icns
+
+# Extract SHA from git if building sources from git repository
+exists($$ABS_PRJ_ROOT/.git) {
+ GIT_SHA = $$system(git rev-list --abbrev-commit -n1 HEAD)
+}
+# Otherwise attempt to extract SHA from .tag file
+isEmpty(GIT_SHA):exists($$ABS_PRJ_ROOT/.tag) {
+ STUDIO_TAG = $$cat($$ABS_PRJ_ROOT/.tag)
+ FIRST_CHAR = $$str_member($$STUDIO_TAG, 0, 0)
+ !equals(FIRST_CHAR, "$"): GIT_SHA = $$first(STUDIO_TAG)
+}
+!isEmpty(GIT_SHA): DEFINES += QT3DSTUDIO_REVISION=$$GIT_SHA
+
+# Get a unique version identifying integer
+STUDIO_MAJOR_VERSION = $$section(MODULE_VERSION, '.', 0, 0)
+STUDIO_MINOR_VERSION = $$section(MODULE_VERSION, '.', 1, 1)
+STUDIO_PATCH_VERSION = $$section(MODULE_VERSION, '.', 2, 2)
+DEFINES += \
+ STUDIO_VERSION_NUM=$${STUDIO_MAJOR_VERSION}0$${STUDIO_MINOR_VERSION}0$${STUDIO_PATCH_VERSION}
diff --git a/src/Authoring/Qt3DStudio/Render/IStudioRenderer.h b/src/Authoring/Qt3DStudio/Render/IStudioRenderer.h
new file mode 100644
index 00000000..fc543a89
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/IStudioRenderer.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_RENDERER
+#define QT3DS_STUDIO_RENDERER
+#pragma once
+
+#include "IDocSceneGraph.h"
+#include <EASTL/vector.h>
+#include <EASTL/string.h>
+#include "Doc.h"
+
+#include <QRect>
+
+QT_BEGIN_NAMESPACE
+class QWidget;
+QT_END_NAMESPACE
+
+namespace Q3DStudio {
+using qt3ds::QT3DSI32;
+class IStudioRenderer : public IDocSceneGraph
+{
+protected:
+ virtual ~IStudioRenderer() {}
+
+public:
+ friend class std::shared_ptr<IStudioRenderer>;
+
+ virtual bool IsInitialized() = 0;
+
+ virtual void Initialize(QWidget *inWindow) = 0;
+
+ virtual void SetViewRect(const QRect &inRect) = 0;
+ virtual void setFullSizePreview(bool enabled) = 0;
+ virtual void setIsSceneCameraView(bool sceneCameraView) = 0;
+
+ virtual void GetEditCameraList(QStringList &outCameras) = 0;
+ virtual void SetPolygonFillModeEnabled(bool inEnableFillMode) = 0;
+ virtual bool IsPolygonFillModeEnabled() const = 0;
+ virtual bool DoesEditCameraSupportRotation(QT3DSI32 inIndex) = 0;
+ virtual bool AreGuidesEnabled() const = 0;
+ virtual void SetGuidesEnabled(bool val) = 0;
+ virtual bool AreGuidesEditable() const = 0;
+ virtual void SetGuidesEditable(bool val) = 0;
+ // Setting the camera to -1 disables the edit cameras
+ // So setting the camera to 0- (numcameras - 1) will set change the active
+ // edit camera.
+ virtual void SetEditCamera(QT3DSI32 inIndex) = 0;
+ virtual QT3DSI32 GetEditCamera() const = 0;
+ virtual void EditCameraZoomToFit() = 0;
+
+ virtual bool isMouseDown() const = 0;
+
+ // This must be safe to call from multiple places
+ virtual void Close() = 0;
+
+ // synchronously render the content
+ virtual void RenderNow() = 0;
+ virtual void getPreviewFbo(QSize &outFboDim, qt3ds::QT3DSU32 &outFboTexture) = 0;
+
+ virtual void MakeContextCurrent() = 0;
+ virtual void ReleaseContext() = 0;
+
+ virtual void RegisterSubpresentations(
+ const QVector<SubPresentationRecord> &subpresentations) = 0;
+
+ // Uses the global studio app to get the doc and dispatch.
+ static std::shared_ptr<IStudioRenderer> CreateStudioRenderer();
+};
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/PathWidget.cpp b/src/Authoring/Qt3DStudio/Render/PathWidget.cpp
new file mode 100644
index 00000000..90b82205
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/PathWidget.cpp
@@ -0,0 +1,512 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "PathWidget.h"
+#include "Qt3DSRenderWidgets.h"
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderVertexBuffer.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "render/Qt3DSRenderInputAssembler.h"
+#include "Qt3DSRenderPath.h"
+#include "Qt3DSRenderPathSubPath.h"
+#include "Qt3DSRenderPathManager.h"
+#include "Qt3DSRenderContextCore.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+
+using namespace qt3ds::widgets;
+
+namespace {
+
+QT3DSVec3 toVec3(QT3DSVec2 inPoint)
+{
+ return QT3DSVec3(inPoint.x, inPoint.y, 0.0f);
+}
+QT3DSVec3 toVec3(QT3DSVec2 inPoint, float z)
+{
+ return QT3DSVec3(inPoint.x, inPoint.y, z);
+}
+
+struct SPointEntry
+{
+ QT3DSVec2 m_Position;
+ QT3DSVec3 m_Color;
+ QT3DSF32 m_ObjectId;
+ SPointEntry(QT3DSVec2 pos, QT3DSVec3 color, size_t objId)
+ : m_Position(pos)
+ , m_Color(color)
+ , m_ObjectId((QT3DSF32)objId)
+ {
+ }
+ SPointEntry() {}
+};
+
+struct SPathWidget : public IPathWidget
+{
+ typedef nvvector<eastl::pair<QT3DSU32, qt3ds::studio::SPathPick::EAnchorProperty>>
+ TReverseAnchorBuffer;
+
+ NVAllocatorCallback &m_Allocator;
+ IQt3DSRenderContext &m_Context;
+
+ NVScopedRefCounted<NVRenderVertexBuffer> m_PointVertexBuffer;
+ NVScopedRefCounted<NVRenderInputAssembler> m_PointAssembler;
+ NVScopedRefCounted<NVRenderShaderProgram> m_PointShader;
+ NVScopedRefCounted<NVRenderShaderProgram> m_PointPickShader;
+
+ NVScopedRefCounted<NVRenderVertexBuffer> m_LineVertexBuffer;
+ NVScopedRefCounted<NVRenderInputAssembler> m_LineAssembler;
+ NVScopedRefCounted<NVRenderShaderProgram> m_LineShader;
+
+ nvvector<eastl::pair<QT3DSVec2, QT3DSVec2>> m_LineBuffer;
+ nvvector<SPointEntry> m_PointBuffer;
+ TReverseAnchorBuffer m_AnchorIndexBuffer;
+ SWidgetRenderSetupResult m_RenderSetup;
+ QT3DSVec2 m_PointViewportDimensions;
+ QT3DSMat44 m_PointMVP;
+
+ QT3DSI32 m_RefCount;
+ SPathWidget(NVAllocatorCallback &inAlloc, IQt3DSRenderContext &inRc)
+ : m_Allocator(inAlloc)
+ , m_Context(inRc)
+ , m_LineBuffer(inAlloc, "m_LineBuffer")
+ , m_PointBuffer(inAlloc, "m_PointBuffer")
+ , m_AnchorIndexBuffer(inAlloc, "m_AnchorIndexBuffer")
+ , m_LineShader(nullptr)
+ , m_RefCount(0)
+ {
+ }
+
+ void addRef() override { ++m_RefCount; }
+ void release() override
+ {
+ --m_RefCount;
+ if (m_RefCount <= 0) {
+ NVAllocatorCallback &alloc(m_Allocator);
+ NVDelete(alloc, this);
+ }
+ }
+
+ void SetNode(SNode &inNode) override { m_Node = &inNode; }
+
+ QT3DSVec3 ToGlobalSpace(QT3DSVec3 inPoint) { return m_Node->m_GlobalTransform.transform(inPoint); }
+
+ QT3DSVec3 ToCameraSpace(QT3DSVec3 inPoint)
+ {
+ QT3DSVec3 theGlobalPos = m_Node->m_GlobalTransform.transform(inPoint);
+ return m_RenderSetup.m_WidgetInfo.m_CameraGlobalInverse.transform(theGlobalPos);
+ }
+
+ QT3DSVec3 ToCameraSpace(QT3DSVec2 inPoint)
+ {
+ return ToCameraSpace(QT3DSVec3(inPoint.x, inPoint.y, 0.0f));
+ }
+
+ void GeneratePointVertexGeometrySections(IShaderProgramGenerator &inProgramGenerator)
+ {
+ IShaderStageGenerator &vertex(*inProgramGenerator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &geometry(
+ *inProgramGenerator.GetStage(ShaderGeneratorStages::Geometry));
+ vertex.AddIncoming("attr_pos", "vec2");
+ vertex.AddIncoming("attr_color", "vec3");
+ vertex.AddIncoming("attr_objid", "float");
+ vertex.AddOutgoing("point_color", "vec3");
+ vertex.AddOutgoing("object_id", "float");
+ vertex.AddUniform("model_view_projection", "mat4");
+ vertex << "void main()" << Endl << "{" << Endl
+ << "\tgl_Position = model_view_projection * vec4( attr_pos.xy, 0.0, 1.0 );" << Endl
+ << "\tpoint_color = attr_color;" << Endl << "\tobject_id = uint(attr_objid);" << Endl
+ << "}" << Endl;
+
+ geometry.AddUniform("viewport_dimensions", "vec2");
+ geometry.AddUniform("pointsize", "float");
+ geometry.AddIncoming("point_color", "vec3");
+ geometry.AddIncoming("object_id", "float");
+ geometry.AddOutgoing("point_colorGE", "vec3");
+ geometry.AddOutgoing("object_idGE", "float");
+ geometry.AddOutgoing("uv_coords", "vec2");
+ geometry.Append("layout (points) in;");
+ geometry.Append("layout (triangle_strip, max_vertices = 4) out;");
+ geometry.Append(
+ "void main() {"
+ "// project points to screen space\n"
+ "\tvec2 p0 = vec2(gl_in[0].gl_Position.xy) / gl_in[0].gl_Position.w;\n"
+ "\tvec2 increments = (pointsize / viewport_dimensions) / 2.0;\n"
+ "\tgl_Position = vec4( p0.x - increments.x, p0.y + increments.y, 0.0, 1.0 );\n"
+ "\tpoint_colorGE = point_color[0];\n"
+ "\tuv_coords = vec2(0.0, 0.0);\n"
+ "\tobject_idGE = object_id[0];\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p0.x + increments.x, p0.y + increments.y, 0.0, 1.0 );\n"
+ "\tpoint_colorGE = point_color[0];\n"
+ "\tuv_coords = vec2(1.0, 0.0);\n"
+ "\tobject_idGE = object_id[0];\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p0.x - increments.x, p0.y - increments.y, 0.0, 1.0 );\n"
+ "\tpoint_colorGE = point_color[0];\n"
+ "\tuv_coords = vec2(0.0, 1.0);\n"
+ "\tobject_idGE = object_id[0];\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p0.x + increments.x, p0.y - increments.y, 0.0, 1.0 );\n"
+ "\tpoint_colorGE = point_color[0];\n"
+ "\tuv_coords = vec2(1.0, 1.0);\n"
+ "\tobject_idGE = object_id[0];\n"
+ "\tEmitVertex();\n"
+ "\tEndPrimitive();\n"
+ "}\n");
+ }
+
+ NVRenderShaderProgram *GetPointShader(IShaderProgramGenerator &inProgramGenerator)
+ {
+ if (m_PointShader)
+ return m_PointShader.mPtr;
+ inProgramGenerator.BeginProgram(IShaderProgramGenerator::DefaultFlags()
+ | ShaderGeneratorStages::Geometry);
+ GeneratePointVertexGeometrySections(inProgramGenerator);
+
+ IShaderStageGenerator &fragment(
+ *inProgramGenerator.GetStage(ShaderGeneratorStages::Fragment));
+ fragment.AddIncoming("point_colorGE", "vec3");
+ fragment.AddIncoming("uv_coords", "vec2");
+ fragment.Append("void main()\n"
+ "{\n"
+ "\tvec2 coords = uv_coords - vec2(.5, .5);\n"
+ "\tfloat coordLen = length( coords );\n"
+ "\tfloat margin = .4;\n"
+ "\tfloat leftover = min( 1.0, (coordLen - margin)/.1 );\n"
+ "\tfloat alpha = coordLen < margin ? 1.0 : mix( 1.0, 0.0, leftover );\n"
+ "\tfragOutput = vec4(point_colorGE, alpha);\n"
+ "}\n");
+
+ m_PointShader = inProgramGenerator.CompileGeneratedShader("path widget point shader");
+ return m_PointShader.mPtr;
+ }
+
+ NVRenderShaderProgram *GetPointPickShader(IShaderProgramGenerator &inProgramGenerator)
+ {
+ if (m_PointPickShader)
+ return m_PointPickShader.mPtr;
+ inProgramGenerator.BeginProgram(IShaderProgramGenerator::DefaultFlags()
+ | ShaderGeneratorStages::Geometry);
+ GeneratePointVertexGeometrySections(inProgramGenerator);
+
+ IShaderStageGenerator &fragment(
+ *inProgramGenerator.GetStage(ShaderGeneratorStages::Fragment));
+ fragment.AddIncoming("object_idGE", "float");
+ fragment.AddIncoming("uv_coords", "vec2");
+ fragment.Append("void main()\n"
+ "{\n"
+ "\tvec2 coords = uv_coords - vec2(.5, .5);\n"
+ "\tfloat coordLen = length( coords );\n"
+ "\tfloat margin = .4;\n"
+ "\tuint object_id = coordLen < margin ? uint(object_idGE + 1) : uint(0);\n"
+ "\tfragOutput.r = float(object_id % 256)/255.0;\n"
+ "\tfragOutput.g = float(object_id / 256)/255.0;\n"
+ //"\tfragOutput.g = float(object_id) / 10.0;\n"
+ "\tfragOutput.b = 0.0;\n"
+ "\tfragOutput.a = 1.0;\n"
+ "}\n");
+ m_PointPickShader =
+ inProgramGenerator.CompileGeneratedShader("path widget point pick shader");
+ return m_PointPickShader.mPtr;
+ }
+
+ NVRenderShaderProgram *GetLineShader(IShaderProgramGenerator &inProgramGenerator)
+ {
+ if (m_LineShader)
+ return m_LineShader.mPtr;
+
+ inProgramGenerator.BeginProgram(IShaderProgramGenerator::DefaultFlags()
+ | ShaderGeneratorStages::Geometry);
+
+ IShaderStageGenerator &vertex(*inProgramGenerator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &geometry(
+ *inProgramGenerator.GetStage(ShaderGeneratorStages::Geometry));
+ IShaderStageGenerator &fragment(
+ *inProgramGenerator.GetStage(ShaderGeneratorStages::Fragment));
+
+ vertex.AddIncoming("attr_pos", "vec2");
+ vertex.AddUniform("model_view_projection", "mat4");
+ vertex << "void main()" << Endl << "{" << Endl
+ << "\tgl_Position = model_view_projection * vec4( attr_pos.xy, 0.0, 1.0 );" << Endl
+ << "}" << Endl;
+
+ geometry.AddUniform("viewport_dimensions", "vec2");
+ geometry.AddUniform("linewidth", "float");
+ geometry.AddOutgoing("uv_coords", "vec2");
+ geometry.Append(
+ "layout (lines) in;\n"
+ "layout (triangle_strip, max_vertices = 4) out;\n"
+ "void main()\n"
+ "{\n"
+ "\tvec2 p0 = vec2(gl_in[0].gl_Position.xy) / gl_in[0].gl_Position.w;\n"
+ "\tvec2 p1 = vec2(gl_in[1].gl_Position.xy) / gl_in[1].gl_Position.w;\n"
+ "\tvec2 slope = normalize( p1 - p0 );\n"
+ "\tvec2 tangent = vec2(slope.y, -slope.x);\n"
+ "\tvec2 increments = (linewidth / viewport_dimensions) / 2.0;\n"
+ "\tvec2 tangentVec = vec2( tangent.x * increments.x, tangent.y * increments.y );\n"
+ "\tgl_Position = vec4( p0 - tangentVec, 0.0, 1.0);\n"
+ "\tuv_coords = vec2(0.0, 0.0);\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p0 + tangentVec, 0.0, 1.0);\n"
+ "\tuv_coords = vec2(1.0, 0.0);\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p1 - tangentVec, 0.0, 1.0);\n"
+ "\tuv_coords = vec2(0.0, 1.0);\n"
+ "\tEmitVertex();\n"
+ "\tgl_Position = vec4( p1 + tangentVec, 0.0, 1.0);\n"
+ "\tuv_coords = vec2(1.0, 1.0);\n"
+ "\tEmitVertex();\n"
+ "\tEndPrimitive();\n"
+ "}\n");
+
+ fragment.AddUniform("line_color", "vec3");
+ fragment.AddIncoming("uv_coords", "vec2");
+ fragment.Append("void main()\n"
+ "{\n"
+ "\tvec2 coords = uv_coords - vec2(.5, .5);\n"
+ "\tfloat coordLen = abs(coords.x);\n"
+ "\tfloat margin = .1;\n"
+ "\tfloat leftover = min( 1.0, (coordLen - margin)/.3 );\n"
+ "\tleftover = leftover * leftover;\n"
+ "\tfloat alpha = coordLen < margin ? 1.0 : mix( 1.0, 0.0, leftover );\n"
+ "\tfragOutput = vec4(line_color, alpha);\n"
+ "}\n");
+
+ m_LineShader = inProgramGenerator.CompileGeneratedShader("path widget line shader");
+ return m_LineShader.mPtr;
+ }
+
+ void RenderPointBuffer(NVRenderShaderProgram &inProgram, const QT3DSMat44 &inMVP,
+ NVRenderContext &inRenderContext)
+ {
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetStencilTestEnabled(false);
+ inRenderContext.SetBlendingEnabled(true);
+ inRenderContext.SetBlendFunction(qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::One,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ inRenderContext.SetBlendEquation(qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+ inRenderContext.SetActiveShader(&inProgram);
+ inProgram.SetPropertyValue("model_view_projection", inMVP);
+ inProgram.SetPropertyValue("pointsize", (QT3DSF32)15.0f);
+ inProgram.SetPropertyValue("viewport_dimensions", m_PointViewportDimensions);
+ inRenderContext.SetInputAssembler(m_PointAssembler);
+ inRenderContext.Draw(NVRenderDrawMode::Points, m_PointBuffer.size(), 0);
+ }
+
+ void PushPoint(const SPointEntry &inEntry, QT3DSU32 inAnchorIndex,
+ qt3ds::studio::SPathPick::EAnchorProperty inProperty)
+ {
+ QT3DSU32 anchorIndex = (QT3DSU32)m_PointBuffer.size();
+ m_PointBuffer.push_back(inEntry);
+ m_AnchorIndexBuffer.push_back(eastl::make_pair(inAnchorIndex, inProperty));
+ }
+
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext) override
+ {
+ if (!m_Node)
+ return;
+ SPath &thePath = static_cast<SPath &>(*m_Node);
+ IPathManager &theManager = m_Context.GetPathManager();
+
+ QT3DSVec3 anchorColor(0, 1, 0);
+ QT3DSVec3 controlColor(0, 0, 1);
+ m_LineBuffer.clear();
+ m_PointBuffer.clear();
+ // point index -> anchor index
+ m_AnchorIndexBuffer.clear();
+ QT3DSU32 anchorIndex = 0;
+
+ for (SPathSubPath *theSubPath = thePath.m_FirstSubPath; theSubPath;
+ theSubPath = theSubPath->m_NextSubPath) {
+ NVDataRef<qt3ds::render::SPathAnchorPoint> thePathBuffer(
+ theManager.GetPathSubPathBuffer(*theSubPath));
+ if (thePathBuffer.size() == 0)
+ return;
+
+ QT3DSU32 numAnchors = thePathBuffer.size();
+ for (QT3DSU32 idx = 0, end = numAnchors; idx < end; ++idx) {
+ const qt3ds::render::SPathAnchorPoint &theAnchorPoint(thePathBuffer[idx]);
+ if (idx > 0) {
+ QT3DSVec2 incoming(qt3ds::render::IPathManagerCore::GetControlPointFromAngleDistance(
+ theAnchorPoint.m_Position, theAnchorPoint.m_IncomingAngle,
+ theAnchorPoint.m_IncomingDistance));
+ PushPoint(SPointEntry(incoming, controlColor, m_PointBuffer.size()),
+ anchorIndex, qt3ds::studio::SPathPick::IncomingControl);
+ m_LineBuffer.push_back(eastl::make_pair(theAnchorPoint.m_Position, incoming));
+ }
+ PushPoint(SPointEntry(theAnchorPoint.m_Position, anchorColor, m_PointBuffer.size()),
+ anchorIndex, qt3ds::studio::SPathPick::Anchor);
+ if (idx < (numAnchors - 1)) {
+ QT3DSVec2 outgoing(qt3ds::render::IPathManagerCore::GetControlPointFromAngleDistance(
+ theAnchorPoint.m_Position, theAnchorPoint.m_OutgoingAngle,
+ theAnchorPoint.m_OutgoingDistance));
+ PushPoint(SPointEntry(outgoing, controlColor, m_PointBuffer.size()),
+ anchorIndex, qt3ds::studio::SPathPick::OutgoingControl);
+ m_LineBuffer.push_back(eastl::make_pair(theAnchorPoint.m_Position, outgoing));
+ }
+ ++anchorIndex;
+ }
+ }
+
+ m_RenderSetup = SWidgetRenderSetupResult(inWidgetContext, *m_Node,
+ qt3ds::render::RenderWidgetModes::Local);
+
+ m_PointMVP = m_RenderSetup.m_WidgetInfo.m_LayerProjection
+ * m_RenderSetup.m_WidgetInfo.m_CameraGlobalInverse * m_Node->m_GlobalTransform;
+
+ NVRenderRect theViewport = inRenderContext.GetViewport();
+ m_PointViewportDimensions = QT3DSVec2((QT3DSF32)theViewport.m_Width, (QT3DSF32)theViewport.m_Height);
+
+ if (!m_LineBuffer.empty()) {
+ QT3DSU32 vertItemSize = sizeof(QT3DSVec2);
+ QT3DSU32 vertBufSize = m_LineBuffer.size() * vertItemSize * 2;
+ if ((!m_LineVertexBuffer) || m_LineVertexBuffer->Size() < vertBufSize) {
+ m_LineVertexBuffer = inRenderContext.CreateVertexBuffer(
+ qt3ds::render::NVRenderBufferUsageType::Dynamic, vertBufSize, vertItemSize,
+ qt3ds::foundation::toU8DataRef(m_LineBuffer.data(), (QT3DSU32)m_LineBuffer.size()));
+ m_LineAssembler = nullptr;
+ } else
+ m_LineVertexBuffer->UpdateBuffer(
+ qt3ds::foundation::toU8DataRef(m_LineBuffer.data(), (QT3DSU32)m_LineBuffer.size()));
+
+ if (m_LineAssembler == nullptr) {
+ qt3ds::render::NVRenderVertexBufferEntry theEntries[] = {
+ qt3ds::render::NVRenderVertexBufferEntry(
+ "attr_pos", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 2, 0),
+ };
+ NVRenderAttribLayout *theAttribLayout =
+ &inWidgetContext.CreateAttributeLayout(toConstDataRef(theEntries, 1));
+ m_LineAssembler = inRenderContext.CreateInputAssembler(
+ theAttribLayout, toConstDataRef(&m_LineVertexBuffer.mPtr, 1), nullptr,
+ toConstDataRef(vertItemSize), toConstDataRef((QT3DSU32)0));
+ }
+ inRenderContext.SetInputAssembler(m_LineAssembler);
+ NVRenderShaderProgram *lineShader =
+ GetLineShader(inWidgetContext.GetProgramGenerator());
+ if (lineShader) {
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetStencilTestEnabled(false);
+ inRenderContext.SetBlendingEnabled(true);
+ inRenderContext.SetBlendFunction(qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::One,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ inRenderContext.SetBlendEquation(qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+ inRenderContext.SetActiveShader(lineShader);
+ lineShader->SetPropertyValue("model_view_projection", m_PointMVP);
+ lineShader->SetPropertyValue("line_color", QT3DSVec3(1.0, 1.0, 0.0));
+ // Note the line needs to be wide enough to account for anti-aliasing.
+ lineShader->SetPropertyValue("linewidth", 3.0f);
+ lineShader->SetPropertyValue("viewport_dimensions", m_PointViewportDimensions);
+ inRenderContext.Draw(NVRenderDrawMode::Lines, m_LineBuffer.size() * 2, 0);
+ }
+ }
+ {
+ QT3DSU32 vertItemSize = (sizeof(SPointEntry));
+ QT3DSU32 vertBufSize = m_PointBuffer.size() * vertItemSize;
+ if ((!m_PointVertexBuffer) || m_PointVertexBuffer->Size() < vertBufSize) {
+ m_PointVertexBuffer = inRenderContext.CreateVertexBuffer(
+ qt3ds::render::NVRenderBufferUsageType::Dynamic, vertBufSize, vertItemSize,
+ qt3ds::foundation::toU8DataRef(m_PointBuffer.data(), (QT3DSU32)m_PointBuffer.size()));
+ m_PointAssembler = nullptr;
+ } else
+ m_PointVertexBuffer->UpdateBuffer(
+ qt3ds::foundation::toU8DataRef(m_PointBuffer.data(), (QT3DSU32)m_PointBuffer.size()));
+ if (m_PointAssembler == nullptr) {
+ qt3ds::render::NVRenderVertexBufferEntry theEntries[] = {
+ qt3ds::render::NVRenderVertexBufferEntry(
+ "attr_pos", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 2, 0),
+ qt3ds::render::NVRenderVertexBufferEntry(
+ "attr_color", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3, 8),
+ qt3ds::render::NVRenderVertexBufferEntry(
+ "attr_objid", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 1, 20),
+ };
+ NVRenderAttribLayout *theAttribLayout =
+ &inWidgetContext.CreateAttributeLayout(toConstDataRef(theEntries, 3));
+ m_PointAssembler = inRenderContext.CreateInputAssembler(
+ theAttribLayout, toConstDataRef(&m_PointVertexBuffer.mPtr, 1), nullptr,
+ toConstDataRef(vertItemSize), toConstDataRef((QT3DSU32)0));
+ }
+ NVRenderShaderProgram *thePointShader =
+ GetPointShader(inWidgetContext.GetProgramGenerator());
+ GetPointPickShader(inWidgetContext.GetProgramGenerator());
+
+ if (thePointShader) {
+ m_PointMVP = m_RenderSetup.m_WidgetInfo.m_LayerProjection
+ * m_RenderSetup.m_WidgetInfo.m_CameraGlobalInverse * m_Node->m_GlobalTransform;
+
+ RenderPointBuffer(*m_PointShader, m_PointMVP, inRenderContext);
+ }
+ }
+ }
+
+ void RenderPick(const QT3DSMat44 &inProjPreMult, NVRenderContext &inRenderContext,
+ QSize inWinDimensions) override
+ {
+ if (m_PointAssembler == nullptr || m_PointPickShader == nullptr)
+ return;
+ // The projection premultiplication step moves the viewport around till
+ // it is centered over the mouse and scales everything *post* rendering (to keep appropriate
+ // aspect).
+ QT3DSMat44 theMVP = inProjPreMult * m_PointMVP;
+ m_PointViewportDimensions =
+ QT3DSVec2((QT3DSF32)inWinDimensions.width(), (QT3DSF32)inWinDimensions.height());
+
+ RenderPointBuffer(*m_PointPickShader, theMVP, inRenderContext);
+ }
+
+ qt3ds::studio::SStudioPickValue PickIndexToPickValue(QT3DSU32 inPickIndex) override
+ {
+ inPickIndex -= 1;
+
+ if (inPickIndex < m_AnchorIndexBuffer.size())
+ return qt3ds::studio::SPathPick(m_AnchorIndexBuffer[inPickIndex].first,
+ m_AnchorIndexBuffer[inPickIndex].second);
+ else {
+ QT3DS_ASSERT(false);
+ return qt3ds::studio::SPathPick();
+ }
+ }
+};
+}
+
+IPathWidget &IPathWidget::CreatePathWidget(NVAllocatorCallback &inCallback, IQt3DSRenderContext &inRc)
+{
+ return *QT3DS_NEW(inCallback, SPathWidget)(inCallback, inRc);
+}
diff --git a/src/Authoring/Qt3DStudio/Render/PathWidget.h b/src/Authoring/Qt3DStudio/Render/PathWidget.h
new file mode 100644
index 00000000..6396b3e9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/PathWidget.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_PATH_WIDGET_H
+#define QT3DS_STUDIO_PATH_WIDGET_H
+#pragma once
+#include "StudioWidget.h"
+#include "Qt3DSDMHandles.h"
+#include "StudioPickValues.h"
+
+namespace qt3ds {
+namespace widgets {
+
+ class IPathWidget : public IStudioWidgetBase
+ {
+ public:
+ qt3ds::studio::SStudioPickValue PickIndexToPickValue(QT3DSU32 inPickIndex) override = 0;
+ static IPathWidget &CreatePathWidget(NVAllocatorCallback &inAlloc,
+ IQt3DSRenderContext &inRenderContext);
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.cpp
new file mode 100644
index 00000000..1b514f65
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** 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 "StudioGradientWidget.h"
+
+namespace qt3ds {
+namespace widgets {
+
+using namespace qt3ds::render;
+
+NVConstDataRef<NVRenderVertexBufferEntry>
+SGradientWidget::GetVertexBufferAttributesAndStride(QT3DSU32 &stride)
+{
+ static NVRenderVertexBufferEntry theEntries[] = {
+ NVRenderVertexBufferEntry("attr_pos", NVRenderComponentTypes::QT3DSF32, 3)
+ };
+
+ stride = 3 * sizeof(QT3DSF32);
+
+ return toConstDataRef(theEntries, 1);
+}
+
+NVRenderInputAssembler *SGradientWidget::CreateSphere(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext,
+ QT3DSF32 radius)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr("Gradient");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec3> theVertexData(m_allocator, "SGradientWidget::theVertexData");
+ nvvector<QT3DSI8> theIndexData(m_allocator, "SGradientWidget::theIndexData");
+
+ // the sphere is just a box
+ theVertexData.push_back(QT3DSVec3(-radius, radius, -radius));
+ theVertexData.push_back(QT3DSVec3(radius, radius, -radius));
+ theVertexData.push_back(QT3DSVec3(radius, -radius, -radius));
+ theVertexData.push_back(QT3DSVec3(-radius, -radius, -radius));
+ theVertexData.push_back(QT3DSVec3(-radius, radius, radius));
+ theVertexData.push_back(QT3DSVec3(radius, radius, radius));
+ theVertexData.push_back(QT3DSVec3(radius, -radius, radius));
+ theVertexData.push_back(QT3DSVec3(-radius, -radius, radius));
+
+ theIndexData.push_back(0); theIndexData.push_back(1); theIndexData.push_back(2);
+ theIndexData.push_back(0); theIndexData.push_back(2); theIndexData.push_back(3);
+
+ theIndexData.push_back(1); theIndexData.push_back(5); theIndexData.push_back(6);
+ theIndexData.push_back(1); theIndexData.push_back(6); theIndexData.push_back(2);
+
+ theIndexData.push_back(5); theIndexData.push_back(4); theIndexData.push_back(7);
+ theIndexData.push_back(5); theIndexData.push_back(7); theIndexData.push_back(6);
+
+ theIndexData.push_back(4); theIndexData.push_back(0); theIndexData.push_back(3);
+ theIndexData.push_back(4); theIndexData.push_back(3); theIndexData.push_back(7);
+
+ theIndexData.push_back(4); theIndexData.push_back(5); theIndexData.push_back(1);
+ theIndexData.push_back(4); theIndexData.push_back(1); theIndexData.push_back(0);
+
+ theIndexData.push_back(6); theIndexData.push_back(7); theIndexData.push_back(3);
+ theIndexData.push_back(6); theIndexData.push_back(3); theIndexData.push_back(2);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU8, theIndexData.size(),
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderShaderProgram *SGradientWidget::CreateGradientShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString itemName = inRenderContext.GetStringTable().RegisterStr("GradientShader");
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(itemName);
+ if (retval)
+ return retval;
+
+ IShaderProgramGenerator &generator(inWidgetContext.GetProgramGenerator());
+ generator.BeginProgram();
+ IShaderStageGenerator &vertGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &fragGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Fragment));
+ vertGenerator.AddIncoming("attr_pos", "vec3");
+ vertGenerator.AddOutgoing("pos", "vec3");
+ vertGenerator.AddUniform("normalMatrix", "mat3");
+ vertGenerator.AddUniform("projectionMatrix", "mat4");
+ vertGenerator.AddUniform("drawMode", "int");
+ // These are required in order to scale the scale widget the way we want to scale it.
+ vertGenerator.Append("void main() {");
+ vertGenerator.Append("\tpos = attr_pos;");
+ vertGenerator.Append("\tif (drawMode == 0)");
+ vertGenerator.Append("\t\tgl_Position = projectionMatrix * vec4(normalMatrix * pos, 1.0);");
+ vertGenerator.Append("\telse\n\t\tgl_Position = vec4(pos.x > 0 ? 1.0 : -1.0, pos.y > 0 \
+ ? 1.0 : -1.0, 0.0, 1.0);");
+ vertGenerator.Append("}");
+ fragGenerator.AddIncoming("pos", "vec3");
+ fragGenerator.AddUniform("color0", "vec3");
+ fragGenerator.AddUniform("color1", "vec3");
+ fragGenerator.AddUniform("color2", "vec3");
+ fragGenerator.AddUniform("color3", "vec3");
+ fragGenerator.AddUniform("drawMode", "int");
+ fragGenerator.Append("void main() {");
+ fragGenerator.Append("\tvec3 npos = normalize(pos);");
+ fragGenerator.Append("\tvec3 color = vec3(0.0);");
+ fragGenerator.Append("\tif (drawMode == 0) {");
+ fragGenerator.Append("\t\tif (npos.y > 0.0)");
+ fragGenerator.Append("\t\t\tcolor = mix(color1, color0, pow(npos.y, 0.25));");
+ fragGenerator.Append("\t\telse\n\t\t\tcolor = mix(color3, color2, pow(-npos.y, 0.5));");
+ fragGenerator.Append("\t} else {\n\t\tcolor = mix(color3, color0, 0.5 * npos.y + 0.5);\n\t}");
+ fragGenerator.Append("\tgl_FragColor.rgb = color;");
+ fragGenerator.Append("\tgl_FragColor.a = 1.0;");
+ fragGenerator.Append("}");
+ return inWidgetContext.CompileAndStoreShader(itemName);
+}
+
+void SGradientWidget::Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext,
+ bool fullScreenQuad)
+{
+ if (m_sphere == nullptr) {
+ m_sphere = CreateSphere(inWidgetContext, inRenderContext, 100.0f);
+ m_shader = CreateGradientShader(inWidgetContext, inRenderContext);
+
+ inRenderContext.SetActiveShader(m_shader);
+ m_shader->SetPropertyValue("color0", QT3DSVec3(0.6f, 0.6f, 0.6f));
+ m_shader->SetPropertyValue("color1", QT3DSVec3(0.4f, 0.4f, 0.4f));
+ m_shader->SetPropertyValue("color2", QT3DSVec3(0.1f, 0.1f, 0.1f));
+ m_shader->SetPropertyValue("color3", QT3DSVec3(0.35f, 0.35f, 0.35f));
+ }
+
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_shader);
+
+ if (fullScreenQuad) {
+ // draw fullscreen quad
+ m_shader->SetPropertyValue("drawMode", 1);
+ inRenderContext.SetInputAssembler(m_sphere);
+ inRenderContext.Draw(NVRenderDrawMode::Triangles, 6, 0);
+ } else {
+ // draw sphere
+ SWidgetRenderInformation info
+ = inWidgetContext.GetWidgetRenderInformation(*m_node, QT3DSVec3(),
+ RenderWidgetModes::Global);
+ m_shader->SetPropertyValue("drawMode", 0);
+ m_shader->SetPropertyValue("normalMatrix", info.m_NormalMatrix);
+ m_shader->SetPropertyValue("projectionMatrix", info.m_PureProjection);
+
+ inRenderContext.SetInputAssembler(m_sphere);
+
+ inRenderContext.Draw(NVRenderDrawMode::Triangles,
+ m_sphere->GetIndexCount(), 0);
+ }
+}
+
+
+SGradientWidget &SGradientWidget::CreateGradientWidget(NVAllocatorCallback &inAlloc)
+{
+ return *QT3DS_NEW(inAlloc, SGradientWidget)(inAlloc);
+}
+
+}
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.h b/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.h
new file mode 100644
index 00000000..0fd71c89
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioGradientWidget.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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 STUDIO_GRADIENT_WIDGET_H
+#define STUDIO_GRADIENT_WIDGET_H
+
+#include "Qt3DSCommonPrecompile.h"
+#include "StudioWidgetImpl.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderVertexBuffer.h"
+#include "Qt3DSRenderNode.h"
+#include "foundation/Qt3DSContainers.h"
+#include "Qt3DSRenderShaderCodeGenerator.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "StudioUtils.h"
+
+namespace qt3ds {
+namespace widgets {
+
+class SGradientWidget
+{
+ NVAllocatorCallback &m_allocator;
+ NVRenderInputAssembler *m_sphere;
+ NVRenderShaderProgram *m_shader;
+ SNode *m_node;
+
+ volatile QT3DSI32 mRefCount;
+
+public:
+ SGradientWidget(NVAllocatorCallback &inAlloc)
+ : m_allocator(inAlloc)
+ , m_sphere(nullptr)
+ , m_shader(nullptr)
+ , mRefCount(0)
+ {
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_allocator)
+
+ NVConstDataRef<qt3ds::render::NVRenderVertexBufferEntry>
+ GetVertexBufferAttributesAndStride(QT3DSU32 &stride);
+ NVRenderInputAssembler *CreateSphere(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext,
+ QT3DSF32 radius);
+
+ NVRenderShaderProgram *CreateGradientShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext,
+ bool fullScreenQuad);
+
+ void SetNode(SNode &inNode)
+ {
+ m_node = &inNode;
+ }
+
+ static SGradientWidget &CreateGradientWidget(NVAllocatorCallback &inAlloc);
+};
+
+}
+}
+
+#endif // STUDIO_GRADIENT_WIDGET_H
diff --git a/src/Authoring/Qt3DStudio/Render/StudioPickValues.h b/src/Authoring/Qt3DStudio/Render/StudioPickValues.h
new file mode 100644
index 00000000..3d59fd87
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioPickValues.h
@@ -0,0 +1,217 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_PICK_VALUES_H
+#define QT3DS_STUDIO_PICK_VALUES_H
+#pragma once
+#include "foundation/Qt3DSDiscriminatedUnion.h"
+#include "foundation/Qt3DSUnionCast.h"
+#include "Qt3DSDMHandles.h"
+#include "StaticMaxSize.h"
+
+namespace qt3ds {
+namespace studio {
+ using qt3dsdm::Qt3DSDMInstanceHandle;
+ using qt3dsdm::Qt3DSDMGuideHandle;
+
+ struct StudioPickValueTypes
+ {
+ enum Enum {
+ UnknownValueType = 0,
+ Instance,
+ Widget,
+ Guide,
+ Path,
+ };
+ };
+
+ struct SWidgetPick
+ {
+ qt3ds::QT3DSI32 m_WidgetId;
+ SWidgetPick(qt3ds::QT3DSI32 id = 0)
+ : m_WidgetId(id)
+ {
+ }
+ };
+
+ struct SPathPick
+ {
+ enum EAnchorProperty {
+ Anchor = 0,
+ IncomingControl,
+ OutgoingControl,
+ };
+
+ qt3ds::QT3DSU32 m_AnchorIndex;
+ EAnchorProperty m_Property;
+
+ SPathPick()
+ : m_AnchorIndex(0)
+ , m_Property(Anchor)
+ {
+ }
+
+ SPathPick(qt3ds::QT3DSU32 ai, EAnchorProperty p)
+ : m_AnchorIndex(ai)
+ , m_Property(p)
+ {
+ }
+ };
+
+ template <typename TDataType>
+ struct SStudioPickValueTypeMap
+ {
+ };
+
+ template <>
+ struct SStudioPickValueTypeMap<Qt3DSDMInstanceHandle>
+ {
+ static StudioPickValueTypes::Enum GetType() { return StudioPickValueTypes::Instance; }
+ };
+
+ template <>
+ struct SStudioPickValueTypeMap<SWidgetPick>
+ {
+ static StudioPickValueTypes::Enum GetType() { return StudioPickValueTypes::Widget; }
+ };
+
+ template <>
+ struct SStudioPickValueTypeMap<Qt3DSDMGuideHandle>
+ {
+ static StudioPickValueTypes::Enum GetType() { return StudioPickValueTypes::Guide; }
+ };
+
+ template <>
+ struct SStudioPickValueTypeMap<SPathPick>
+ {
+ static StudioPickValueTypes::Enum GetType() { return StudioPickValueTypes::Path; }
+ };
+
+ struct SStudioPickValueTraits
+ {
+ typedef StudioPickValueTypes::Enum TIdType;
+ enum {
+ TBufferSize = Q3DStudio::StaticMaxSize<qt3dsdm::Qt3DSDMInstanceHandle,
+ SWidgetPick,
+ qt3dsdm::Qt3DSDMGuideHandle,
+ SPathPick>::value
+ };
+
+ static TIdType getNoDataId() { return StudioPickValueTypes::UnknownValueType; }
+
+ template <typename TDataType>
+ static TIdType getType()
+ {
+ return SStudioPickValueTypeMap<TDataType>().GetType();
+ }
+
+ template <typename TRetType, typename TVisitorType>
+ static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor)
+ {
+ switch (inType) {
+ case StudioPickValueTypes::Instance:
+ return inVisitor(*qt3ds::NVUnionCast<qt3dsdm::Qt3DSDMInstanceHandle *>(inData));
+ case StudioPickValueTypes::Widget:
+ return inVisitor(*qt3ds::NVUnionCast<SWidgetPick *>(inData));
+ case StudioPickValueTypes::Guide:
+ return inVisitor(*qt3ds::NVUnionCast<qt3dsdm::Qt3DSDMGuideHandle *>(inData));
+ case StudioPickValueTypes::Path:
+ return inVisitor(*qt3ds::NVUnionCast<SPathPick *>(inData));
+ default:
+ QT3DS_ASSERT(false);
+ case StudioPickValueTypes::UnknownValueType:
+ return inVisitor();
+ }
+ }
+
+ template <typename TRetType, typename TVisitorType>
+ static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor)
+ {
+ switch (inType) {
+ case StudioPickValueTypes::Instance:
+ return inVisitor(*qt3ds::NVUnionCast<const qt3dsdm::Qt3DSDMInstanceHandle *>(inData));
+ case StudioPickValueTypes::Widget:
+ return inVisitor(*qt3ds::NVUnionCast<const SWidgetPick *>(inData));
+ case StudioPickValueTypes::Guide:
+ return inVisitor(*qt3ds::NVUnionCast<const qt3dsdm::Qt3DSDMGuideHandle *>(inData));
+ case StudioPickValueTypes::Path:
+ return inVisitor(*qt3ds::NVUnionCast<const SPathPick *>(inData));
+ default:
+ QT3DS_ASSERT(false);
+ case StudioPickValueTypes::UnknownValueType:
+ return inVisitor();
+ }
+ }
+ };
+
+ typedef qt3ds::foundation::
+ DiscriminatedUnion<qt3ds::foundation::
+ DiscriminatedUnionGenericBase<SStudioPickValueTraits,
+ SStudioPickValueTraits::TBufferSize>,
+ SStudioPickValueTraits::TBufferSize>
+ TStudioPickValueType;
+
+ struct SStudioPickValue : public TStudioPickValueType
+ {
+ SStudioPickValue() {}
+ SStudioPickValue(Qt3DSDMInstanceHandle inst)
+ : TStudioPickValueType(inst)
+ {
+ }
+ SStudioPickValue(SWidgetPick inst)
+ : TStudioPickValueType(inst)
+ {
+ }
+ SStudioPickValue(Qt3DSDMGuideHandle inst)
+ : TStudioPickValueType(inst)
+ {
+ }
+ SStudioPickValue(SPathPick inst)
+ : TStudioPickValueType(inst)
+ {
+ }
+ SStudioPickValue(const SStudioPickValue &other)
+ : TStudioPickValueType(static_cast<const TStudioPickValueType &>(other))
+ {
+ }
+ SStudioPickValue &operator=(const SStudioPickValue &other)
+ {
+ TStudioPickValueType::operator=(static_cast<const TStudioPickValueType &>(other));
+ return *this;
+ }
+ int GetWidgetId() const
+ {
+ if (getType() == StudioPickValueTypes::Widget)
+ return getData<SWidgetPick>().m_WidgetId;
+ return 0;
+ }
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioRenderer.cpp b/src/Authoring/Qt3DStudio/Render/StudioRenderer.cpp
new file mode 100644
index 00000000..678fd6c0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioRenderer.cpp
@@ -0,0 +1,1128 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioRendererImpl.h"
+#include "StudioRendererTranslation.h"
+#include "StudioPreferences.h"
+#include "HotKeys.h"
+#include "StudioUtils.h"
+#include "Qt3DSMath.h"
+#include "Qt3DSOffscreenRenderKey.h"
+#include "Qt3DSOffscreenRenderManager.h"
+#include "q3dsqmlrender.h"
+#include "q3dsqmlstreamproxy.h"
+#include "StudioSubPresentationRenderer.h"
+
+#include <QtCore/qdebug.h>
+
+#ifdef _WIN32
+#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union
+#endif
+using namespace qt3ds::studio;
+
+namespace {
+
+const QT3DSU32 g_WheelFactor = 10; // the wheel zoom factor
+
+struct SEditCameraDefinition
+{
+ EditCameraTypes::Enum m_Type;
+ // Directional cameras have a direction they point
+ QT3DSVec3 m_Direction; // not normalized
+ QString m_Name;
+};
+
+SEditCameraDefinition g_EditCameraDefinitions[] = {
+ { EditCameraTypes::Perspective, QT3DSVec3(1, -1, -1), QObject::tr("Perspective View") },
+ { EditCameraTypes::Orthographic, QT3DSVec3(1, -1, -1), QObject::tr("Orthographic View") },
+ { EditCameraTypes::Directional, QT3DSVec3(0, -1, 0), QObject::tr("Top View") },
+ { EditCameraTypes::Directional, QT3DSVec3(0, 1, 0), QObject::tr("Bottom View") },
+ { EditCameraTypes::Directional, QT3DSVec3(1, 0, 0), QObject::tr("Left View") },
+ { EditCameraTypes::Directional, QT3DSVec3(-1, 0, 0), QObject::tr("Right View") },
+ { EditCameraTypes::Directional, QT3DSVec3(0, 0, -1), QObject::tr("Front View") },
+ { EditCameraTypes::Directional, QT3DSVec3(0, 0, 1), QObject::tr("Back View") },
+};
+QT3DSU32 g_NumEditCameras = sizeof(g_EditCameraDefinitions) / sizeof(*g_EditCameraDefinitions);
+
+struct StudioSubPresentation
+{
+ SubPresentationRecord subpresentation;
+ IOffscreenRenderer *renderer;
+
+ bool operator == (const SubPresentationRecord &r) const
+ {
+ return r.m_id == subpresentation.m_id &&
+ r.m_argsOrSrc == subpresentation.m_argsOrSrc &&
+ r.m_type == subpresentation.m_type;
+ }
+};
+
+struct SRendererImpl : public IStudioRenderer,
+ public IDataModelListener,
+ public IReloadListener,
+ public CPresentationChangeListener,
+ public CSceneDragListener,
+ public CToolbarChangeListener,
+ public IOffscreenRenderer::IOffscreenRendererCallback
+{
+ typedef eastl::vector<Option<SEditCameraPersistentInformation>> TEditCameraInfoList;
+ std::shared_ptr<CWGLRenderContext> m_RenderContext;
+ NVScopedRefCounted<IQt3DSRenderContext> m_Context;
+ QRect m_Rect;
+ CDispatch &m_Dispatch;
+ CDoc &m_Doc;
+ std::shared_ptr<STranslation> m_Translation;
+ CPt m_MouseDownPoint;
+ CPt m_PreviousMousePoint;
+ bool m_HasPresentation;
+ bool m_Closed;
+ CUpdateableDocumentEditor m_UpdatableEditor;
+ MovementTypes::Enum m_LastDragToolMode;
+ bool m_MaybeDragStart;
+ TEditCameraInfoList m_EditCameraInformation;
+ QT3DSI32 m_EditCameraIndex;
+ SEditCameraPersistentInformation m_MouseDownCameraInformation;
+ SStudioPickValue m_PickResult;
+ bool m_RenderRequested;
+ int m_LastToolMode;
+ bool m_GuidesEnabled;
+ qt3dsdm::TSignalConnectionPtr m_SelectionSignal;
+ float m_pixelRatio;
+ QHash<QString, StudioSubPresentation> m_subpresentations;
+ QScopedPointer<Q3DSQmlStreamProxy> m_proxy;
+ QMap<QString, int> m_initialFrameMap;
+ bool m_fullSizePreview = false;
+ bool m_mouseDown = false;
+
+ SRendererImpl()
+ : m_Dispatch(*g_StudioApp.GetCore()->GetDispatch())
+ , m_Doc(*g_StudioApp.GetCore()->GetDoc())
+ , m_HasPresentation(false)
+ , m_Closed(false)
+ , m_UpdatableEditor(m_Doc)
+ , m_LastDragToolMode(MovementTypes::Unknown)
+ , m_MaybeDragStart(false)
+ , m_EditCameraIndex(-1)
+ , m_RenderRequested(false)
+ , m_LastToolMode(0)
+ , m_GuidesEnabled(true)
+ , m_pixelRatio(0.0)
+ {
+ m_Dispatch.AddReloadListener(this);
+ m_Dispatch.AddDataModelListener(this);
+ m_Dispatch.AddPresentationChangeListener(this);
+ m_SelectionSignal =
+ m_Dispatch.ConnectSelectionChange(std::bind(&SRendererImpl::OnSelectionChange, this));
+ m_Dispatch.AddSceneDragListener(this);
+ m_Dispatch.AddToolbarChangeListener(this);
+ }
+ ~SRendererImpl() override
+ {
+ Close();
+ m_Dispatch.RemoveDataModelListener(this);
+ m_Dispatch.RemovePresentationChangeListener(this);
+ m_Dispatch.RemoveSceneDragListener(this);
+ m_Dispatch.RemoveToolbarChangeListener(this);
+ }
+
+ // IDocSceneGraph
+ QT3DSVec3 GetIntendedPosition(qt3dsdm::Qt3DSDMInstanceHandle inHandle, CPt inPoint) override
+ {
+ if (m_Translation)
+ return m_Translation->GetIntendedPosition(inHandle, inPoint);
+
+ return QT3DSVec3(0, 0, 0);
+ }
+
+ void RegisterSubpresentations(const QVector<SubPresentationRecord> &subpresentations) override
+ {
+ if (m_proxy.isNull())
+ m_proxy.reset(new Q3DSQmlStreamProxy());
+ IOffscreenRenderManager &offscreenMgr(m_Context->GetOffscreenRenderManager());
+ const QString projectPath = m_Doc.GetCore()->getProjectFile().getProjectPath();
+ // setPath expects full path, but strips the filename
+ m_proxy->setPath(projectPath + QLatin1Char('/'));
+ QVector<SubPresentationRecord> toUnregister;
+ QVector<SubPresentationRecord> toRegister;
+ const auto keys = m_subpresentations.keys();
+ for (QString key : keys) {
+ if (!subpresentations.contains(m_subpresentations[key].subpresentation))
+ toUnregister.append(m_subpresentations[key].subpresentation);
+ }
+
+ for (int i = 0; i < subpresentations.size(); ++i) {
+ if (!m_subpresentations.contains(subpresentations[i].m_id)
+ || !(m_subpresentations[subpresentations[i].m_id] == subpresentations[i])) {
+ toRegister.append(subpresentations[i]);
+ }
+ }
+
+ for (int i = 0; i < toUnregister.size(); ++i) {
+ QByteArray data = toUnregister[i].m_id.toLocal8Bit();
+ qt3ds::render::CRegisteredString rid
+ = m_Context->GetStringTable().RegisterStr(data.data());
+ offscreenMgr.ReleaseOffscreenRenderer(qt3ds::render::SOffscreenRendererKey(rid));
+ m_subpresentations.remove(toUnregister[i].m_id);
+ m_proxy->unregisterPresentation(toUnregister[i].m_id);
+ }
+
+ for (int i = 0; i < toRegister.size(); ++i) {
+ QByteArray data = toRegister[i].m_id.toLocal8Bit();
+ qt3ds::render::CRegisteredString rid
+ = m_Context->GetStringTable().RegisterStr(data.data());
+ if (toRegister[i].m_type == QStringLiteral("presentation-qml")) {
+ m_proxy->registerPresentation(toRegister[i].m_id, toRegister[i].m_argsOrSrc);
+
+ qt3ds::render::IOffscreenRenderer *theOffscreenRenderer =
+ QT3DS_NEW(m_Context->GetAllocator(),
+ Q3DSQmlRender)(*m_Context, data.data());
+ offscreenMgr.RegisterOffscreenRenderer(
+ qt3ds::render::SOffscreenRendererKey(rid), *theOffscreenRenderer);
+ m_subpresentations[toRegister[i].m_id].renderer = theOffscreenRenderer;
+ theOffscreenRenderer->addCallback(this);
+ } else {
+ qt3ds::render::IOffscreenRenderer *theOffscreenRenderer =
+ QT3DS_NEW(m_Context->GetAllocator(),
+ StudioSubpresentationRenderer)(*m_Context, toRegister[i].m_id,
+ toRegister[i].m_argsOrSrc,
+ projectPath);
+ offscreenMgr.RegisterOffscreenRenderer(
+ qt3ds::render::SOffscreenRendererKey(rid), *theOffscreenRenderer);
+ m_subpresentations[toRegister[i].m_id].renderer = theOffscreenRenderer;
+ theOffscreenRenderer->addCallback(this);
+ }
+ m_subpresentations[toRegister[i].m_id].subpresentation = toRegister[i];
+ }
+ // Process qml proxy events so that we have initialized the qml producer,
+ // then get the desired environment to initialize the qml renderer.
+ QCoreApplication::processEvents();
+ for (int i = 0; i < toRegister.size(); ++i) {
+ if (toRegister[i].m_type == QLatin1String("presentation-qml"))
+ m_subpresentations[toRegister[i].m_id].renderer
+ ->GetDesiredEnvironment(QT3DSVec2(1.0f, 1.0f));
+ }
+ RequestRender();
+ }
+
+ void ReleaseOffscreenRenderersForSubpresentations()
+ {
+ if (!m_Context.mPtr)
+ return;
+
+ IOffscreenRenderManager &offscreenMgr(m_Context->GetOffscreenRenderManager());
+
+ QVector<SubPresentationRecord> toUnregister;
+
+ const auto keys = m_subpresentations.keys();
+ for (QString key : keys)
+ toUnregister.append(m_subpresentations[key].subpresentation);
+
+ for (int i = 0; i < toUnregister.size(); ++i) {
+ QByteArray data = toUnregister[i].m_id.toLocal8Bit();
+ qt3ds::render::CRegisteredString rid
+ = m_Context->GetStringTable().RegisterStr(data.data());
+ offscreenMgr.ReleaseOffscreenRenderer(qt3ds::render::SOffscreenRendererKey(rid));
+ }
+ }
+
+ void onOffscreenRendererInitialized(const QString &id) override
+ {
+ // Request render after first frame rendered by the offscreen renderer
+ m_initialFrameMap[id] = 1;
+ }
+
+ void onOffscreenRendererFrame(const QString &id) override
+ {
+ if (m_initialFrameMap.contains(id)) {
+ RequestRender();
+ m_initialFrameMap.remove(id);
+ }
+ }
+
+ ITextRenderer *GetTextRenderer() override
+ {
+ if (m_Context.mPtr)
+ return m_Context->GetTextRenderer();
+ return nullptr;
+ }
+ // The buffer manager may not be available
+ IBufferManager *GetBufferManager() override
+ {
+ if (m_Context.mPtr)
+ return &m_Context->GetBufferManager();
+ return nullptr;
+ }
+
+ IPathManager *GetPathManager() override
+ {
+ if (m_Context.mPtr)
+ return &m_Context->GetPathManager();
+ return nullptr;
+ }
+
+ qt3ds::foundation::IStringTable *GetRenderStringTable() override
+ {
+ if (m_Context.mPtr)
+ return &m_Context->GetStringTable();
+ return nullptr;
+ }
+
+ bool IsInitialized() override { return m_Context.mPtr != nullptr; }
+
+ void Initialize(QWidget *inWindow) override
+ {
+ if (m_Closed)
+ return;
+ QT3DS_ASSERT(!m_RenderContext);
+ QT3DS_ASSERT(m_Context.mPtr == nullptr);
+ try {
+ m_RenderContext = std::make_shared<CWGLRenderContext>(inWindow);
+
+ Q3DStudio::CString theResourcePath = Q3DStudio::CString::fromQString(
+ StudioUtils::resourcePath());
+ NVScopedRefCounted<qt3ds::render::IQt3DSRenderContextCore> theCore =
+ qt3ds::render::IQt3DSRenderContextCore::Create(
+ m_RenderContext->GetRenderContext().GetFoundation(),
+ m_RenderContext->GetRenderContext().GetStringTable());
+
+ // Create text renderer
+ qt3ds::render::ITextRendererCore &theTextRenderer(
+ qt3ds::render::ITextRendererCore::CreateQtTextRenderer(
+ m_RenderContext->GetRenderContext().GetFoundation(),
+ m_RenderContext->GetRenderContext().GetStringTable()));
+ theCore->SetTextRendererCore(theTextRenderer);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ ITextRendererCore &distanceFieldRenderer(
+ ITextRendererCore::createDistanceFieldRenderer(
+ m_RenderContext->GetRenderContext().GetFoundation()));
+ theCore->setDistanceFieldRenderer(distanceFieldRenderer);
+#endif
+
+ m_Context = theCore->CreateRenderContext(
+ m_RenderContext->GetRenderContext(),
+ m_RenderContext->GetRenderContext().GetStringTable().RegisterStr(
+ theResourcePath.c_str()), false);
+
+ // Allow the artist to interact with the top level objects alone.
+ m_Context->GetRenderer().PickRenderPlugins(false);
+
+ SetupTextRenderer();
+
+ m_Context->SetAuthoringMode(true);
+
+ InitializePointerTags(m_Context->GetStringTable());
+ SetViewRect(m_Rect);
+#ifdef KDAB_TEMPORARILY_REMOVE
+ // KDAB_TODO the below call asserts on windows
+ m_RenderContext->GetRenderContext().SetClearColor(QT3DSVec4(0, 0, 0, 1));
+#endif
+ if (m_HasPresentation)
+ CreateTranslator();
+
+ // Notify that renderer has been initialized
+ m_Dispatch.FireOnRendererInitialized();
+ } catch (...) {
+ m_Context = nullptr;
+ m_RenderContext = std::shared_ptr<CWGLRenderContext>();
+ throw;
+ }
+ }
+
+ void SetViewRect(const QRect &inRect) override
+ {
+ if (m_RenderContext)
+ m_RenderContext->resized();
+
+ m_Rect = inRect;
+ if (IsInitialized()) {
+ m_pixelRatio = StudioUtils::devicePixelRatio();
+ m_RenderContext->BeginRender();
+ NVRenderContext &theContext = m_RenderContext->GetRenderContext();
+ theContext.SetViewport(qt3ds::render::NVRenderRect(0, 0, inRect.width(),
+ inRect.height()));
+ SetTranslationViewport();
+ m_RenderContext->EndRender();
+ }
+ }
+
+ void setFullSizePreview(bool enabled) override
+ {
+ m_fullSizePreview = enabled;
+ }
+
+ void setIsSceneCameraView(bool sceneCameraView) override
+ {
+ m_RenderContext->GetRenderContext().setIsSceneCameraView(sceneCameraView);
+ }
+
+ // Request that this object renders. May be ignored if a transaction
+ // is ongoing so we don't get multiple rendering per transaction.
+ void RequestRender() override
+ {
+ if (m_RenderContext)
+ m_RenderContext->requestRender();
+ }
+
+ void RenderRequestedRender()
+ {
+ if (m_RenderRequested) {
+ m_RenderContext->requestRender();
+ }
+ }
+
+ void RenderNow() override
+ {
+ Render();
+ }
+
+ void getPreviewFbo(QSize &outFboDim, qt3ds::QT3DSU32 &outFboTexture) override
+ {
+ if (m_Translation) {
+ outFboDim = QSize(m_Translation->m_previewFboDimensions.x,
+ m_Translation->m_previewFboDimensions.y);
+ // The handle is a void * so first cast to size_t to avoid truncating pointer warning
+ if (m_Translation->m_previewTexture) {
+ outFboTexture = static_cast<qt3ds::QT3DSU32>(reinterpret_cast<size_t>(
+ m_Translation->m_previewTexture->GetTextureObjectHandle()));
+ } else {
+ outFboTexture = 0;
+ }
+
+ } else {
+ outFboDim = QSize(0, 0);
+ outFboTexture = 0;
+ }
+ }
+
+ bool isMouseDown() const override
+ {
+ return m_mouseDown;
+ }
+
+ void MakeContextCurrent() override
+ {
+ m_RenderContext->BeginRender();
+ }
+
+ void ReleaseContext() override
+ {
+ m_RenderContext->EndRender();
+ }
+
+ void Render()
+ {
+ m_RenderRequested = false;
+ if (!m_Closed && IsInitialized()) {
+ m_RenderContext->BeginRender();
+ bool overlayPreview = false;
+ if (m_Translation) {
+ overlayPreview = CStudioPreferences::showEditModePreview()
+ && m_Translation->m_EditCameraEnabled
+ && m_Translation->hasRoomForOverlayPreview();
+ m_Translation->PreRender(overlayPreview || m_fullSizePreview);
+ }
+ NVRenderContext &theContext = m_RenderContext->GetRenderContext();
+ theContext.SetDepthWriteEnabled(true);
+ theContext.Clear(qt3ds::render::NVRenderClearFlags(
+ qt3ds::render::NVRenderClearValues::Color
+ | qt3ds::render::NVRenderClearValues::Depth));
+ if (m_Translation) {
+ if (overlayPreview || m_fullSizePreview) {
+ // Full size preview is used for both scene camera tab and overlay preview
+ m_Translation->Render(0, false, true, false);
+ m_Translation->PreRender(false);
+ }
+ m_Translation->Render(m_PickResult.GetWidgetId(), m_GuidesEnabled, false,
+ overlayPreview);
+ }
+
+ m_RenderContext->EndRender();
+ }
+ }
+ void GetEditCameraList(QStringList &outCameras) override
+ {
+ outCameras.clear();
+ for (QT3DSU32 idx = 0; idx < g_NumEditCameras; ++idx)
+ outCameras.push_back(g_EditCameraDefinitions[idx].m_Name);
+ }
+ void SetPolygonFillModeEnabled(bool inEnableLight) override
+ {
+ CStudioPreferences::SetEditViewFillMode(inEnableLight);
+ RequestRender();
+ }
+
+ bool DoesEditCameraSupportRotation(QT3DSI32 inIndex) override
+ {
+ if (inIndex >= 0 && inIndex < (QT3DSI32)g_NumEditCameras)
+ return g_EditCameraDefinitions[inIndex].m_Type != EditCameraTypes::Directional;
+ return false;
+ }
+
+ bool AreGuidesEnabled() const override { return m_GuidesEnabled; }
+
+ void SetGuidesEnabled(bool val) override { m_GuidesEnabled = val; }
+
+ bool AreGuidesEditable() const override { return m_Doc.isValid() ? m_Doc.GetDocumentReader().AreGuidesEditable() : false; }
+
+ void SetGuidesEditable(bool val) override { if (m_Doc.isValid()) m_Doc.GetDocumentReader().SetGuidesEditable(val); }
+
+ // Setting the camera to -1 disables the edit cameras
+ // So setting the camera to 0- (numcameras - 1) will set change the active
+ // edit camera.
+ void SetEditCamera(QT3DSI32 inIndex) override
+ {
+ QT3DSI32 oldIndex = m_EditCameraIndex;
+ m_EditCameraIndex = qMin(inIndex, (QT3DSI32)g_NumEditCameras);
+ // save the old edit camera information
+ if (oldIndex != m_EditCameraIndex && m_Translation && m_Translation->m_EditCameraEnabled) {
+ while (m_EditCameraInformation.size() <= (QT3DSU32)oldIndex)
+ m_EditCameraInformation.push_back(Empty());
+
+ m_EditCameraInformation[oldIndex] = m_Translation->m_EditCameraInfo;
+ }
+
+ ApplyEditCameraIndex();
+ RequestRender();
+ }
+
+ QT3DSI32 GetEditCamera() const override
+ {
+ if (m_EditCameraIndex >= 0 && m_EditCameraIndex < (QT3DSI32)g_NumEditCameras)
+ return m_EditCameraIndex;
+ return -1;
+ }
+
+ bool IsPolygonFillModeEnabled() const override
+ {
+ return GetEditCamera() >= 0 && CStudioPreferences::GetEditViewFillMode();
+ }
+
+ void EditCameraZoomToFit() override
+ {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = m_Doc.GetSelectedInstance();
+ if (!m_Translation || m_Translation->m_EditCameraEnabled == false)
+ return;
+ // If we aren't pointed at a node then bounce up the asset graph till we are.
+ while (theInstance.Valid() && m_Translation->GetOrCreateTranslator(theInstance)
+ && GraphObjectTypes::IsNodeType(
+ m_Translation->GetOrCreateTranslator(theInstance)->GetGraphObject().m_Type)
+ == false) {
+ theInstance = m_Translation->m_AssetGraph.GetParent(theInstance);
+ }
+ // If we still aren't pointed at a node then use the active layer.
+ if (theInstance.Valid() == false
+ || m_Translation->GetOrCreateTranslator(theInstance) == nullptr
+ || GraphObjectTypes::IsNodeType(
+ m_Translation->GetOrCreateTranslator(theInstance)->GetGraphObject().m_Type)
+ == false) {
+ theInstance = m_Doc.GetActiveLayer();
+ }
+
+ // If we *still* aren't pointed at a node then bail.
+ if (m_Translation->GetOrCreateTranslator(theInstance) == nullptr
+ || GraphObjectTypes::IsNodeType(
+ m_Translation->GetOrCreateTranslator(theInstance)->GetGraphObject().m_Type)
+ == false) {
+ return;
+ }
+
+ SNode &theNode = static_cast<SNode &>(
+ m_Translation->GetOrCreateTranslator(theInstance)->GetGraphObject());
+ qt3ds::NVBounds3 theBounds;
+ theBounds.setEmpty();
+ if (theNode.m_Type == GraphObjectTypes::Layer) {
+ SNode *theEditLayer = m_Translation->GetEditCameraLayer();
+ if (theEditLayer) {
+ for (SNode *theChild = theEditLayer->m_FirstChild; theChild;
+ theChild = theChild->m_NextSibling) {
+ qt3ds::NVBounds3 childBounds = theChild->GetBounds(
+ m_Context->GetBufferManager(), m_Context->GetPathManager());
+ if (childBounds.isEmpty() == false) {
+ childBounds.transform(theChild->m_GlobalTransform);
+ theBounds.include(childBounds);
+ }
+ }
+ }
+ } else {
+ theBounds =
+ theNode.GetBounds(m_Context->GetBufferManager(), m_Context->GetPathManager());
+ }
+
+ // Fake bounds for non-physical objects
+ if (theBounds.isEmpty()) {
+ const int dim = 50.0f; // Dimensions of a default sized cube
+ theBounds = qt3ds::NVBounds3(QT3DSVec3(-dim, -dim, -dim), QT3DSVec3(dim, dim, dim));
+ }
+
+ // Empty groups don't have proper global transform, so we need to recalculate it.
+ // For simplicity's sake, we recalculate for all groups, not just empty ones.
+ if (theNode.m_Type == GraphObjectTypes::Node)
+ theNode.CalculateGlobalVariables();
+
+ QT3DSVec3 theCenter = theNode.m_GlobalTransform.transform(theBounds.getCenter());
+
+ // Center the edit camera so that it points directly at the bounds center point
+ m_Translation->m_EditCameraInfo.m_Position = theCenter;
+ // Now we need to adjust the camera's zoom such that the view frustum contains the bounding
+ // box.
+ // But to do that I need to figure out what the view frustum is at -600 units from the near
+ // clip plane
+
+ QT3DSVec3 theExtents = theBounds.getExtents().multiply(theNode.m_Scale);
+
+ // get the largest extent and then some addition so things fit nicely in the viewport.
+ QT3DSF32 theMaxPossibleRadius = theExtents.magnitude();
+
+ // easiest case, the viewport dimensions map directly to the
+ m_Translation->m_EditCameraInfo.m_ViewRadius = theMaxPossibleRadius;
+ RequestRender();
+ }
+
+ // This must be safe to call from multiple places
+ void Close() override
+ {
+ ReleaseOffscreenRenderersForSubpresentations();
+ m_subpresentations.clear();
+ m_proxy.reset();
+ m_Closed = true;
+ m_Translation = std::shared_ptr<STranslation>();
+ m_Context = nullptr;
+ m_RenderContext = std::shared_ptr<CWGLRenderContext>();
+ }
+
+ // Data model listener
+
+ // Fired before a large group of notifications come out so views can
+ // only refresh their view once.
+ void OnBeginDataModelNotifications() override {}
+ // Fired after a large gruop of notifications (onInstancePropertyChanged, etc) come out
+ // so views can be careful about refreshing their data and there view
+ void OnEndDataModelNotifications() override { Render(); }
+
+ // Fired during 3d drag or mouse move events (or keyframe drag) or likewise
+ // events so that views that need to update based on the new data can.
+ void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override
+ {
+ if (m_Translation) {
+ m_Translation->MarkDirty(inInstance);
+ // Pass to translation system
+ Render();
+ }
+ }
+ // Same thing, but fired when more than one instance is being refreshed.
+ void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance,
+ long inInstanceCount) override
+ {
+ // Pass to translation system
+ if (m_Translation) {
+ m_Translation->MarkDirty(inInstance, inInstanceCount);
+ // Pass to translation system
+ Render();
+ }
+ Render();
+ }
+
+ void OnReloadEffectInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override
+ {
+ if (m_Translation)
+ m_Translation->ReleaseEffect(inInstance);
+ }
+
+ void ApplyEditCameraIndex()
+ {
+ if (!m_Translation)
+ return;
+ if (m_EditCameraIndex < 0 || m_EditCameraIndex >= (QT3DSI32)g_NumEditCameras)
+ m_Translation->m_EditCameraEnabled = false;
+ else {
+ const SEditCameraDefinition &theDefinition(g_EditCameraDefinitions[m_EditCameraIndex]);
+
+ while ((size_t)m_EditCameraIndex >= m_EditCameraInformation.size())
+ m_EditCameraInformation.push_back(Empty());
+
+ Option<SEditCameraPersistentInformation> &theCameraInfo =
+ m_EditCameraInformation[m_EditCameraIndex];
+
+ if (!theCameraInfo.hasValue()) {
+ theCameraInfo = SEditCameraPersistentInformation();
+ // TODO - consider resizing clip planes to scene so we use the depth buffer more
+ // accurately
+ // or consider requesting a larger depth buffer from the windowing system.
+ // Setup the camera
+ QT3DSVec3 normalizedDir = theDefinition.m_Direction;
+ normalizedDir.normalize();
+ if (theDefinition.m_Type == EditCameraTypes::Directional) {
+ theCameraInfo->m_Direction = normalizedDir;
+ } else {
+ theCameraInfo->m_Direction = QT3DSVec3(0, 0, -1);
+ theCameraInfo->m_xRotation = -qt3ds::NVAtan(normalizedDir.x / normalizedDir.z);
+ theCameraInfo->m_yRotation = qt3ds::NVAsin(normalizedDir.y);
+ }
+ theCameraInfo->m_CameraType = theDefinition.m_Type;
+ }
+
+ m_Translation->m_EditCameraEnabled = true;
+ m_Translation->m_EditCameraInfo = theCameraInfo;
+ }
+ }
+
+ void SetTranslationViewport()
+ {
+ if (m_Translation) {
+ m_Translation->SetViewport(QT3DSF32(m_Rect.right() - m_Rect.left()),
+ QT3DSF32(m_Rect.bottom() - m_Rect.top()));
+ }
+ }
+
+ void CreateTranslator()
+ {
+ if (!m_Translation) {
+ if (m_Context.mPtr) {
+ m_Translation = std::make_shared<STranslation>(std::ref(*this),
+ std::ref(*m_Context.mPtr));
+ ApplyEditCameraIndex();
+ SetTranslationViewport();
+ }
+ }
+ }
+
+ void SetupTextRenderer()
+ {
+ if (m_Context.mPtr && m_Context->GetTextRenderer()) {
+ if (m_Context->getDistanceFieldRenderer())
+ m_Context->getDistanceFieldRenderer()->ClearProjectFontDirectories();
+ m_Context->GetTextRenderer()->ClearProjectFontDirectories();
+ QString projectPath = g_StudioApp.GetCore()->getProjectFile().getProjectPath();
+ if (!projectPath.isEmpty()) {
+ // Add the installed font folders from the res dir.
+ Q3DStudio::CString thePath(
+ Q3DStudio::CString::fromQString(
+ StudioUtils::resourcePath() + QStringLiteral("/Font")));
+ // For QT3DS-3353 assume project fonts are in a subdirectory relative to project.
+ QString projectFontPath = projectPath + QStringLiteral("/fonts");
+ m_Context->GetTextRenderer()->AddSystemFontDirectory(
+ m_Context->GetStringTable().RegisterStr(thePath.c_str()));
+ m_Context->GetTextRenderer()->AddProjectFontDirectory(
+ m_Context->GetStringTable().RegisterStr(projectFontPath.toLatin1().data()));
+ if (m_Context->getDistanceFieldRenderer()) {
+ m_Context->getDistanceFieldRenderer()->AddSystemFontDirectory(
+ m_Context->GetStringTable().RegisterStr(thePath.c_str()));
+ m_Context->getDistanceFieldRenderer()->AddProjectFontDirectory(
+ m_Context->GetStringTable().RegisterStr(
+ projectFontPath.toLatin1().data()));
+ }
+ }
+ }
+ }
+
+ //==========================================================================
+ /**
+ * New presentation is being created.
+ */
+ void OnNewPresentation() override
+ {
+ OnClosingPresentation();
+ m_proxy.reset();
+ ReleaseOffscreenRenderersForSubpresentations();
+ m_subpresentations.clear();
+ m_HasPresentation = true;
+ // Reset edit camera information.
+ m_EditCameraInformation.clear();
+ // Rebuild translation
+ CreateTranslator();
+ SetupTextRenderer();
+ RequestRender();
+ }
+
+ //==========================================================================
+ /**
+ * The current presentation is being closed.
+ */
+ void OnClosingPresentation() override
+ {
+ // Destroy translation
+ m_Translation = std::shared_ptr<STranslation>();
+ m_HasPresentation = false;
+ }
+
+ void OnSelectionChange() { RequestRender(); }
+
+ qt3dsdm::Qt3DSDMInstanceHandle GetAnchorPointFromPick(SPathPick &inPick)
+ {
+ return m_Translation->GetAnchorPoint(inPick);
+ }
+
+ Qt3DSDMInstanceHandle getObjectAt(const QPoint &pt) override
+ {
+ if (m_Translation == nullptr)
+ return Qt3DSDMInstanceHandle();
+
+ const QPoint point(pt * m_pixelRatio);
+ const auto pick = m_Translation->Pick(point, TranslationSelectMode::Single, true);
+ if (pick.getType() == StudioPickValueTypes::Instance)
+ return pick.getData<Qt3DSDMInstanceHandle>();
+ return Qt3DSDMInstanceHandle();
+ }
+
+ //==========================================================================
+ // CSceneDragListener
+ //==========================================================================
+ void OnSceneMouseDown(SceneDragSenderType::Enum inSenderType, QPoint inPoint, int) override
+ {
+ if (m_Translation == nullptr)
+ return;
+
+ m_mouseDown = true;
+ inPoint.setX(inPoint.x() * m_pixelRatio);
+ inPoint.setY(inPoint.y() * m_pixelRatio);
+
+ m_PickResult = SStudioPickValue();
+ if (inSenderType == SceneDragSenderType::SceneWindow) {
+ PickTargetAreas::Enum pickArea = m_Translation->GetPickArea(inPoint);
+ if (pickArea == PickTargetAreas::Presentation) {
+ TranslationSelectMode::Enum theSelectMode = TranslationSelectMode::Group;
+ switch (g_StudioApp.GetSelectMode()) {
+ case STUDIO_SELECTMODE_ENTITY:
+ theSelectMode = TranslationSelectMode::Single;
+ break;
+ case STUDIO_SELECTMODE_GROUP:
+ theSelectMode = TranslationSelectMode::Group;
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ m_RenderContext->BeginRender();
+ m_PickResult = m_Translation->Pick(inPoint, theSelectMode);
+ m_RenderContext->EndRender();
+ // If we definitely did not pick a widget.
+ if (m_PickResult.getType() == StudioPickValueTypes::Instance) {
+ qt3dsdm::Qt3DSDMInstanceHandle theHandle(
+ m_PickResult.getData<Qt3DSDMInstanceHandle>());
+ if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
+ m_Doc.ToggleDataModelObjectToSelection(theHandle);
+ } else {
+ if (m_Doc.getSelectedInstancesCount() > 1)
+ m_Doc.DeselectAllItems(true);
+
+ if (theHandle != m_Doc.GetSelectedInstance())
+ m_Doc.SelectDataModelObject(theHandle);
+ }
+ } else if (m_PickResult.getType() == StudioPickValueTypes::Guide) {
+ m_Doc.NotifySelectionChanged(
+ m_PickResult.getData<qt3dsdm::Qt3DSDMGuideHandle>());
+ } else if (m_PickResult.getType() == StudioPickValueTypes::Path) {
+ SPathPick thePick = m_PickResult.getData<SPathPick>();
+ qt3dsdm::Qt3DSDMInstanceHandle theAnchorHandle =
+ m_Translation->GetAnchorPoint(thePick);
+ if (theAnchorHandle.Valid() && theAnchorHandle != m_Doc.GetSelectedInstance())
+ m_Doc.SelectDataModelObject(theAnchorHandle);
+ } else if (m_PickResult.getType() == StudioPickValueTypes::UnknownValueType) {
+ m_Doc.DeselectAllItems(true);
+ }
+ RequestRender();
+ } else if (pickArea == PickTargetAreas::Matte) {
+ qt3ds::foundation::Option<qt3dsdm::SGuideInfo> pickResult =
+ m_Translation->PickRulers(inPoint);
+ if (pickResult.hasValue()) {
+ Q3DStudio::IDocumentEditor &docEditor(
+ m_UpdatableEditor.EnsureEditor(QObject::tr("Create Guide"),
+ __FILE__, __LINE__));
+ Qt3DSDMGuideHandle newGuide = docEditor.CreateGuide(*pickResult);
+ m_PickResult = SStudioPickValue(newGuide);
+ m_Doc.NotifySelectionChanged(newGuide);
+ } else {
+ m_Doc.DeselectAllItems(true);
+ }
+ }
+ }
+
+ m_LastDragToolMode = MovementTypes::Unknown;
+ m_MaybeDragStart = true;
+ m_MouseDownPoint = inPoint;
+ m_PreviousMousePoint = inPoint;
+ m_MouseDownCameraInformation = m_Translation->m_EditCameraInfo;
+ m_LastToolMode = g_StudioApp.GetToolMode();
+ }
+
+ void OnSceneMouseDrag(SceneDragSenderType::Enum, QPoint inPoint, int inToolMode,
+ int inFlags) override
+ {
+ if (m_Translation == nullptr)
+ return;
+
+ inPoint.setX(inPoint.x() * m_pixelRatio);
+ inPoint.setY(inPoint.y() * m_pixelRatio);
+
+ if (m_MaybeDragStart) {
+ // Dragging in the first 5 pixels will be ignored to avoid unconsciously accidental
+ // moves
+ CPt theDragDistance = inPoint - m_MouseDownPoint;
+ if (m_PickResult.getType() == StudioPickValueTypes::Widget
+ || inToolMode != STUDIO_TOOLMODE_SCALE) {
+ if (theDragDistance.x * theDragDistance.x + theDragDistance.y * theDragDistance.y
+ <= 25)
+ return;
+ } else {
+ if (qAbs(theDragDistance.y) <= 5)
+ return;
+ }
+ }
+
+ m_MaybeDragStart = false;
+
+ // If the tool mode changes then we throw out the last widget pick if there was one.
+ if (m_LastToolMode != inToolMode)
+ m_PickResult = SStudioPickValue();
+ m_LastToolMode = inToolMode;
+
+ // General dragging
+ if (m_PickResult.getType() == StudioPickValueTypes::Instance
+ || m_PickResult.getType()
+ == StudioPickValueTypes::UnknownValueType) // matte drag and widget drag
+ {
+ // Not sure what right-click drag does in the scene.
+ bool isEditCamera = m_Translation->m_EditCameraEnabled;
+ int theCameraToolMode = isEditCamera ? (inToolMode & (STUDIO_CAMERATOOL_MASK)) : 0;
+ bool rightClick = (inFlags & CHotKeys::MOUSE_RBUTTON) != 0;
+
+ if (theCameraToolMode == 0) {
+ if (m_Doc.GetDocumentReader().IsInstance(m_Doc.GetSelectedInstance())) {
+ if (m_Doc.getSelectedInstancesCount() == 1) {
+ bool rightClick = (inFlags & CHotKeys::MOUSE_RBUTTON) != 0;
+ MovementTypes::Enum theMovement(MovementTypes::Unknown);
+
+ switch (inToolMode) {
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ case STUDIO_TOOLMODE_MOVE:
+ if (rightClick)
+ theMovement = MovementTypes::TranslateAlongCameraDirection;
+ else
+ theMovement = MovementTypes::Translate;
+ break;
+ case STUDIO_TOOLMODE_SCALE:
+ if (rightClick)
+ theMovement = MovementTypes::ScaleZ;
+ else
+ theMovement = MovementTypes::Scale;
+ break;
+ case STUDIO_TOOLMODE_ROTATE:
+ if (rightClick)
+ theMovement = MovementTypes::RotationAboutCameraDirection;
+ else
+ theMovement = MovementTypes::Rotation;
+ break;
+ }
+
+ if (theMovement != MovementTypes::Unknown) {
+ bool theLockToAxis = (inFlags & CHotKeys::MODIFIER_SHIFT) != 0;
+
+ if (m_LastDragToolMode != MovementTypes::Unknown
+ && theMovement != m_LastDragToolMode) {
+ m_UpdatableEditor.RollbackEditor();
+ m_MouseDownPoint = inPoint;
+ }
+
+ m_LastDragToolMode = theMovement;
+
+ switch (theMovement) {
+ case MovementTypes::TranslateAlongCameraDirection:
+ m_Translation->TranslateSelectedInstanceAlongCameraDirection(
+ m_MouseDownPoint, inPoint, m_UpdatableEditor);
+ break;
+ case MovementTypes::Translate:
+ m_Translation->TranslateSelectedInstance(
+ m_MouseDownPoint, inPoint, m_UpdatableEditor, theLockToAxis);
+ break;
+ case MovementTypes::ScaleZ:
+ m_Translation->ScaleSelectedInstanceZ(m_MouseDownPoint, inPoint,
+ m_UpdatableEditor);
+ break;
+ case MovementTypes::Scale:
+ m_Translation->ScaleSelectedInstance(m_MouseDownPoint, inPoint,
+ m_UpdatableEditor);
+ break;
+ case MovementTypes::Rotation:
+ m_Translation->RotateSelectedInstance(
+ m_MouseDownPoint, m_PreviousMousePoint, inPoint,
+ m_UpdatableEditor, theLockToAxis);
+ break;
+ case MovementTypes::RotationAboutCameraDirection:
+ m_Translation->RotateSelectedInstanceAboutCameraDirectionVector(
+ m_PreviousMousePoint, inPoint, m_UpdatableEditor);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ QT3DSF32 theXDistance = static_cast<QT3DSF32>(inPoint.x() - m_MouseDownPoint.x);
+ QT3DSF32 theYDistance = static_cast<QT3DSF32>(inPoint.y() - m_MouseDownPoint.y);
+ QT3DSF32 theSubsetXDistance = static_cast<QT3DSF32>(inPoint.x() - m_PreviousMousePoint.x);
+ QT3DSF32 theSubsetYDistance = static_cast<QT3DSF32>(inPoint.y() - m_PreviousMousePoint.y);
+
+ // Edit cameras are not implemented.
+ switch (theCameraToolMode) {
+ case STUDIO_TOOLMODE_CAMERA_PAN: {
+ QT3DSVec3 theXAxis =
+ m_Translation->m_EditCamera.m_GlobalTransform.column0.getXYZ();
+ QT3DSVec3 theYAxis =
+ m_Translation->m_EditCamera.m_GlobalTransform.column1.getXYZ();
+ QT3DSVec3 theXChange = -1.0f * theXAxis * theXDistance;
+ QT3DSVec3 theYChange = theYAxis * theYDistance;
+ QT3DSVec3 theDiff = theXChange + theYChange;
+ m_Translation->m_EditCameraInfo.m_Position =
+ m_MouseDownCameraInformation.m_Position + theDiff;
+ RequestRender();
+ } break;
+ case STUDIO_TOOLMODE_CAMERA_ZOOM: {
+ QT3DSF32 theMultiplier = 1.0f + theSubsetYDistance / 40.0f;
+ m_Translation->m_EditCameraInfo.m_ViewRadius =
+ qMax(.0001f, m_Translation->m_EditCameraInfo.m_ViewRadius * theMultiplier);
+ RequestRender();
+ } break;
+ case STUDIO_TOOLMODE_CAMERA_ROTATE: {
+ if (m_Translation->m_EditCameraInfo.SupportsRotation()) {
+ if (!rightClick) {
+ m_Translation->m_EditCameraInfo.m_xRotation =
+ m_MouseDownCameraInformation.m_xRotation
+ + (theSubsetXDistance * g_RotationScaleFactor / 20.0f);
+ m_Translation->m_EditCameraInfo.m_yRotation =
+ m_MouseDownCameraInformation.m_yRotation
+ - (theSubsetYDistance * g_RotationScaleFactor / 20.0f);
+ // Avoid rounding errors stemming from extremely large rotation angles
+ if (m_Translation->m_EditCameraInfo.m_xRotation < -qt3ds::NVPi)
+ m_Translation->m_EditCameraInfo.m_xRotation += qt3ds::NVPi * 2.0f;
+ if (m_Translation->m_EditCameraInfo.m_xRotation > qt3ds::NVPi)
+ m_Translation->m_EditCameraInfo.m_xRotation -= qt3ds::NVPi * 2.0f;
+ if (m_Translation->m_EditCameraInfo.m_yRotation < -qt3ds::NVPi)
+ m_Translation->m_EditCameraInfo.m_yRotation += qt3ds::NVPi * 2.0f;
+ if (m_Translation->m_EditCameraInfo.m_yRotation > qt3ds::NVPi)
+ m_Translation->m_EditCameraInfo.m_yRotation -= qt3ds::NVPi * 2.0f;
+ }
+ m_MouseDownCameraInformation.m_xRotation =
+ m_Translation->m_EditCameraInfo.m_xRotation;
+ m_MouseDownCameraInformation.m_yRotation =
+ m_Translation->m_EditCameraInfo.m_yRotation;
+ RequestRender();
+ }
+ } break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ }
+ } // if ( m_PickResult.m_WidgetId.hasValue() == false )
+
+ // We need to do widget-specific dragging.
+ else if (m_PickResult.getType() == StudioPickValueTypes::Widget) {
+ m_Translation->PerformWidgetDrag(m_PickResult.GetWidgetId(), m_MouseDownPoint,
+ m_PreviousMousePoint, inPoint, m_UpdatableEditor);
+ } else if (m_PickResult.getType() == StudioPickValueTypes::Guide) {
+ m_Translation->PerformGuideDrag(m_PickResult.getData<Qt3DSDMGuideHandle>(), inPoint,
+ m_UpdatableEditor);
+ } else if (m_PickResult.getType() == StudioPickValueTypes::Path) {
+ SPathPick thePick = m_PickResult.getData<SPathPick>();
+ m_Translation->PerformPathDrag(thePick, m_MouseDownPoint, m_PreviousMousePoint, inPoint,
+ m_UpdatableEditor);
+ }
+ m_PreviousMousePoint = inPoint;
+ }
+
+ void OnSceneMouseUp(SceneDragSenderType::Enum) override
+ {
+ m_MaybeDragStart = false;
+ m_mouseDown = false;
+ Qt3DSDMGuideHandle theSelectedGuide;
+ if (m_PickResult.getType() == StudioPickValueTypes::Guide) {
+ theSelectedGuide = m_PickResult.getData<Qt3DSDMGuideHandle>();
+ m_Translation->CheckGuideInPresentationRect(theSelectedGuide, m_UpdatableEditor);
+ }
+ m_UpdatableEditor.CommitEditor();
+ m_PickResult = SStudioPickValue();
+ if (m_Translation)
+ m_Translation->EndDrag();
+ if (theSelectedGuide.GetHandleValue()) {
+ // Get rid of selection if things aren't editable.
+ if (m_Doc.GetDocumentReader().AreGuidesEditable())
+ m_Doc.NotifySelectionChanged(theSelectedGuide);
+ else
+ m_Doc.NotifySelectionChanged();
+ }
+ RequestRender();
+ }
+
+ void OnSceneMouseDblClick(SceneDragSenderType::Enum inSenderType, QPoint inPoint) override
+ {
+ if (inSenderType == SceneDragSenderType::SceneWindow && m_Translation) {
+ inPoint.setX(inPoint.x() * m_pixelRatio);
+ inPoint.setY(inPoint.y() * m_pixelRatio);
+ m_RenderContext->BeginRender();
+ SStudioPickValue theResult(
+ m_Translation->Pick(inPoint, TranslationSelectMode::NestedComponentSingle));
+ m_RenderContext->EndRender();
+
+ if (theResult.getType() == StudioPickValueTypes::Instance)
+ m_Doc.SelectAndNavigateToDataModelObject(theResult.getData<Qt3DSDMInstanceHandle>());
+ else if (theResult.getType() == StudioPickValueTypes::Path) {
+ SPathPick thePickValue = theResult.getData<SPathPick>();
+ qt3dsdm::Qt3DSDMInstanceHandle theAnchorHandle =
+ m_Translation->GetAnchorPoint(thePickValue);
+ if (theAnchorHandle.Valid() && theAnchorHandle != m_Doc.GetSelectedInstance()) {
+ m_Doc.SelectDataModelObject(theAnchorHandle);
+ }
+ }
+ }
+ }
+
+ void OnSceneMouseWheel(SceneDragSenderType::Enum inSenderType, short inDelta,
+ int inToolMode) override
+ {
+ ASSERT(inSenderType == SceneDragSenderType::Matte);
+ if (inToolMode == STUDIO_TOOLMODE_CAMERA_ZOOM && m_Translation) {
+ QT3DSF32 theMultiplier = 1.0f - inDelta / static_cast<QT3DSF32>(120 * g_WheelFactor);
+ m_Translation->m_EditCameraInfo.m_ViewRadius =
+ qMax(.0001f, m_Translation->m_EditCameraInfo.m_ViewRadius * theMultiplier);
+ RequestRender();
+ }
+ }
+
+ void OnToolbarChange() override { RequestRender(); }
+};
+}
+
+std::shared_ptr<IStudioRenderer> IStudioRenderer::CreateStudioRenderer()
+{
+ return std::make_shared<SRendererImpl>();
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioRendererImpl.h b/src/Authoring/Qt3DStudio/Render/StudioRendererImpl.h
new file mode 100644
index 00000000..530bf384
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioRendererImpl.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_RENDERER_IMPL_H
+#define QT3DS_STUDIO_RENDERER_IMPL_H
+#pragma once
+#include "IStudioRenderer.h"
+#include "WGLRenderContext.h"
+#include "Qt3DSDMDataTypes.h"
+#include "Qt3DSDMSignals.h"
+#include "Qt3DSRenderContextCore.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "IDocumentReader.h"
+#include "Qt3DSFileTools.h"
+#include "render/Qt3DSRenderContext.h"
+#include "foundation/Qt3DSVec4.h"
+#include "DispatchListeners.h"
+#include "Dispatch.h"
+#include "Core.h"
+#include "foundation/Qt3DSInvasiveSet.h"
+#include "Qt3DSRenderer.h"
+#include "Qt3DSRenderScene.h"
+#include "Qt3DSRenderNode.h"
+#include "Qt3DSRenderLayer.h"
+#include "Qt3DSRenderModel.h"
+#include "Qt3DSRenderDefaultMaterial.h"
+#include "Qt3DSRenderLight.h"
+#include "Qt3DSRenderCamera.h"
+#include "Qt3DSRenderImage.h"
+#include "Qt3DSRenderPresentation.h"
+#include "StudioProjectSettings.h"
+#include "Qt3DSRenderUIPSharedTranslation.h"
+#include "Qt3DSRenderBufferManager.h"
+#include "StudioFullSystem.h"
+#include "Qt3DSDMSignals.h"
+#include "CoreConst.h"
+#include "IDocumentEditor.h"
+#include "foundation/Qt3DSPlane.h"
+#include "foundation/Qt3DSQuat.h"
+#include "Qt3DSTextRenderer.h"
+#include "foundation/Qt3DSOption.h"
+#include "foundation/Qt3DSMathUtils.h"
+#include "Qt3DSRenderEffect.h"
+#include "Qt3DSRenderPath.h"
+#include "Qt3DSRenderPathSubPath.h"
+
+namespace qt3ds {
+namespace studio {
+ using Q3DStudio::IDocumentReader;
+ using Q3DStudio::CUpdateableDocumentEditor;
+ using Q3DStudio::TIdentifier;
+ using Q3DStudio::IStudioRenderer;
+ using qt3ds::foundation::NVScopedRefCounted;
+ using qt3ds::QT3DSVec3;
+ using qt3ds::QT3DSQuat;
+ using qt3ds::QT3DSF32;
+ using qt3ds::QT3DSMat44;
+ using qt3ds::QT3DSMat33;
+ using qt3ds::QT3DSI32;
+ using qt3ds::QT3DSVec2;
+ using qt3ds::QT3DSVec4;
+ using qt3ds::QT3DSU32;
+ using qt3ds::foundation::Empty;
+ using qt3ds::foundation::InvasiveSet;
+ using qt3ds::foundation::nvhash_map;
+ using qt3ds::foundation::nvvector;
+ using qt3ds::foundation::rotationArc;
+ using qt3ds::foundation::Option;
+ using qt3ds::foundation::Empty;
+ using qt3ds::render::IQt3DSRenderContext;
+ using qt3ds::render::SScene;
+ using qt3ds::render::SLayer;
+ using qt3ds::render::SNode;
+ using qt3ds::render::SGraphObject;
+ using qt3ds::render::SLight;
+ using qt3ds::render::SCamera;
+ using qt3ds::render::SDefaultMaterial;
+ using qt3ds::render::SImage;
+ using qt3ds::render::SModel;
+ using qt3ds::render::SText;
+ using qt3ds::render::GraphObjectTypes;
+ using qt3ds::render::SRay;
+ using qt3ds::render::ITextRenderer;
+ using qt3ds::render::SEffect;
+ using qt3ds::render::IEffectSystem;
+ using qt3ds::render::SDynamicObject;
+ using qt3ds::render::SCustomMaterial;
+ using qt3ds::render::IDynamicObjectSystem;
+ using qt3ds::render::ICustomMaterialSystem;
+ using qt3ds::render::IBufferManager;
+ using qt3ds::render::IPathManager;
+ using qt3ds::render::SPath;
+ using qt3ds::render::SPathSubPath;
+ using qt3ds::render::SReferencedMaterial;
+ using qt3ds::render::CRegisteredString;
+ using qt3ds::render::IStringTable;
+ using qt3dsdm::SFloat3;
+ using qt3dsdm::SLong4;
+ using qt3dsdm::SComposerObjectDefinitions;
+ using qt3dsdm::Qt3DSDMInstanceHandle;
+ using qt3dsdm::Qt3DSDMPropertyHandle;
+}
+}
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.cpp b/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.cpp
new file mode 100644
index 00000000..82678986
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.cpp
@@ -0,0 +1,4083 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioRendererImpl.h"
+#include "StudioRendererTranslation.h"
+#include "Qt3DSRenderEffectSystem.h"
+#include "foundation/StrConvertUTF.h"
+#include "Qt3DSFileTools.h"
+#include "Qt3DSRenderUIPLoader.h"
+#include "Qt3DSRenderWidgets.h"
+#include "foundation/Qt3DSBounds3.h"
+#include "Qt3DSRenderResourceManager.h"
+#include "render/Qt3DSRenderFrameBuffer.h"
+#include "Qt3DSRenderCamera.h"
+#include "foundation/Qt3DSPlane.h"
+#include "Qt3DSRenderRotationHelper.h"
+#include "Qt3DSRenderPluginGraphObject.h"
+#include "Qt3DSRenderPlugin.h"
+#include "StudioCoreSystem.h"
+#include "Qt3DSDMDataCore.h"
+#include "Qt3DSRenderPluginPropertyValue.h"
+#include "Qt3DSRenderEffectSystem.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "Qt3DSRenderMaterialHelpers.h"
+#include "Qt3DSRenderDynamicObjectSystem.h"
+#include "Qt3DSRenderCustomMaterialSystem.h"
+#include "Qt3DSRenderReferencedMaterial.h"
+#include "Qt3DSRenderPixelGraphicsTypes.h"
+#include "Qt3DSRenderPixelGraphicsRenderer.h"
+#include "Qt3DSRenderPathManager.h"
+
+#include "PathWidget.h"
+#include "Qt3DSRenderLightmaps.h"
+#include "StudioPreferences.h"
+#include "HotKeys.h"
+#include "Qt3DSRenderCamera.h"
+#include "Qt3DSRenderLight.h"
+
+#pragma warning(disable : 4100) // unreferenced formal parameter
+
+using namespace qt3ds::studio;
+QT3DSU32 qt3ds::studio::g_GraphObjectTranslatorTag;
+using qt3ds::render::SPGGraphObject;
+using qt3ds::render::SPGRect;
+using qt3ds::render::SPGVertLine;
+using qt3ds::render::SPGHorzLine;
+using qt3ds::render::NVRenderRectF;
+using qt3ds::render::NVRenderRect;
+
+namespace {
+using namespace qt3dsdm;
+
+struct STranslatorDataModelParser
+{
+ STranslation &m_Context;
+ Qt3DSDMInstanceHandle m_InstanceHandle;
+ STranslatorDataModelParser(STranslation &inContext, Qt3DSDMInstanceHandle inInstance)
+ : m_Context(inContext)
+ , m_InstanceHandle(inInstance)
+ {
+ }
+ Qt3DSDMInstanceHandle GetInstanceHandle() { return m_InstanceHandle; }
+
+ template <typename TDataType>
+ inline Option<TDataType> GetPropertyValue(qt3dsdm::Qt3DSDMPropertyHandle inProperty)
+ {
+ Option<SValue> theValue =
+ m_Context.m_Reader.GetRawInstancePropertyValue(GetInstanceHandle(), inProperty);
+ if (theValue.hasValue())
+ return qt3dsdm::get<TDataType>(*theValue);
+ return Empty();
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSF32 &outValue)
+ {
+ Option<float> theValue = GetPropertyValue<float>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = theValue.getValue();
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSU32 &outValue)
+ {
+ auto theValue = GetPropertyValue<qt3ds::QT3DSI32>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = qMax(theValue.getValue(), 0);
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSI32 &outValue)
+ {
+ auto theValue = GetPropertyValue<qt3ds::QT3DSI32>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = *theValue;
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseRadianProperty(Qt3DSDMPropertyHandle inProperty, QT3DSF32 &outValue)
+ {
+ if (ParseProperty(inProperty, outValue)) {
+ TORAD(outValue);
+ return true;
+ }
+ return false;
+ }
+ bool ParseRadianProperty(Qt3DSDMPropertyHandle inProperty, QT3DSVec3 &outValue)
+ {
+ if (ParseProperty(inProperty, outValue)) {
+ TORAD(outValue.x);
+ TORAD(outValue.y);
+ TORAD(outValue.z);
+ return true;
+ }
+ return false;
+ }
+ bool ParseOpacityProperty(Qt3DSDMPropertyHandle inProperty, QT3DSF32 &outValue)
+ {
+ if (ParseProperty(inProperty, outValue)) {
+ outValue = (1.0f / 100.0f) * outValue;
+ return true;
+ }
+ return false;
+ }
+ bool ParseRotationOrder(Qt3DSDMPropertyHandle inProperty, QT3DSU32 &outValue)
+ {
+ qt3ds::render::CRegisteredString temp;
+ if (ParseProperty(inProperty, temp)) {
+ outValue = qt3ds::render::MapRotationOrder(temp);
+ return true;
+ }
+ return false;
+ }
+ bool ParseOrientation(Qt3DSDMPropertyHandle inProperty, qt3ds::render::NodeFlags &outValue)
+ {
+ qt3ds::render::CRegisteredString temp;
+ if (ParseProperty(inProperty, temp)) {
+ bool isLeftHanded = strcmp(temp.c_str(), "Left Handed") == 0;
+ outValue.SetLeftHanded(isLeftHanded);
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, bool &outValue)
+ {
+ Option<bool> theValue = GetPropertyValue<bool>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = theValue.getValue();
+ return true;
+ }
+ return false;
+ }
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSVec2 &outValue)
+ {
+ Option<qt3dsdm::SFloat2> theValue = GetPropertyValue<qt3dsdm::SFloat2>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = QT3DSVec2(theValue->m_Floats[0], theValue->m_Floats[1]);
+ return true;
+ }
+ return false;
+ }
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSVec3 &outValue)
+ {
+ Option<SFloat3> theValue = GetPropertyValue<SFloat3>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = QT3DSVec3(theValue->m_Floats[0], theValue->m_Floats[1], theValue->m_Floats[2]);
+ return true;
+ }
+ return false;
+ }
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, QT3DSVec4 &outValue)
+ {
+ Option<SFloat4> theValue = GetPropertyValue<SFloat4>(inProperty);
+ if (theValue.hasValue()) {
+ outValue = QT3DSVec4(theValue->m_Floats[0], theValue->m_Floats[1],
+ theValue->m_Floats[2], theValue->m_Floats[3]);
+ return true;
+ }
+ return false;
+ }
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, qt3ds::render::CRegisteredString &outValue)
+ {
+ Option<qt3dsdm::TDataStrPtr> theValue = GetPropertyValue<qt3dsdm::TDataStrPtr>(inProperty);
+ if (theValue.hasValue() && *theValue) {
+ qt3ds::render::IStringTable &theStrTable(m_Context.m_Context.GetStringTable());
+ outValue = theStrTable.RegisterStr((*theValue)->GetData());
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseAndResolveSourcePath(qt3dsdm::Qt3DSDMPropertyHandle inProperty,
+ qt3ds::render::CRegisteredString &outValue)
+ {
+ if (ParseProperty(inProperty, outValue)) {
+ if (outValue.IsValid() && outValue.c_str()[0] != '#') {
+ Q3DStudio::CFilePath theDirectory = m_Context.m_Doc.GetDocumentDirectory();
+ Q3DStudio::CFilePath theResolvedPath =
+ Q3DStudio::CFilePath::CombineBaseAndRelative(theDirectory, outValue.c_str());
+ if (theResolvedPath.exists()) {
+ outValue = m_Context.m_Context.GetStringTable()
+ .RegisterStr(theResolvedPath.toCString());
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ template <typename TEnumType>
+ bool ParseEnumProperty(qt3dsdm::Qt3DSDMPropertyHandle inProperty, TEnumType &ioValue)
+ {
+ qt3ds::render::CRegisteredString temp;
+ if (ParseProperty(inProperty, temp)) {
+ qt3ds::render::SEnumNameMap *theNameMap(qt3ds::render::SEnumParseMap<TEnumType>::GetMap());
+ for (qt3ds::render::SEnumNameMap *theIter = theNameMap;
+ theIter->m_Name && *theIter->m_Name; ++theIter) {
+ // hack to match advanced overlay types, whose name start with a '*'
+ const char8_t *p = temp;
+ if (*p == '*')
+ ++p;
+ if (strcmp(p, theIter->m_Name) == 0) {
+ ioValue = (TEnumType)theIter->m_Enum;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool ParseNodeFlagsProperty(qt3dsdm::Qt3DSDMPropertyHandle inProperty,
+ qt3ds::render::NodeFlags &outValue,
+ qt3ds::render::NodeFlagValues::Enum theFlag)
+ {
+ bool temp = false;
+ if (ParseProperty(inProperty, temp)) {
+ outValue.ClearOrSet(temp, theFlag);
+ return true;
+ }
+ return false;
+ }
+ bool ParseNodeFlagsInverseProperty(qt3dsdm::Qt3DSDMPropertyHandle inProperty,
+ qt3ds::render::NodeFlags &outValue,
+ qt3ds::render::NodeFlagValues::Enum theFlag)
+ {
+ bool temp = false;
+ if (ParseProperty(inProperty, temp)) {
+ outValue.ClearOrSet(!temp, theFlag);
+ return true;
+ }
+ return false;
+ }
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, qt3ds::render::SImage *&ioImage)
+ {
+ Option<SLong4> theData = GetPropertyValue<SLong4>(inProperty);
+ if (theData.hasValue()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(
+ m_Context.m_Reader.GetInstanceForGuid(*theData));
+ SGraphObjectTranslator *imageTranslator = m_Context.GetOrCreateTranslator(theInstance);
+ if (imageTranslator
+ && imageTranslator->GetGraphObject().m_Type
+ == qt3ds::render::GraphObjectTypes::Image) {
+ SImage *theNewImage = static_cast<SImage *>(&imageTranslator->GetGraphObject());
+ ioImage = theNewImage;
+ } else
+ ioImage = nullptr;
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, qt3ds::render::SGraphObject *&ioObjRef)
+ {
+ Option<SObjectRefType> theData = GetPropertyValue<SObjectRefType>(inProperty);
+ if (theData.hasValue()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(
+ m_Context.m_Reader.GetInstanceForObjectRef(m_InstanceHandle, *theData));
+ SGraphObjectTranslator *theItemTranslator =
+ m_Context.GetOrCreateTranslator(theInstance);
+ if (theItemTranslator)
+ ioObjRef = &theItemTranslator->GetGraphObject();
+ }
+ return true;
+ }
+
+ bool ParseProperty(Qt3DSDMPropertyHandle inProperty, qt3ds::render::SNode *&ioNodePtr)
+ {
+ Option<SObjectRefType> theData = GetPropertyValue<SObjectRefType>(inProperty);
+ SNode *theNewNodePtr = nullptr;
+ if (theData.hasValue()) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(
+ m_Context.m_Reader.GetInstanceForObjectRef(m_InstanceHandle, *theData));
+ SGraphObjectTranslator *theItemTranslator =
+ m_Context.GetOrCreateTranslator(theInstance);
+ if (theItemTranslator) {
+ SGraphObject &theObject = theItemTranslator->GetGraphObject();
+ if (GraphObjectTypes::IsNodeType(theObject.m_Type))
+ theNewNodePtr = &static_cast<SNode &>(theObject);
+ }
+ }
+ ioNodePtr = theNewNodePtr;
+ return true;
+ }
+};
+
+// Define parse tables
+#define Scene_ClearColor m_Scene.m_BackgroundColor
+#define Scene_UseClearColor m_Scene.m_BgColorEnable
+#define Node_Rotation m_Node.m_Rotation
+#define Node_Position m_Node.m_Position
+#define Node_Scale m_Node.m_Scale
+#define Node_Pivot m_Node.m_Pivot
+#define Node_LocalOpacity m_Node.m_Opacity
+#define Node_RotationOrder m_Node.m_RotationOrder
+#define Node_LeftHanded m_Node.m_Orientation
+#define Layer_TemporalAAEnabled m_Layer.m_TemporalAA
+#define Layer_LayerEnableDepthTest m_Layer.m_DisableDepthTest
+#define Layer_LayerEnableDepthPrePass m_Layer.m_DisableDepthPrepass
+#define Layer_ClearColor m_Layer.m_BackgroundColor
+#define Layer_Background m_Layer.m_Background
+#define Layer_BlendType m_Layer.m_BlendType
+#define Layer_Size m_Layer.m_Size
+#define Layer_Location m_Layer.m_Location
+#define Layer_ProgressiveAAMode m_Layer.m_ProgressiveAA
+#define Layer_MultisampleAAMode m_Layer.m_MultisampleAA
+#define Layer_HorizontalFieldValues m_Layer.m_HorizontalFieldValues
+#define Layer_Left m_Layer.m_Left
+#define Layer_LeftUnits m_Layer.m_LeftUnits
+#define Layer_Width m_Layer.m_Width
+#define Layer_WidthUnits m_Layer.m_WidthUnits
+#define Layer_Right m_Layer.m_Right
+#define Layer_RightUnits m_Layer.m_RightUnits
+#define Layer_VerticalFieldValues m_Layer.m_VerticalFieldValues
+#define Layer_Top m_Layer.m_Top
+#define Layer_TopUnits m_Layer.m_TopUnits
+#define Layer_Height m_Layer.m_Height
+#define Layer_HeightUnits m_Layer.m_HeightUnits
+#define Layer_Bottom m_Layer.m_Bottom
+#define Layer_BottomUnits m_Layer.m_BottomUnits
+#define Layer_AoStrength m_Layer.m_AoStrength
+#define Layer_AoDistance m_Layer.m_AoDistance
+#define Layer_AoSoftness m_Layer.m_AoSoftness
+#define Layer_AoBias m_Layer.m_AoBias
+#define Layer_AoSamplerate m_Layer.m_AoSamplerate
+#define Layer_AoDither m_Layer.m_AoDither
+#define Layer_ShadowStrength m_Layer.m_ShadowStrength
+#define Layer_ShadowDist m_Layer.m_ShadowDist
+#define Layer_ShadowSoftness m_Layer.m_ShadowSoftness
+#define Layer_ShadowBias m_Layer.m_ShadowBias
+#define Layer_LightProbe m_Layer.m_LightProbe
+#define Layer_ProbeBright m_Layer.m_ProbeBright
+#define Layer_FastIbl m_Layer.m_FastIbl
+#define Layer_ProbeHorizon m_Layer.m_ProbeHorizon
+#define Layer_ProbeFov m_Layer.m_ProbeFov
+#define Layer_LightProbe2 m_Layer.m_LightProbe2
+#define Layer_Probe2Fade m_Layer.m_Probe2Fade
+#define Layer_Probe2Window m_Layer.m_Probe2Window
+#define Layer_Probe2Pos m_Layer.m_Probe2Pos
+#define Layer_TexturePath m_Asset.m_SourcePath
+#define Camera_ClipNear m_Camera.m_ClipNear
+#define Camera_ClipFar m_Camera.m_ClipFar
+#define Camera_FOV m_Camera.m_Fov
+#define Camera_FOVHorizontal m_Camera.m_FovHorizontal
+#define Camera_Orthographic m_Camera.m_Orthographic
+#define Camera_ScaleMode m_Camera.m_ScaleMode
+#define Camera_ScaleAnchor m_Camera.m_ScaleAnchor
+#define Light_LightType m_Light.m_LightType
+#define Light_Scope m_Light.m_Scope
+#define Light_DiffuseColor m_Light.m_LightColor
+#define Light_SpecularColor m_Light.m_SpecularColor
+#define Light_AmbientColor m_Light.m_AmbientColor
+#define Light_Brightness m_Light.m_Brightness
+#define Light_LinearFade m_Light.m_LinearFade
+#define Light_ExponentialFade m_Light.m_ExpFade
+#define Light_AreaWidth m_Light.m_AreaWidth
+#define Light_AreaHeight m_Light.m_AreaHeight
+#define Light_CastShadow m_Light.m_CastShadow
+#define Light_ShadowBias m_Light.m_ShadowBias
+#define Light_ShadowFactor m_Light.m_ShadowFactor
+#define Light_ShadowMapRes m_Light.m_ShadowMapRes
+#define Light_ShadowMapFar m_Light.m_ShadowMapFar
+#define Light_ShadowMapFov m_Light.m_ShadowMapFov
+#define Light_ShadowFilter m_Light.m_ShadowFilter
+#define Model_MeshPath m_Asset.m_SourcePath
+#define Model_ShadowCaster m_Model.m_ShadowCaster
+#define Model_TessellationMode m_Model.m_Tessellation
+#define Model_EdgeTess m_Model.m_EdgeTess
+#define Model_InnerTess m_Model.m_InnerTess
+#define Lightmaps_LightmapIndirect m_Lightmaps.m_LightmapIndirect
+#define Lightmaps_LightmapRadiosity m_Lightmaps.m_LightmapRadiosity
+#define Lightmaps_LightmapShadow m_Lightmaps.m_LightmapShadow
+#define MaterialBase_IblProbe m_MaterialBase.m_IblProbe
+#define Material_Lighting m_Material.m_ShaderLighting
+#define Material_BlendMode m_Material.m_BlendMode
+#define Material_DiffuseColor m_Material.m_DiffuseColor
+#define Material_DiffuseMaps_0 m_Material.m_DiffuseMap1
+#define Material_DiffuseMaps_1 m_Material.m_DiffuseMap2
+#define Material_DiffuseMaps_2 m_Material.m_DiffuseMap3
+#define Material_EmissivePower m_Material.m_EmissivePower
+#define Material_EmissiveColor m_Material.m_EmissiveColor
+#define Material_EmissiveMap m_Material.m_EmissiveMap
+#define Material_EmissiveMap2 m_Material.m_EmissiveMap2
+#define Material_SpecularReflection m_Material.m_SpecularReflection
+#define Material_SpecularMap m_Material.m_SpecularMap
+#define Material_SpecularModel m_Material.m_SpecularModel
+#define Material_SpecularTint m_Material.m_SpecularTint
+#define Material_IOR m_Material.m_IOR
+#define Material_FresnelPower m_Material.m_FresnelPower
+#define Material_SpecularAmount m_Material.m_SpecularAmount
+#define Material_SpecularRoughness m_Material.m_SpecularRoughness
+#define Material_RoughnessMap m_Material.m_RoughnessMap
+#define Material_Opacity m_Material.m_Opacity
+#define Material_OpacityMap m_Material.m_OpacityMap
+#define Material_BumpMap m_Material.m_BumpMap
+#define Material_BumpAmount m_Material.m_BumpAmount
+#define Material_NormalMap m_Material.m_NormalMap
+#define Material_DisplacementMap m_Material.m_DisplacementMap
+#define Material_DisplaceAmount m_Material.m_DisplaceAmount
+#define Material_TranslucencyMap m_Material.m_TranslucencyMap
+#define Material_TranslucentFalloff m_Material.m_TranslucentFalloff
+#define Material_DiffuseLightWrap m_Material.m_DiffuseLightWrap
+#define Material_ReferencedMaterial m_ReferencedMaterial.m_ReferencedMaterial
+#define Material_VertexColors m_Material.m_VertexColors
+#define Image_ImagePath m_Asset.m_SourcePath
+#define Image_OffscreenRendererId m_Image.m_SubPresentation
+#define Image_Scale_X m_Image.m_RepeatU
+#define Image_Scale_Y m_Image.m_RepeatV
+#define Image_Pivot_X m_Image.m_PivotU
+#define Image_Pivot_Y m_Image.m_PivotV
+#define Image_Rotation m_Image.m_RotationUV
+#define Image_Position_X m_Image.m_PositionU
+#define Image_Position_Y m_Image.m_PositionV
+#define Image_MappingMode m_Image.m_TextureMapping
+#define Image_HorizontalTilingMode m_Image.m_TilingU
+#define Image_VerticalTilingMode m_Image.m_TilingV
+#define Text_Text m_Text.m_TextString
+#define Text_Font m_Text.m_Font
+#define Text_FontSize m_Text.m_Size
+#define Text_HorizontalAlignment m_Text.m_HorzAlign
+#define Text_VerticalAlignment m_Text.m_VertAlign
+#define Text_Leading m_Text.m_Leading
+#define Text_Tracking m_Text.m_Tracking
+#define Text_DropShadow m_Text.m_DropShadow
+#define Text_DropShadowStrength m_Text.m_DropShadowStrength
+#define Text_DropShadowOffsetX m_Text.m_DropShadowOffsetX
+#define Text_DropShadowOffsetY m_Text.m_DropShadowOffsetY
+#define Text_WordWrap m_Text.m_WordWrap
+#define Text_BoundingBox m_Text.m_BoundingBox
+#define Text_Elide m_Text.m_Elide
+#define Text_TextColor m_Text.m_TextColor
+#define Text_EnableAcceleratedFont m_Text.m_EnableAcceleratedFont
+#define Path_Width m_Path.m_Width
+#define Path_LinearError m_Path.m_LinearError
+#define Path_InnerTessAmount m_Path.m_InnerTessAmount
+#define Path_EdgeTessAmount m_Path.m_EdgeTessAmount
+#define Path_Opacity m_Path.m_Opacity
+#define Path_BeginCapping m_Path.m_BeginCap
+#define Path_BeginCapOffset m_Path.m_BeginCapOffset
+#define Path_BeginCapOpacity m_Path.m_BeginCapOpacity
+#define Path_BeginCapWidth m_Path.m_BeginCapWidth
+#define Path_EndCapping m_Path.m_EndCap
+#define Path_EndCapOffset m_Path.m_EndCapOffset
+#define Path_EndCapOpacity m_Path.m_EndCapOpacity
+#define Path_EndCapWidth m_Path.m_EndCapWidth
+#define Path_PathType m_Path.m_PathType
+#define Path_PaintStyle m_Path.m_PaintStyle
+#define Path_PathBuffer m_Asset.m_SourcePath
+#define SubPath_Closed m_SubPath.m_Closed
+
+// Fill in implementations for the actual parse tables.
+#define HANDLE_QT3DS_RENDER_PROPERTY(type, name, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_VEC3_PROPERTY(type, name, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_REAL_VEC2_PROPERTY(type, name, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_COLOR_PROPERTY(type, name, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_RADIAN_PROPERTY(type, name, dirty) \
+ theParser.ParseRadianProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_VEC3_RADIAN_PROPERTY(type, name, dirty) \
+ theParser.ParseRadianProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_OPACITY_PROPERTY(type, name, dirty) \
+ theParser.ParseOpacityProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_ROTATION_ORDER_PROPERTY(type, name, dirty) \
+ theParser.ParseRotationOrder(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_NODE_ORIENTATION_PROPERTY(type, name, dirty) \
+ theParser.ParseOrientation(inContext.m_ObjectDefinitions.type##_##name, theItem.m_Flags);
+#define HANDLE_QT3DS_RENDER_DEPTH_TEST_PROPERTY(type, name, dirty) \
+ if (theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name)) \
+ theItem.m_##name = !theItem.m_##name;
+#define HANDLE_QT3DS_NODE_FLAGS_PROPERTY(type, name, dirty) \
+ theParser.ParseNodeFlagsProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_Flags, \
+ qt3ds::render::NodeFlagValues::name);
+#define HANDLE_QT3DS_NODE_FLAGS_INVERSE_PROPERTY(type, name, dirty) \
+ theParser.ParseNodeFlagsInverseProperty(inContext.m_ObjectDefinitions.type##_##name, \
+ theItem.m_Flags, qt3ds::render::NodeFlagValues::name);
+#define HANDLE_QT3DS_RENDER_ENUM_PROPERTY(type, name, dirty) \
+ theParser.ParseEnumProperty(inContext.m_ObjectDefinitions.type##_##name, theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_SOURCEPATH_PROPERTY(type, name, dirty) \
+ theParser.ParseAndResolveSourcePath(inContext.m_ObjectDefinitions.type##_##name, \
+ theItem.m_##name);
+#define HANDLE_QT3DS_RENDER_ARRAY_PROPERTY(type, name, index, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name##_##index, \
+ theItem.m_##name[index]);
+#define HANDLE_QT3DS_RENDER_VEC2_PROPERTY(type, name, dirty) \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name##_##X, \
+ theItem.m_##name.x); \
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.type##_##name##_##Y, theItem.m_##name.y);
+#define HANDLE_QT3DS_RENDER_COLOR_VEC3_PROPERTY( \
+ type, name, dirty) // noop by intention already handled by HANDLE_QT3DS_RENDER_COLOR_PROPERTY
+#define HANDLE_QT3DS_RENDER_TRANSFORM_VEC3_PROPERTY( \
+ type, name, dirty) // noop by intention already handled by HANDLE_QT3DS_RENDER_VEC3_PROPERTY
+
+struct SSceneTranslator : public SGraphObjectTranslator
+{
+ SSceneTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SScene)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SScene &theItem = static_cast<SScene &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_SCENE_PROPERTIES
+ SLayer *theCurrentLayer = nullptr;
+ theItem.m_FirstChild = nullptr;
+ for (long idx = 0, end = inContext.m_AssetGraph.GetChildCount(GetInstanceHandle());
+ idx < end; ++idx) {
+ SGraphObjectTranslator::PushTranslation(inContext);
+ qt3dsdm::Qt3DSDMInstanceHandle theLayer =
+ inContext.m_AssetGraph.GetChild(GetInstanceHandle(), idx);
+ SGraphObjectTranslator *theTranslator = inContext.GetOrCreateTranslator(theLayer);
+ if (theTranslator && theTranslator->GetGraphObject().m_Type ==
+ qt3ds::render::GraphObjectTypes::Layer) {
+ SLayer *theLayerObj = static_cast<SLayer *>(&theTranslator->GetGraphObject());
+ theLayerObj->m_NextSibling = nullptr;
+ if (theItem.m_FirstChild == nullptr)
+ theItem.m_FirstChild = theLayerObj;
+ else
+ theCurrentLayer->m_NextSibling = theLayerObj;
+ theCurrentLayer = theLayerObj;
+ }
+ }
+ }
+ void ClearChildren() override
+ {
+ SScene &theItem = static_cast<SScene &>(GetGraphObject());
+ SLayer *theLastChild = nullptr;
+
+ for (SLayer *theChild = theItem.m_FirstChild; theChild;
+ theChild = static_cast<SLayer *>(theChild->m_NextSibling)) {
+ if (theLastChild)
+ theLastChild->m_NextSibling = nullptr;
+ theChild->m_Parent = nullptr;
+ theLastChild = theChild;
+ }
+ theItem.m_FirstChild = nullptr;
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (inChild.m_Type != GraphObjectTypes::Layer)
+ return;
+ SScene &theItem = static_cast<SScene &>(GetGraphObject());
+ SLayer &theLayer = static_cast<SLayer &>(inChild);
+ theItem.AddChild(theLayer);
+ }
+ void SetActive(bool /*inActive*/) override
+ {
+ // How could we not be active?
+ }
+};
+
+struct SNodeTranslator : public SGraphObjectTranslator
+{
+ SNodeTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SNode)())
+ {
+ Initialize();
+ }
+ SNodeTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, SNode &inNode)
+ : SGraphObjectTranslator(inInstance, inNode)
+ {
+ Initialize();
+ }
+ void Initialize()
+ {
+ SNode &theNode = static_cast<SNode &>(GetGraphObject());
+ // Ensure the global transform is valid because we use this before we render sometimes.
+ theNode.m_GlobalTransform = QT3DSMat44::createIdentity();
+ }
+ static inline bool IsNodeType(qt3ds::render::GraphObjectTypes::Enum inType)
+ {
+ return qt3ds::render::GraphObjectTypes::IsNodeType(inType);
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SGraphObjectTranslator::PushTranslation(inContext);
+ SNode &theItem = static_cast<SNode &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_NODE_PROPERTIES
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Node.m_BoneId,
+ theItem.m_SkeletonId);
+ bool ignoresParent = false;
+ if (theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Node.m_IgnoresParent,
+ ignoresParent))
+ theItem.m_Flags.SetIgnoreParentTransform(ignoresParent);
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (GraphObjectTypes::IsNodeType(inChild.m_Type) == false) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+
+ SNode &theItem = static_cast<SNode &>(GetGraphObject());
+ SNode &theChild = static_cast<SNode &>(inChild);
+ theItem.AddChild(theChild);
+ theItem.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty);
+ theChild.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty);
+ }
+ void ClearChildren() override
+ {
+ SNode &theItem = static_cast<SNode &>(GetGraphObject());
+ SNode *theLastChild = nullptr;
+ for (SNode *theChild = theItem.m_FirstChild; theChild; theChild = theChild->m_NextSibling) {
+ if (theLastChild)
+ theLastChild->m_NextSibling = nullptr;
+ theLastChild = theChild;
+ theChild->m_Parent = nullptr;
+ }
+ theItem.m_FirstChild = nullptr;
+ }
+
+ void SetActive(bool inActive) override
+ {
+ SNode &theNode = static_cast<SNode &>(GetGraphObject());
+ if (inActive != theNode.m_Flags.IsActive()) {
+ theNode.m_Flags.SetActive(inActive);
+ theNode.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty);
+ }
+ }
+};
+
+struct SLayerTranslator : public SNodeTranslator
+{
+ SLayerTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SLayer)())
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ SLayer &theItem = static_cast<SLayer &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_LAYER_PROPERTIES
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Layer.m_AoSamplerate,
+ theItem.m_AoSamplerate);
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (GraphObjectTypes::IsNodeType(inChild.m_Type)) {
+ SNodeTranslator::AppendChild(inChild);
+ } else if (inChild.m_Type == GraphObjectTypes::Effect) {
+ SLayer &theItem = static_cast<SLayer &>(GetGraphObject());
+ theItem.AddEffect(static_cast<SEffect &>(inChild));
+ } else if (inChild.m_Type == GraphObjectTypes::RenderPlugin) {
+ SLayer &theItem = static_cast<SLayer &>(GetGraphObject());
+ theItem.m_RenderPlugin = &static_cast<qt3ds::render::SRenderPlugin &>(inChild);
+ }
+ }
+ void ClearChildren() override
+ {
+ SNodeTranslator::ClearChildren();
+ SLayer &theItem = static_cast<SLayer &>(GetGraphObject());
+ SEffect *theLastChild = nullptr;
+ for (SEffect *theChild = theItem.m_FirstEffect; theChild;
+ theChild = theChild->m_NextEffect) {
+ if (theLastChild)
+ theLastChild->m_NextEffect = nullptr;
+ theLastChild = theChild;
+ theChild->m_Layer = nullptr;
+ }
+ theItem.m_FirstEffect = nullptr;
+ theItem.m_RenderPlugin = nullptr;
+ // Don't clear the light probe properties because those are added/removed as part of the
+ // normal
+ // property scan, they aren't added as generic children.
+ }
+};
+struct SLightTranslator : public SNodeTranslator
+{
+ SLightTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SLight)())
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ SLight &theItem = static_cast<SLight &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_LIGHT_PROPERTIES
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Light.m_ShadowMapRes,
+ theItem.m_ShadowMapRes);
+ }
+ void AppendChild(SGraphObject &inChild) override { SNodeTranslator::AppendChild(inChild); }
+ void ClearChildren() override { SNodeTranslator::ClearChildren(); }
+};
+struct SCameraTranslator : public SNodeTranslator
+{
+ SCameraTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SCamera)())
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ SCamera &theItem = static_cast<SCamera &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_CAMERA_PROPERTIES
+ }
+};
+struct SModelTranslator : public SNodeTranslator
+{
+ SModelTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SModel)())
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ SModel &theItem = static_cast<SModel &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_MODEL_PROPERTIES
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Model.m_PoseRoot,
+ theItem.m_SkeletonRoot);
+
+ theItem.m_FirstMaterial = nullptr;
+ for (long idx = 0, end = inContext.m_AssetGraph.GetChildCount(GetInstanceHandle());
+ idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theItemHandle =
+ inContext.m_AssetGraph.GetChild(GetInstanceHandle(), idx);
+ SGraphObjectTranslator *theTranslator = inContext.GetOrCreateTranslator(theItemHandle);
+ if (theTranslator && IsMaterial(theTranslator->GetGraphObject())) {
+ SGraphObject *theMaterial = &theTranslator->GetGraphObject();
+ SetNextMaterialSibling(*theMaterial, nullptr);
+ theItem.AddMaterial(*theMaterial);
+ }
+ }
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (GraphObjectTypes::IsNodeType(inChild.m_Type)) {
+ SNodeTranslator::AppendChild(inChild);
+ } else if (IsMaterial(inChild)) {
+ SModel &theItem = static_cast<SModel &>(GetGraphObject());
+ theItem.AddMaterial(inChild);
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ }
+ void ClearChildren() override
+ {
+ SModel &theItem = static_cast<SModel &>(GetGraphObject());
+ SNodeTranslator::ClearChildren();
+
+ SGraphObject *theLastMaterial = nullptr;
+ for (SGraphObject *theMaterial = theItem.m_FirstMaterial; theMaterial;
+ theMaterial = qt3ds::render::GetNextMaterialSibling(theMaterial)) {
+ if (theLastMaterial)
+ qt3ds::render::SetNextMaterialSibling(*theLastMaterial, nullptr);
+ theLastMaterial = theMaterial;
+ }
+ theItem.m_FirstMaterial = nullptr;
+ }
+};
+
+static SFloat2 ToFloat2(const Option<SValue> &inValue)
+{
+ if (inValue.hasValue())
+ return inValue->getData<SFloat2>();
+ return SFloat2();
+}
+
+static float ToFloat(const Option<SValue> &inValue)
+{
+ if (inValue.hasValue())
+ return inValue->getData<QT3DSF32>();
+ return 0.0f;
+}
+
+struct SPathSubPathTranslator : public SGraphObjectTranslator
+{
+ eastl::vector<qt3ds::render::SPathAnchorPoint> m_PathBuffer;
+ SPathSubPathTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SPathSubPath)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SPathSubPath &theItem = static_cast<SPathSubPath &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_PATH_SUBPATH_PROPERTIES
+ m_PathBuffer.clear();
+ Q3DStudio::IDocumentReader &theReader(inContext.m_Doc.GetDocumentReader());
+ QT3DSU32 anchorCount = 0;
+ for (QT3DSI32 idx = 0, end = inContext.m_AssetGraph.GetChildCount(GetInstanceHandle());
+ idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theAnchor =
+ inContext.m_AssetGraph.GetChild(GetInstanceHandle(), idx);
+ if (theReader.GetObjectTypeName(theAnchor) == L"PathAnchorPoint")
+ ++anchorCount;
+ }
+ QT3DSU32 anchorIdx = 0;
+ for (QT3DSI32 idx = 0, end = inContext.m_AssetGraph.GetChildCount(GetInstanceHandle());
+ idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theAnchor =
+ inContext.m_AssetGraph.GetChild(GetInstanceHandle(), idx);
+ if (theReader.GetObjectTypeName(theAnchor) == L"PathAnchorPoint") {
+ SFloat2 theAnchorPos = ToFloat2(theReader.GetInstancePropertyValue(
+ theAnchor,
+ inContext.m_ObjectDefinitions.m_PathAnchorPoint.m_Position.m_Property));
+ float theIncomingAngle = ToFloat(theReader.GetInstancePropertyValue(
+ theAnchor,
+ inContext.m_ObjectDefinitions.m_PathAnchorPoint.m_IncomingAngle.m_Property));
+ float theIncomingDistance = ToFloat(theReader.GetInstancePropertyValue(
+ theAnchor,
+ inContext.m_ObjectDefinitions.m_PathAnchorPoint.m_IncomingDistance.m_Property));
+ float theOutgoingDistance = ToFloat(theReader.GetInstancePropertyValue(
+ theAnchor,
+ inContext.m_ObjectDefinitions.m_PathAnchorPoint.m_OutgoingDistance.m_Property));
+ qt3ds::render::SPathAnchorPoint thePoint(QT3DSVec2(theAnchorPos[0], theAnchorPos[1]),
+ theIncomingAngle, theIncomingAngle + 180.0f,
+ theIncomingDistance, theOutgoingDistance);
+ m_PathBuffer.push_back(thePoint);
+ ++anchorIdx;
+ }
+ }
+ inContext.m_Context.GetPathManager().SetPathSubPathData(
+ theItem,
+ qt3ds::foundation::toConstDataRef(m_PathBuffer.begin(), (QT3DSU32)m_PathBuffer.size()));
+ }
+
+ void AppendChild(SGraphObject &) override {}
+
+ void ClearChildren() override {}
+
+ void SetActive(bool /*inActive*/) override {}
+};
+
+struct SPathTranslator : public SNodeTranslator
+{
+ SPathTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SPath)())
+ {
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (GraphObjectTypes::IsMaterialType(inChild.m_Type)) {
+ SPath &theItem = static_cast<SPath &>(GetGraphObject());
+ theItem.AddMaterial(&inChild);
+ theItem.m_Flags.SetDirty(true);
+ } else if (inChild.m_Type == GraphObjectTypes::PathSubPath) {
+ SPath &theItem = static_cast<SPath &>(GetGraphObject());
+ theItem.AddSubPath(static_cast<SPathSubPath &>(inChild));
+ theItem.m_Flags.SetDirty(true);
+ } else {
+ SNodeTranslator::AppendChild(inChild);
+ }
+ }
+
+ void ClearChildren() override
+ {
+ SNodeTranslator::ClearChildren();
+ SPath &theItem = static_cast<SPath &>(GetGraphObject());
+ theItem.ClearMaterials();
+ theItem.ClearSubPaths();
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ SPath &theItem = static_cast<SPath &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_PATH_PROPERTIES
+ }
+};
+
+struct SDefaultMaterialTranslator : public SGraphObjectTranslator
+{
+ SDefaultMaterialTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SDefaultMaterial)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SGraphObjectTranslator::PushTranslation(inContext);
+ SDefaultMaterial &theItem = static_cast<SDefaultMaterial &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_MATERIAL_PROPERTIES
+
+ // qt3dsdm::Qt3DSDMInstanceHandle parent = inContext.m_AssetGraph.GetParent(
+ // GetInstanceHandle() );
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapIndirect,
+ theItem.m_Lightmaps.m_LightmapIndirect);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapRadiosity,
+ theItem.m_Lightmaps.m_LightmapRadiosity);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapShadow,
+ theItem.m_Lightmaps.m_LightmapShadow);
+ }
+
+ void AppendChild(SGraphObject &) override {}
+ void ClearChildren() override {}
+
+ void SetActive(bool /*inActive*/) override {}
+};
+
+struct SImageTranslator : public SGraphObjectTranslator
+{
+ SImageTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SImage)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SGraphObjectTranslator::PushTranslation(inContext);
+ SImage &theItem = static_cast<SImage &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_IMAGE_PROPERTIES
+
+ theItem.m_Flags.SetDirty(true);
+ theItem.m_Flags.SetTransformDirty(true);
+ }
+ void AppendChild(SGraphObject &child) override
+ {
+ SImage &theItem = static_cast<SImage &>(GetGraphObject());
+ if (child.m_Type == GraphObjectTypes::RenderPlugin)
+ theItem.m_RenderPlugin = &static_cast<qt3ds::render::SRenderPlugin &>(child);
+ }
+ void ClearChildren() override
+ {
+ SImage &theItem = static_cast<SImage &>(GetGraphObject());
+ theItem.m_RenderPlugin = nullptr;
+ }
+
+ void SetActive(bool /*inActive*/) override {}
+};
+
+struct STextTranslator : public SNodeTranslator
+{
+ STextTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SNodeTranslator(inInstance, *QT3DS_NEW(inAlloc, SText)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SNodeTranslator::PushTranslation(inContext);
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ SText &theItem = static_cast<SText &>(GetGraphObject());
+ ITERATE_QT3DS_RENDER_TEXT_PROPERTIES
+ theItem.m_Flags.SetTextDirty(true);
+ }
+};
+
+inline qt3ds::QT3DSVec2 ToRenderType(const qt3dsdm::SFloat2 &inType)
+{
+ return qt3ds::QT3DSVec2(inType.m_Floats[0], inType.m_Floats[1]);
+}
+inline qt3ds::QT3DSVec3 ToRenderType(const qt3dsdm::SFloat3 &inType)
+{
+ return qt3ds::QT3DSVec3(inType.m_Floats[0], inType.m_Floats[1], inType.m_Floats[2]);
+}
+inline qt3ds::QT3DSVec4 ToRenderType(const qt3dsdm::SFloat4 &inType)
+{
+ return qt3ds::QT3DSVec4(inType.m_Floats[0], inType.m_Floats[1], inType.m_Floats[2],
+ inType.m_Floats[3]);
+}
+
+struct SDynamicObjectTranslator : public SGraphObjectTranslator
+{
+ typedef eastl::vector<eastl::pair<QT3DSU32, int>> TIdxToPropertyMap;
+ eastl::basic_string<qt3ds::foundation::TWCharEASTLConverter::TCharType> m_ConvertStr;
+ TIdxToPropertyMap m_PropertyMap;
+
+ SDynamicObjectTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &,
+ SDynamicObject &inObject)
+ : SGraphObjectTranslator(inInstance, inObject)
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SDynamicObject &theItem = static_cast<SDynamicObject &>(GetGraphObject());
+ IDynamicObjectSystem &theSystem = inContext.m_Context.GetDynamicObjectSystem();
+ using namespace qt3ds::render::dynamic;
+ using qt3ds::foundation::NVConstDataRef;
+ NVConstDataRef<SPropertyDefinition> theProperties =
+ theSystem.GetProperties(theItem.m_ClassName);
+ if (m_PropertyMap.size() == 0) {
+ for (QT3DSU32 idx = 0, end = theProperties.size(); idx < end; ++idx) {
+ const SPropertyDefinition &theDefinition(theProperties[idx]);
+ qt3ds::foundation::ConvertUTF(theDefinition.m_Name.c_str(), 0, m_ConvertStr);
+ const wchar_t *thePropName =
+ reinterpret_cast<const wchar_t *>(m_ConvertStr.c_str());
+ qt3dsdm::Qt3DSDMPropertyHandle theProperty =
+ inContext.m_Reader.FindProperty(GetInstanceHandle(), thePropName);
+ if (theProperty.Valid())
+ m_PropertyMap.push_back(eastl::make_pair(idx, theProperty.GetHandleValue()));
+ }
+ }
+ std::shared_ptr<IDataCore> theDataCore =
+ inContext.m_StudioSystem.GetFullSystem()->GetCoreSystem()->GetDataCore();
+ for (TIdxToPropertyMap::iterator theIter = m_PropertyMap.begin(), end = m_PropertyMap.end();
+ theIter != end; ++theIter) {
+ const SPropertyDefinition &theDefinition(theProperties[theIter->first]);
+ qt3dsdm::Qt3DSDMPropertyHandle theProperty = theIter->second;
+ // Sometimes it is possible to have dirty properties that no longer exist, e.g.
+ // when undoing standard material -> custom material change. We just ignore changes
+ // to such properties.
+ if (theDataCore->IsProperty(theProperty)) {
+ Option<qt3dsdm::SValue> theValueOpt =
+ inContext.m_Reader.GetInstancePropertyValue(GetInstanceHandle(), theProperty);
+ if (theValueOpt.hasValue()) {
+ qt3dsdm::SValue &theValue(*theValueOpt);
+ switch (qt3dsdm::GetValueType(theValue)) {
+ case qt3dsdm::DataModelDataType::Long:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSI32) {
+ theItem.SetPropertyValue(theDefinition,
+ qt3dsdm::get<qt3ds::QT3DSI32>(theValue));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ case qt3dsdm::DataModelDataType::Bool:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSRenderBool) {
+ theItem.SetPropertyValue(theDefinition, qt3dsdm::get<bool>(theValue));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ case qt3dsdm::DataModelDataType::Float:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSF32) {
+ theItem.SetPropertyValue(theDefinition, qt3dsdm::get<float>(theValue));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ case qt3dsdm::DataModelDataType::Float2:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSVec2) {
+ theItem.SetPropertyValue(
+ theDefinition, ToRenderType(qt3dsdm::get<qt3dsdm::SFloat2>(theValue)));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ case qt3dsdm::DataModelDataType::Float3:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSVec3) {
+ theItem.SetPropertyValue(
+ theDefinition, ToRenderType(qt3dsdm::get<qt3dsdm::SFloat3>(theValue)));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ case qt3dsdm::DataModelDataType::Float4:
+ if (theDefinition.m_DataType
+ == qt3ds::render::NVRenderShaderDataTypes::QT3DSVec4) {
+ theItem.SetPropertyValue(
+ theDefinition, ToRenderType(qt3dsdm::get<qt3dsdm::SFloat4>(theValue)));
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ break;
+ // Could be either an enum or a texture.
+ case qt3dsdm::DataModelDataType::String: {
+ qt3dsdm::TDataStrPtr theData = qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue);
+ if (theData) {
+ eastl::string theStr;
+ qt3ds::render::ConvertWideUTF(theData->GetData(), 0, theStr);
+ eastl::string theWorkspace;
+ theItem.SetPropertyValue(
+ theDefinition, theStr.c_str(),
+ inContext.m_Doc.GetCore()->getProjectFile()
+ .getProjectPath().toLocal8Bit().constData(),
+ theWorkspace, inContext.m_Context.GetStringTable());
+ }
+ } break;
+ default:
+ QT3DS_ASSERT(false);
+ }
+ }
+ }
+ }
+ }
+
+ void AppendChild(SGraphObject &) override {}
+ void ClearChildren() override {}
+};
+
+struct SEffectTranslator : public SDynamicObjectTranslator
+{
+ // TODO - move this map to inContext and have it looked up by name.
+ IEffectSystem *m_EffectSystem;
+
+ SEffectTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc,
+ SEffect &inEffect)
+ : SDynamicObjectTranslator(inInstance, inAlloc, inEffect)
+ , m_EffectSystem(nullptr)
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ m_EffectSystem = &inContext.m_Context.GetEffectSystem();
+ SDynamicObjectTranslator::PushTranslation(inContext);
+ }
+
+ void SetActive(bool inActive) override
+ {
+ SEffect &theItem = static_cast<SEffect &>(GetGraphObject());
+ if (m_EffectSystem)
+ theItem.SetActive(inActive, *m_EffectSystem);
+ else
+ theItem.m_Flags.SetActive(inActive);
+ }
+
+ void ResetEffect() override
+ {
+ SEffect &theItem = static_cast<SEffect &>(GetGraphObject());
+ if (m_EffectSystem)
+ theItem.Reset(*m_EffectSystem);
+ }
+};
+struct SCustomMaterialTranslator : public SDynamicObjectTranslator
+{
+ ICustomMaterialSystem *m_MaterialSystem;
+
+ SCustomMaterialTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3ds::NVAllocatorCallback &inAlloc, SCustomMaterial &inMaterial)
+ : SDynamicObjectTranslator(inInstance, inAlloc, inMaterial)
+ , m_MaterialSystem(nullptr)
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SDynamicObjectTranslator::PushTranslation(inContext);
+ SCustomMaterial &theItem = static_cast<SCustomMaterial &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_CUSTOM_MATERIAL_PROPERTIES
+
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapIndirect,
+ theItem.m_Lightmaps.m_LightmapIndirect);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapRadiosity,
+ theItem.m_Lightmaps.m_LightmapRadiosity);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapShadow,
+ theItem.m_Lightmaps.m_LightmapShadow);
+ }
+
+ void SetActive(bool inActive) override
+ {
+ if (m_MaterialSystem) {
+ SCustomMaterial &theItem = static_cast<SCustomMaterial &>(GetGraphObject());
+ if (inActive != theItem.m_Flags.IsActive()) {
+ theItem.m_Flags.SetActive(inActive);
+ m_MaterialSystem->OnMaterialActivationChange(theItem, inActive);
+ }
+ }
+ }
+};
+struct SReferencedMaterialTranslator : public SGraphObjectTranslator
+{
+ SReferencedMaterialTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SReferencedMaterial)())
+ {
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ SGraphObjectTranslator::PushTranslation(inContext);
+ SReferencedMaterial &theItem = static_cast<SReferencedMaterial &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ ITERATE_QT3DS_RENDER_REFERENCED_MATERIAL_PROPERTIES
+
+ theItem.m_Dirty.SetDirty();
+ if (theItem.m_ReferencedMaterial == &theItem) {
+ qCCritical(qt3ds::INVALID_OPERATION,
+ "Referenced material is referencing itself.");
+ } else if (theItem.m_ReferencedMaterial
+ && theItem.m_ReferencedMaterial->m_Type == GraphObjectTypes::DefaultMaterial) {
+ SDefaultMaterial *theDefaultItem =
+ static_cast<SDefaultMaterial *>(theItem.m_ReferencedMaterial);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapIndirect,
+ theDefaultItem->m_Lightmaps.m_LightmapIndirect);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapRadiosity,
+ theDefaultItem->m_Lightmaps.m_LightmapRadiosity);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapShadow,
+ theDefaultItem->m_Lightmaps.m_LightmapShadow);
+ } else if (theItem.m_ReferencedMaterial
+ && theItem.m_ReferencedMaterial->m_Type == GraphObjectTypes::CustomMaterial) {
+ SCustomMaterial *theDefaultItem =
+ static_cast<SCustomMaterial *>(theItem.m_ReferencedMaterial);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapIndirect,
+ theDefaultItem->m_Lightmaps.m_LightmapIndirect);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapRadiosity,
+ theDefaultItem->m_Lightmaps.m_LightmapRadiosity);
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Lightmaps.m_LightmapShadow,
+ theDefaultItem->m_Lightmaps.m_LightmapShadow);
+ }
+ }
+
+ void AppendChild(SGraphObject &) override {}
+
+ void ClearChildren() override {}
+
+ void SetActive(bool /*inActive*/) override {}
+};
+using qt3ds::render::SRenderPlugin;
+using qt3ds::render::SRenderPropertyValueUpdate;
+using qt3ds::render::IRenderPluginClass;
+using qt3ds::render::SRenderPluginPropertyDeclaration;
+struct SRenderPluginPropertyUpdateFactory
+{
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates, float value,
+ const SRenderPluginPropertyDeclaration &theDec, IRenderPluginClass &)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(theDec.m_Name, value));
+ }
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates, qt3ds::QT3DSI32 value,
+ const SRenderPluginPropertyDeclaration &theDec, IRenderPluginClass &)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(theDec.m_Name, value));
+ }
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates, bool value,
+ const SRenderPluginPropertyDeclaration &theDec, IRenderPluginClass &)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(theDec.m_Name, value));
+ }
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates, qt3dsdm::TDataStrPtr value,
+ const SRenderPluginPropertyDeclaration &theDec, IRenderPluginClass &,
+ qt3ds::foundation::IStringTable &strTable)
+ {
+ if (value) {
+ ioUpdates.push_back(
+ SRenderPropertyValueUpdate(theDec.m_Name, strTable.RegisterStr(value->GetData())));
+ }
+ }
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates, qt3dsdm::SStringRef value,
+ const SRenderPluginPropertyDeclaration &theDec, IRenderPluginClass &,
+ qt3ds::foundation::IStringTable &strTable)
+ {
+ ioUpdates.push_back(
+ SRenderPropertyValueUpdate(theDec.m_Name, strTable.RegisterStr(value.m_Id)));
+ }
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates,
+ const qt3dsdm::SFloat2 &value, const SRenderPluginPropertyDeclaration &theDec,
+ IRenderPluginClass &inClass)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset).first, value.m_Floats[0]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 1).first, value.m_Floats[1]));
+ }
+
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates,
+ const qt3dsdm::SFloat3 &value, const SRenderPluginPropertyDeclaration &theDec,
+ IRenderPluginClass &inClass)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset).first, value.m_Floats[0]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 1).first, value.m_Floats[1]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 2).first, value.m_Floats[2]));
+ }
+
+ static void Add(eastl::vector<SRenderPropertyValueUpdate> &ioUpdates,
+ const qt3dsdm::SFloat4 &value, const SRenderPluginPropertyDeclaration &theDec,
+ IRenderPluginClass &inClass)
+ {
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset).first, value.m_Floats[0]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 1).first, value.m_Floats[1]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 2).first, value.m_Floats[2]));
+ ioUpdates.push_back(SRenderPropertyValueUpdate(
+ inClass.GetPropertyValueInfo(theDec.m_StartOffset + 3).first, value.m_Floats[3]));
+ }
+};
+struct SRenderPluginTranslator : public SGraphObjectTranslator
+{
+ eastl::vector<SRenderPropertyValueUpdate> m_PropertyUpdates;
+
+ SRenderPluginTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SRenderPlugin)())
+ {
+ }
+
+ void PushTranslation(STranslation &inContext) override
+ {
+ SRenderPlugin &theItem = static_cast<SRenderPlugin &>(GetGraphObject());
+ // First, get the instance via resolving the source path.
+ Qt3DSDMPropertyHandle sourcepath =
+ inContext.m_Reader.FindProperty(GetInstanceHandle(), L"sourcepath");
+ Option<SValue> theSourcePath =
+ inContext.m_Reader.GetInstancePropertyValue(GetInstanceHandle(), sourcepath);
+ qt3dsdm::TDataStrPtr theData = theSourcePath->getData<qt3dsdm::TDataStrPtr>();
+ if (!theData)
+ return;
+
+ Q3DStudio::CFilePath theFullPath = inContext.m_Doc.GetResolvedPathToDoc(theData->GetData());
+ qt3ds::foundation::IStringTable &theStrTable = inContext.m_Context.GetStringTable();
+ theItem.m_PluginPath = theStrTable.RegisterStr(theFullPath.toCString());
+ qt3ds::render::IRenderPluginInstance *theInstance =
+ inContext.m_Context.GetRenderPluginManager().GetOrCreateRenderPluginInstance(
+ theItem.m_PluginPath, &theItem);
+
+ // Couldn't load the instance, so we can't render the instance.
+ if (theInstance == nullptr)
+ return;
+ // Grab the instance's parent and get the properties that are specific to just that
+ // instance.
+ TInstanceHandleList derivationParents;
+ std::shared_ptr<IDataCore> theDataCore =
+ inContext.m_StudioSystem.GetFullSystem()->GetCoreSystem()->GetDataCore();
+ theDataCore->GetInstanceParents(GetInstanceHandle(), derivationParents);
+ if (derivationParents.size() == 0)
+ return;
+ TPropertyHandleList theSpecificProperties;
+ theDataCore->GetInstanceProperties(derivationParents[0], theSpecificProperties);
+ eastl::string propStem;
+ eastl::string propname;
+ m_PropertyUpdates.clear();
+ qt3ds::render::IRenderPluginClass &theClass = theInstance->GetPluginClass();
+ using qt3ds::render::SRenderPluginPropertyDeclaration;
+ if (theClass.GetRegisteredProperties().size() == 0) {
+ for (size_t idx = 0, end = theSpecificProperties.size(); idx < end; ++idx) {
+ Qt3DSDMPropertyDefinition theProperty =
+ theDataCore->GetProperty(theSpecificProperties[idx]);
+ qt3dsdm::AdditionalMetaDataType::Value theMetaType =
+ inContext.m_StudioSystem.GetActionMetaData()->GetAdditionalMetaDataType(
+ GetInstanceHandle(), theSpecificProperties[idx]);
+ CRegisteredString thePropName(theStrTable.RegisterStr(theProperty.m_Name.c_str()));
+ switch (theProperty.m_Type) {
+ case DataModelDataType::Float:
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Float));
+ break;
+ case DataModelDataType::Float2:
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Vector2));
+ break;
+ case DataModelDataType::Float3:
+ if (theMetaType != AdditionalMetaDataType::Color) {
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Vector3));
+ } else {
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Color));
+ }
+ break;
+ case DataModelDataType::Float4:
+ if (theMetaType == AdditionalMetaDataType::Color) {
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Color));
+ }
+ break;
+ case DataModelDataType::Long:
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Long));
+ break;
+ case DataModelDataType::String:
+ case DataModelDataType::StringRef:
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::String));
+ break;
+ case DataModelDataType::Bool:
+ theClass.RegisterProperty(SRenderPluginPropertyDeclaration(
+ thePropName, qt3ds::render::SRenderPluginPropertyTypes::Boolean));
+ break;
+ default:
+ // Unsupported plugin property.
+ QT3DS_ASSERT(false);
+ break;
+ }
+ }
+ }
+ for (size_t idx = 0, end = theSpecificProperties.size(); idx < end; ++idx) {
+ Option<SValue> thePropValueOpt = inContext.m_Reader.GetInstancePropertyValue(
+ GetInstanceHandle(), theSpecificProperties[idx]);
+ if (thePropValueOpt.hasValue()) {
+ SValue &thePropValue = thePropValueOpt.getValue();
+ Qt3DSDMPropertyDefinition theProperty =
+ theDataCore->GetProperty(theSpecificProperties[idx]);
+ SRenderPluginPropertyDeclaration theDeclaration(theClass.GetPropertyDeclaration(
+ theStrTable.RegisterStr(theProperty.m_Name.c_str())));
+
+ switch (thePropValue.getType()) {
+ case DataModelDataType::None:
+ QT3DS_ASSERT(false);
+ break;
+ case DataModelDataType::Float:
+ SRenderPluginPropertyUpdateFactory::Add(
+ m_PropertyUpdates, thePropValue.getData<float>(), theDeclaration, theClass);
+ break;
+ case DataModelDataType::Float2:
+ SRenderPluginPropertyUpdateFactory::Add(m_PropertyUpdates,
+ thePropValue.getData<SFloat2>(),
+ theDeclaration, theClass);
+ break;
+ case DataModelDataType::Float3:
+ SRenderPluginPropertyUpdateFactory::Add(m_PropertyUpdates,
+ thePropValue.getData<SFloat3>(),
+ theDeclaration, theClass);
+ break;
+ case DataModelDataType::Float4:
+ SRenderPluginPropertyUpdateFactory::Add(m_PropertyUpdates,
+ thePropValue.getData<SFloat4>(),
+ theDeclaration, theClass);
+ break;
+ case DataModelDataType::Long:
+ SRenderPluginPropertyUpdateFactory::Add(
+ m_PropertyUpdates, thePropValue.getData<qt3ds::QT3DSI32>(), theDeclaration, theClass);
+ break;
+ case DataModelDataType::String:
+ SRenderPluginPropertyUpdateFactory::Add(m_PropertyUpdates,
+ thePropValue.getData<TDataStrPtr>(),
+ theDeclaration, theClass, theStrTable);
+ break;
+ case DataModelDataType::Bool:
+ SRenderPluginPropertyUpdateFactory::Add(
+ m_PropertyUpdates, thePropValue.getData<bool>(), theDeclaration, theClass);
+ break;
+ case DataModelDataType::StringRef:
+ SRenderPluginPropertyUpdateFactory::Add(m_PropertyUpdates,
+ thePropValue.getData<SStringRef>(),
+ theDeclaration, theClass, theStrTable);
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ }
+ }
+ theInstance->Update(NVConstDataRef<SRenderPropertyValueUpdate>(m_PropertyUpdates.data(),
+ m_PropertyUpdates.size()));
+ }
+ void AppendChild(SGraphObject &) override {}
+ void ClearChildren() override {}
+ void SetActive(bool inActive) override
+ {
+ SRenderPlugin &theItem = static_cast<SRenderPlugin &>(GetGraphObject());
+ theItem.m_Flags.SetActive(inActive);
+ }
+};
+
+struct SAliasTranslator : public SGraphObjectTranslator
+{
+ SGraphObjectTranslator *m_ReferenceTree;
+ Qt3DSDMInstanceHandle m_ReferencedInstance;
+ SAliasTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3ds::NVAllocatorCallback &inAlloc)
+ : SGraphObjectTranslator(inInstance, *QT3DS_NEW(inAlloc, SNode)())
+ , m_ReferenceTree(nullptr)
+ {
+ }
+ void RecurseAndCreateTranslators(STranslation &inContext,
+ qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+ {
+ for (QT3DSI32 idx = 0, end = inContext.m_AssetGraph.GetChildCount(inInstance); idx < end;
+ ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theChild = inContext.m_AssetGraph.GetChild(inInstance, idx);
+ inContext.GetOrCreateTranslator(theChild, m_InstanceHandle);
+ RecurseAndCreateTranslators(inContext, theChild);
+ }
+ }
+ void PushTranslation(STranslation &inContext) override
+ {
+ STranslatorDataModelParser theParser(inContext, GetInstanceHandle());
+ Option<SObjectRefType> theData = theParser.GetPropertyValue<SObjectRefType>(
+ inContext.m_ObjectDefinitions.m_Alias.m_ReferencedNode);
+ m_ReferencedInstance = Qt3DSDMInstanceHandle();
+ m_ReferenceTree = nullptr;
+ ((SNode *)m_GraphObject)->m_Flags.SetDirty(true);
+ if (theData.hasValue()) {
+ m_ReferencedInstance =
+ inContext.m_Reader.GetInstanceForObjectRef(GetInstanceHandle(), *theData);
+ if (inContext.m_Reader.IsInstance(m_ReferencedInstance)) {
+ m_ReferenceTree =
+ inContext.GetOrCreateTranslator(m_ReferencedInstance, m_InstanceHandle);
+ if (m_ReferenceTree
+ && !GraphObjectTypes::IsNodeType(m_ReferenceTree->GetGraphObject().m_Type)) {
+ QT3DS_ASSERT(false);
+ m_ReferenceTree = nullptr;
+ m_ReferencedInstance = Qt3DSDMInstanceHandle();
+ } else {
+ RecurseAndCreateTranslators(inContext, m_ReferencedInstance);
+ }
+ }
+ }
+ }
+
+ void AfterRenderGraphIsBuilt(STranslation &inContext) override
+ {
+ SNode &theItem = static_cast<SNode &>(GetGraphObject());
+ STranslatorDataModelParser theParser(inContext, m_InstanceHandle);
+ ITERATE_QT3DS_RENDER_NODE_PROPERTIES
+ theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Node.m_BoneId,
+ theItem.m_SkeletonId);
+ bool ignoresParent = false;
+ if (theParser.ParseProperty(inContext.m_ObjectDefinitions.m_Node.m_IgnoresParent,
+ ignoresParent))
+ theItem.m_Flags.SetIgnoreParentTransform(ignoresParent);
+ theItem.m_Flags.SetDirty(true);
+ }
+ void AppendChild(SGraphObject &inObject) override
+ {
+ if (m_ReferenceTree)
+ m_ReferenceTree->AppendChild(inObject);
+ }
+ void ClearChildren() override
+ {
+ if (m_ReferenceTree)
+ m_ReferenceTree->ClearChildren();
+ }
+ void SetActive(bool inActive) override
+ {
+ SNode &theItem = static_cast<SNode &>(GetGraphObject());
+ theItem.m_Flags.SetActive(inActive);
+ }
+ SGraphObject &GetGraphObject() override
+ {
+ if (m_ReferenceTree)
+ return *m_ReferenceTree->m_GraphObject;
+ return *m_GraphObject;
+ }
+ qt3dsdm::Qt3DSDMInstanceHandle GetSceneGraphInstanceHandle() override
+ {
+ if (m_ReferencedInstance.Valid())
+ return m_ReferencedInstance;
+ return m_InstanceHandle;
+ }
+ Qt3DSDMInstanceHandle GetInstanceHandle() override { return m_InstanceHandle; }
+
+ SGraphObject &GetNonAliasedGraphObject() override { return *m_GraphObject; }
+};
+}
+
+void SGraphObjectTranslator::PushTranslation(STranslation &inTranslatorContext)
+{
+ Q3DStudio::CString theId = inTranslatorContext.m_Reader.GetFileId(GetInstanceHandle());
+ if (theId.size())
+ GetGraphObject().m_Id =
+ inTranslatorContext.m_Context.GetStringTable().RegisterStr(theId.c_str());
+}
+
+bool STranslation::IncludeNode(const SNode &inNode)
+{
+ SGraphObjectTranslator *theTranslator = inNode.m_UserData.DynamicCast<SGraphObjectTranslator>();
+ if (theTranslator
+ && m_Doc.GetDocumentReader().IsCurrentlyActive(theTranslator->GetInstanceHandle()))
+ return true;
+ return false;
+}
+
+void STranslation::ReleaseEffect(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ if (m_Reader.IsInstance(inInstance) == false)
+ return;
+
+ qt3dsdm::ComposerObjectTypes::Enum theType = m_ObjectDefinitions.GetType(inInstance);
+ qt3dsdm::Qt3DSDMInstanceHandle theParentClass = m_Reader.GetFirstBaseClass(inInstance);
+
+ if (theType == NULL && theParentClass.Valid())
+ theType = m_ObjectDefinitions.GetType(theParentClass);
+
+ if (theType == qt3dsdm::ComposerObjectTypes::Effect) {
+ IEffectSystem &theSystem = m_Context.GetEffectSystem();
+ if (theParentClass.Valid()) {
+ Q3DStudio::CString theInstanceName = m_Reader.GetName(theParentClass);
+ CRegisteredString theNameStr =
+ m_Context.GetStringTable().RegisterStr(theInstanceName);
+
+ if (theSystem.IsEffectRegistered(theNameStr)) {
+ TInstanceToTranslatorMap::iterator theTranslatorList =
+ m_TranslatorMap.find(inInstance);
+ if (theTranslatorList != m_TranslatorMap.end())
+ m_TranslatorMap.erase(theTranslatorList);
+ theSystem.SetEffectRequiresCompilation(theNameStr, true);
+ }
+ }
+ }
+}
+
+SGraphObjectTranslator *STranslation::CreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ SGraphObjectTranslator *theNewTranslator = nullptr;
+ qt3dsdm::ComposerObjectTypes::Enum theType = m_ObjectDefinitions.GetType(inInstance);
+ qt3dsdm::Qt3DSDMInstanceHandle theParentClass = m_Reader.GetFirstBaseClass(inInstance);
+ if (theType == NULL && theParentClass.Valid())
+ theType = m_ObjectDefinitions.GetType(theParentClass);
+
+ // For the subset of possible instances, pick out the valid translators.
+ switch (theType) {
+ case qt3dsdm::ComposerObjectTypes::Group:
+ case qt3dsdm::ComposerObjectTypes::Component:
+ case qt3dsdm::ComposerObjectTypes::Node:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SNodeTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Scene:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SSceneTranslator)(inInstance, m_Allocator);
+ m_Scene = static_cast<SScene *>(&theNewTranslator->GetGraphObject());
+ m_Scene->m_Presentation = &m_Presentation;
+ break;
+ case qt3dsdm::ComposerObjectTypes::Layer:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SLayerTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Light:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SLightTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Camera:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SCameraTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Model:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SModelTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Image:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SImageTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Text:
+ theNewTranslator = QT3DS_NEW(m_Allocator, STextTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Material:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SDefaultMaterialTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::ReferencedMaterial:
+ theNewTranslator =
+ QT3DS_NEW(m_Allocator, SReferencedMaterialTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Alias:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SAliasTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Path:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SPathTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::SubPath:
+ theNewTranslator = QT3DS_NEW(m_Allocator, SPathSubPathTranslator)(inInstance, m_Allocator);
+ break;
+ case qt3dsdm::ComposerObjectTypes::Effect: {
+ IEffectSystem &theSystem = m_Context.GetEffectSystem();
+ if (theParentClass.Valid()) {
+ Q3DStudio::CString theInstanceName = m_Reader.GetName(theParentClass);
+ CRegisteredString theNameStr =
+ m_Context.GetStringTable().RegisterStr(theInstanceName);
+
+ if (theSystem.IsEffectRegistered(theNameStr)
+ && theSystem.DoesEffectRequireCompilation(theNameStr)) {
+ theSystem.UnregisterEffect(theNameStr);
+ }
+
+ if (!theSystem.IsEffectRegistered(theNameStr)) {
+ // We assume the effect has already been registered and such.
+ qt3dsdm::IMetaData &theMetaData(*m_StudioSystem.GetActionMetaData());
+ Q3DStudio::CString theInstancePath = m_Reader.GetSourcePath(theParentClass);
+ Option<qt3dsdm::SMetaDataEffect> theMetaEffect =
+ theMetaData.GetEffectBySourcePath(
+ m_Context.GetStringTable().GetNarrowStr(theInstancePath));
+ if (theMetaEffect.hasValue()) {
+ qt3ds::render::IUIPLoader::CreateEffectClassFromMetaEffect(
+ theNameStr, m_Context.GetFoundation(), theSystem, theMetaEffect,
+ m_Context.GetStringTable());
+ theSystem.SetEffectRequiresCompilation(theNameStr, true);
+ }
+ }
+
+ if (theSystem.IsEffectRegistered(theNameStr)) {
+ theNewTranslator = QT3DS_NEW(m_Allocator, SEffectTranslator)(
+ inInstance, m_Allocator,
+ *theSystem.CreateEffectInstance(theNameStr, m_Allocator));
+ }
+ }
+ }
+ break;
+ case qt3dsdm::ComposerObjectTypes::CustomMaterial: {
+ ICustomMaterialSystem &theSystem = m_Context.GetCustomMaterialSystem();
+ if (theParentClass.Valid()) {
+ Q3DStudio::CString theInstanceName = m_Reader.GetName(theParentClass);
+ CRegisteredString theNameStr =
+ m_Context.GetStringTable().RegisterStr(theInstanceName);
+ if (!theSystem.IsMaterialRegistered(theNameStr)) {
+ // We assume the effect has already been registered and such.
+ qt3dsdm::IMetaData &theMetaData(*m_StudioSystem.GetActionMetaData());
+ Q3DStudio::CString theInstancePath = m_Reader.GetSourcePath(theParentClass);
+ Option<qt3dsdm::SMetaDataCustomMaterial> theMaterialData =
+ theMetaData.GetMaterialBySourcePath(
+ m_Context.GetStringTable().GetNarrowStr(theInstancePath));
+ if (theMaterialData.hasValue()) {
+ qt3ds::render::IUIPLoader::CreateMaterialClassFromMetaMaterial(
+ theNameStr, m_Context.GetFoundation(), theSystem, theMaterialData,
+ m_Context.GetStringTable());
+ }
+ }
+ if (theSystem.IsMaterialRegistered(theNameStr)) {
+ theNewTranslator = QT3DS_NEW(m_Allocator, SCustomMaterialTranslator)(
+ inInstance, m_Allocator,
+ *theSystem.CreateCustomMaterial(theNameStr, m_Allocator));
+ static_cast<SCustomMaterialTranslator *>(theNewTranslator)->m_MaterialSystem =
+ &theSystem;
+ }
+ }
+ }
+ break;
+ case qt3dsdm::ComposerObjectTypes::RenderPlugin: {
+ theNewTranslator = QT3DS_NEW(m_Allocator, SRenderPluginTranslator)(inInstance, m_Allocator);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return theNewTranslator;
+}
+
+bool CompareTranslator(const STranslation::THandleTranslatorPair &first,
+ const STranslation::THandleTranslatorPair &second)
+{
+ return first.first == second.first;
+}
+
+struct STranslatorPredicate
+{
+ Qt3DSDMInstanceHandle m_Instance;
+ STranslatorPredicate(Qt3DSDMInstanceHandle &ins)
+ : m_Instance(ins)
+ {
+ }
+ bool operator()(const STranslation::THandleTranslatorPair &first) const
+ {
+ return first.first == m_Instance;
+ }
+};
+
+Option<STranslation::THandleTranslatorPair>
+FindTranslator(STranslation::THandleTranslatorPairList &inList,
+ Qt3DSDMInstanceHandle inInstance = Qt3DSDMInstanceHandle())
+{
+ STranslation::THandleTranslatorPairList::iterator iter =
+ eastl::find_if(inList.begin(), inList.end(), STranslatorPredicate(inInstance));
+ if (iter != inList.end())
+ return *iter;
+ return Empty();
+}
+
+SGraphObjectTranslator *STranslation::GetOrCreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ return GetOrCreateTranslator(inInstance, Qt3DSDMInstanceHandle());
+}
+
+SGraphObjectTranslator *
+STranslation::GetOrCreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMInstanceHandle inAliasInstance)
+{
+ TInstanceToTranslatorMap::iterator theTranslatorList =
+ m_TranslatorMap.insert(eastl::make_pair(inInstance, THandleTranslatorPairList())).first;
+ THandleTranslatorPairList &theList = theTranslatorList->second;
+ Option<STranslation::THandleTranslatorPair> theExistingTranslator =
+ FindTranslator(theList, inAliasInstance);
+
+ if (theExistingTranslator.hasValue()) {
+ return theExistingTranslator->second;
+ }
+ if (m_Reader.IsInstance(inInstance) == false)
+ return nullptr;
+
+ SGraphObjectTranslator *theNewTranslator = CreateTranslator(inInstance);
+ if (theNewTranslator != nullptr) {
+ theNewTranslator->m_AliasInstanceHandle = inAliasInstance;
+ m_DirtySet.insert(*theNewTranslator);
+ theList.push_back(THandleTranslatorPair(inAliasInstance, theNewTranslator));
+ }
+
+ return theNewTranslator;
+}
+
+STranslation::THandleTranslatorPairList &
+STranslation::GetTranslatorsForInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ return m_TranslatorMap.insert(eastl::make_pair(inInstance, THandleTranslatorPairList()))
+ .first->second;
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle STranslation::GetAnchorPoint(QT3DSU32 inAnchorIndex)
+{
+ SGraphObjectTranslator *thePathTranslator =
+ static_cast<SGraphObjectTranslator *>(m_PathWidget->GetNode().m_UserData.m_UserData);
+ if (thePathTranslator == nullptr)
+ return qt3dsdm::Qt3DSDMInstanceHandle();
+ qt3dsdm::Qt3DSDMInstanceHandle thePathHandle = thePathTranslator->GetInstanceHandle();
+ QT3DSU32 theAnchorIndex = 0;
+ for (QT3DSI32 idx = 0, end = m_AssetGraph.GetChildCount(thePathHandle); idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theChildInstance = m_AssetGraph.GetChild(thePathHandle, idx);
+ if (m_Doc.GetDocumentReader().GetObjectTypeName(theChildInstance) == L"SubPath") {
+ QT3DSI32 numAnchors = m_AssetGraph.GetChildCount(theChildInstance);
+ QT3DSU32 endIndex = theAnchorIndex + (QT3DSU32)numAnchors;
+ if (endIndex > inAnchorIndex) {
+ return m_AssetGraph.GetChild(theChildInstance, inAnchorIndex - theAnchorIndex);
+ } else
+ theAnchorIndex = endIndex;
+ }
+ }
+ return qt3dsdm::Qt3DSDMInstanceHandle();
+}
+
+qt3dsdm::Qt3DSDMInstanceHandle STranslation::GetAnchorPoint(SPathPick &inPick)
+{
+ return GetAnchorPoint(inPick.m_AnchorIndex);
+}
+
+namespace qt3ds {
+namespace studio {
+ struct SEditCameraLayerTranslator : public SLayerTranslator
+ {
+ SEditCameraLayerTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3ds::NVAllocatorCallback &inAlloc)
+ : SLayerTranslator(inInstance, inAlloc)
+ {
+ }
+ void PushTranslation(STranslation &) override
+ {
+ SLayer &theItem = static_cast<SLayer &>(GetGraphObject());
+ theItem.m_Flags.SetActive(true);
+ }
+ void AppendChild(SGraphObject &inChild) override
+ {
+ if (GraphObjectTypes::IsNodeType(inChild.m_Type)) {
+ SNodeTranslator::AppendChild(inChild);
+ }
+ }
+ };
+}
+}
+
+STranslation::STranslation(IStudioRenderer &inRenderer, IQt3DSRenderContext &inContext)
+ : m_Renderer(inRenderer)
+ , m_Context(inContext)
+ , m_Doc(*g_StudioApp.GetCore()->GetDoc())
+ , m_Reader(m_Doc.GetDocumentReader())
+ , m_ObjectDefinitions(
+ m_Doc.GetStudioSystem()->GetClientDataModelBridge()->GetObjectDefinitions())
+ , m_StudioSystem(*m_Doc.GetStudioSystem())
+ , m_FullSystem(*m_Doc.GetStudioSystem()->GetFullSystem())
+ , m_AssetGraph(*m_Doc.GetAssetGraph())
+ , m_Allocator(inContext.GetRenderContext().GetFoundation())
+ , m_TranslatorMap(inContext.GetAllocator(), "STranslation::m_TranslatorMap")
+ , m_DirtySet(inContext.GetAllocator(), "STranslation::m_DirtySet")
+ , m_Scene(nullptr)
+ , m_SignalConnections(inContext.GetAllocator(), "STranslation::m_SignalConnections")
+ , m_ComponentSecondsDepth(0)
+ , m_KeyRepeat(0)
+ , m_EditCameraEnabled(false)
+ , m_EditLightEnabled(false)
+ , m_Viewport(0, 0)
+ , m_EditCameraLayerTranslator(nullptr)
+ , m_PixelBuffer(inContext.GetAllocator(), "STranslation::m_PixelBuffer")
+ , m_editModeCamerasAndLights(inContext.GetAllocator(),
+ "STranslation::m_editModeCamerasAndLights")
+ , m_GuideAllocator(inContext.GetAllocator(), "STranslation::m_GuideAllocator")
+{
+ m_EditCamera.m_Flags.SetActive(true);
+ m_EditLight.m_Flags.SetActive(true);
+ qt3dsdm::Qt3DSDMInstanceHandle theScene = m_AssetGraph.GetRoot(0);
+ m_GraphIterator.ClearResults();
+ m_AssetGraph.GetDepthFirst(m_GraphIterator, theScene);
+ for (; !m_GraphIterator.IsDone(); ++m_GraphIterator) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(m_GraphIterator.GetCurrent());
+ GetOrCreateTranslator(theInstance);
+ }
+ qt3dsdm::IStudioFullSystemSignalProvider *theProvider = m_FullSystem.GetSignalProvider();
+ m_SignalConnections.push_back(
+ theProvider->ConnectInstanceCreated(std::bind(&STranslation::DoMarkDirty,
+ this, std::placeholders::_1)));
+ m_SignalConnections.push_back(theProvider->ConnectInstanceDeleted(
+ std::bind(&STranslation::ReleaseTranslation, this, std::placeholders::_1)));
+ m_SignalConnections.push_back(
+ theProvider->ConnectInstancePropertyValue(std::bind(&STranslation::DoMarkDirty,
+ this, std::placeholders::_1)));
+ m_SignalConnections.push_back(m_AssetGraph.ConnectChildAdded(
+ std::bind(&STranslation::MarkGraphInstanceDirty, this, std::placeholders::_1,
+ std::placeholders::_2)));
+ m_SignalConnections.push_back(m_AssetGraph.ConnectChildMoved(
+ std::bind(&STranslation::MarkGraphInstanceDirty, this, std::placeholders::_1,
+ std::placeholders::_2)));
+ m_SignalConnections.push_back(m_AssetGraph.ConnectChildRemoved(
+ std::bind(&STranslation::MarkGraphInstanceDirty, this, std::placeholders::_1,
+ std::placeholders::_2)));
+ m_SignalConnections.push_back(theProvider->ConnectBeginComponentSeconds(
+ std::bind(&STranslation::MarkBeginComponentSeconds, this, std::placeholders::_1)));
+ m_SignalConnections.push_back(theProvider->ConnectComponentSeconds(
+ std::bind(&STranslation::MarkComponentSeconds, this, std::placeholders::_1)));
+
+ ::CColor color = CStudioPreferences::GetRulerBackgroundColor(); // Rectangles under tick marks
+ m_rectColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+ color = CStudioPreferences::GetRulerTickColor(); // Tick marks
+ m_lineColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+ color = CStudioPreferences::GetGuideColor();
+ m_guideColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+ color = CStudioPreferences::GetGuideSelectedColor();
+ m_selectedGuideColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+ color = CStudioPreferences::GetGuideFillColor(); // Not sure what this is used for
+ m_guideFillColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+ color = CStudioPreferences::GetGuideFillSelectedColor(); // Not sure what this is used for
+ m_selectedGuideFillColor = QT3DSVec4(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f,
+ 1.f);
+}
+
+void STranslation::BuildRenderGraph(SGraphObjectTranslator &inParent, bool scenePreviewPass,
+ Qt3DSDMInstanceHandle inAliasHandle)
+{
+ SGraphObjectTranslator &theParentTranslator(inParent);
+ theParentTranslator.ClearChildren();
+ if (m_EditCameraEnabled && !scenePreviewPass) {
+ const auto objectType = theParentTranslator.GetGraphObject().m_Type;
+ if (objectType == GraphObjectTypes::Layer) {
+ theParentTranslator.AppendChild(m_EditCamera);
+ if (m_EditLightEnabled) {
+ m_EditLight.m_Parent = &m_EditCamera;
+ m_EditCamera.m_FirstChild = &m_EditLight;
+ } else {
+ m_EditCamera.m_FirstChild = nullptr;
+ m_EditLight.m_Parent = nullptr;
+ }
+ } else if (objectType == GraphObjectTypes::Light) {
+ m_editModeCamerasAndLights.push_back(&inParent);
+ } else if (objectType == GraphObjectTypes::Camera) {
+ m_editModeCamerasAndLights.push_back(&inParent);
+ }
+ }
+
+ // Alias handles propagate down the scene graph.
+ if (inParent.GetInstanceHandle() != inParent.GetSceneGraphInstanceHandle())
+ inAliasHandle = inParent.GetInstanceHandle();
+ for (long idx = 0, end = m_AssetGraph.GetChildCount(inParent.GetSceneGraphInstanceHandle());
+ idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theChild(
+ m_AssetGraph.GetChild(inParent.GetSceneGraphInstanceHandle(), idx));
+ SGraphObjectTranslator *theTranslator = GetOrCreateTranslator(theChild, inAliasHandle);
+ if (theTranslator == nullptr)
+ continue;
+
+ // We we have edit cameras active, we only render the active layer and we remove any cameras
+ // in the active layer. Furthermore if our edit light is active, then we also remove any
+ // active lights in the layer.
+ if (m_EditCameraEnabled && !scenePreviewPass) {
+ if (theTranslator->GetGraphObject().m_Type == GraphObjectTypes::Layer) {
+ if (theChild == m_Doc.GetActiveLayer()) {
+ if (m_EditCameraLayerTranslator != nullptr
+ && m_EditCameraLayerTranslator->GetInstanceHandle() != theChild) {
+ QT3DS_FREE(m_Allocator, m_EditCameraLayerTranslator);
+ m_EditCameraLayerTranslator = nullptr;
+ }
+ if (!m_EditCameraLayerTranslator) {
+ m_EditCameraLayerTranslator =
+ QT3DS_NEW(m_Allocator, SEditCameraLayerTranslator)(theChild,
+ m_Allocator);
+ }
+ theTranslator = m_EditCameraLayerTranslator;
+ theParentTranslator.AppendChild(theTranslator->GetGraphObject());
+ BuildRenderGraph(*m_EditCameraLayerTranslator, scenePreviewPass);
+ }
+ } else {
+ theParentTranslator.AppendChild(theTranslator->GetGraphObject());
+ BuildRenderGraph(theChild, scenePreviewPass, inAliasHandle);
+
+ if (theTranslator->GetGraphObject().m_Type == GraphObjectTypes::Effect)
+ theTranslator->SetActive(false);
+ else if (theTranslator->GetGraphObject().m_Type == GraphObjectTypes::Camera)
+ theTranslator->SetActive(false);
+ else if (theTranslator->GetGraphObject().m_Type == GraphObjectTypes::Light
+ && m_EditLightEnabled == true)
+ theTranslator->SetActive(false);
+ else
+ theTranslator->SetActive(m_Reader.IsCurrentlyActive(theChild));
+ }
+ } else // Else build the graph and it will be an exact copy of the asset graph.
+ {
+ theParentTranslator.AppendChild(theTranslator->GetGraphObject());
+ if (m_Reader.IsCurrentlyActive(theChild)) {
+ BuildRenderGraph(theChild, scenePreviewPass, inAliasHandle);
+ theTranslator->SetActive(true);
+ } else {
+ theTranslator->SetActive(false);
+ DeactivateScan(*theTranslator, inAliasHandle);
+ }
+ }
+ }
+ if (GraphObjectTypes::Layer == theParentTranslator.GetGraphObject().m_Type)
+ m_Context.GetRenderer().ChildrenUpdated(
+ static_cast<SLayer &>(theParentTranslator.GetGraphObject()));
+
+ // Allow certain nodes to override their children.
+ theParentTranslator.AfterRenderGraphIsBuilt(*this);
+}
+
+void STranslation::DeactivateScan(SGraphObjectTranslator &inParent,
+ Qt3DSDMInstanceHandle inAliasHandle)
+{
+ // Alias handles propagate down the scene graph.
+ if (inParent.GetInstanceHandle() != inParent.GetSceneGraphInstanceHandle())
+ inAliasHandle = inParent.GetInstanceHandle();
+ for (long idx = 0, end = m_AssetGraph.GetChildCount(inParent.GetSceneGraphInstanceHandle());
+ idx < end; ++idx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theChild(
+ m_AssetGraph.GetChild(inParent.GetSceneGraphInstanceHandle(), idx));
+ SGraphObjectTranslator *theTranslator = GetOrCreateTranslator(theChild, inAliasHandle);
+ if (theTranslator == nullptr)
+ continue;
+ theTranslator->SetActive(false);
+ DeactivateScan(*theTranslator, inAliasHandle);
+ }
+}
+
+// We build the render graph every time we render. This may seem wasteful
+void STranslation::BuildRenderGraph(qt3dsdm::Qt3DSDMInstanceHandle inParent, bool scenePreviewPass,
+ Qt3DSDMInstanceHandle inAliasHandle)
+{
+ SGraphObjectTranslator *theParentTranslator = GetOrCreateTranslator(inParent, inAliasHandle);
+ if (theParentTranslator == nullptr)
+ return;
+ if (m_Reader.IsCurrentlyActive(inParent) == false) {
+ theParentTranslator->SetActive(false);
+ return;
+ }
+ BuildRenderGraph(*theParentTranslator, scenePreviewPass, inAliasHandle);
+}
+
+void STranslation::ReleaseTranslation(Q3DStudio::TIdentifier inInstance)
+{
+ m_TranslatorMap.erase(inInstance);
+}
+
+void STranslation::MarkDirty(qt3dsdm::Qt3DSDMInstanceHandle inInstance)
+{
+ // Anchor points are not handled individually.
+ if (m_Reader.GetObjectTypeName(inInstance) == L"PathAnchorPoint")
+ inInstance = m_AssetGraph.GetParent(inInstance);
+ GetOrCreateTranslator(inInstance);
+
+ THandleTranslatorPairList &theTranslators = GetTranslatorsForInstance(inInstance);
+ for (size_t idx = 0, end = theTranslators.size(); idx < end; ++idx) {
+ m_DirtySet.insert(*theTranslators[(eastl::allocator::size_type)idx].second);
+ // Reset effect when effect parameters change, as with certain corner cases
+ // some effects would accumulate ~infinitely and result would not reflect
+ // actual parameter setting unless reset (f.ex corona, other blur types)
+ m_DirtySet.back()->ResetEffect();
+ }
+ RequestRender();
+}
+
+QT3DSVec2 STranslation::GetPreviewViewportDimensions() const
+{
+ CStudioProjectSettings *theSettings = m_Doc.GetCore()->GetStudioProjectSettings();
+ QSize thePresSize = theSettings->getPresentationSize();
+ return QT3DSVec2(thePresSize.width(), thePresSize.height());
+}
+
+qt3ds::QT3DSVec2 STranslation::GetOverlayPreviewDimensions() const
+{
+ QT3DSVec2 ret(0.0f);
+ if (hasRoomForOverlayPreview()) {
+ CStudioProjectSettings *theSettings = m_Doc.GetCore()->GetStudioProjectSettings();
+ QSize thePresSize = theSettings->getPresentationSize();
+ ret = QT3DSVec2(thePresSize.width(), thePresSize.height());
+
+ const float aspect = ret.x / ret.y;
+ if (aspect > 1.0) {
+ ret.x = m_overlayPreviewSize > ret.x ? ret.x : m_overlayPreviewSize;
+ ret.y = ret.x / aspect;
+ } else {
+ ret.y = m_overlayPreviewSize > ret.y ? ret.y : m_overlayPreviewSize;
+ ret.x = ret.y * aspect;
+ }
+ }
+ return ret;
+}
+
+void STranslation::PreRender(bool scenePreviewPass)
+{
+ // Run through the entire asset graph and mark active or inactive if we have an
+ // associated render representation.
+ // If we cache all the components and some of their state then we don't have to do this
+ // but for now it is more stable to run through the graph.
+ // There is always one root, the scene.
+ TIdentifier theRoot = m_AssetGraph.GetRoot(0);
+ if (!scenePreviewPass)
+ m_editModeCamerasAndLights.clear();
+ ClearDirtySet();
+ m_EditLightEnabled = CStudioPreferences::editModeLightingEnabled();
+ BuildRenderGraph(theRoot, scenePreviewPass);
+ QT3DSVec2 theViewportDims(GetViewportDimensions());
+ if (scenePreviewPass) {
+ m_Context.SetScaleMode(qt3ds::render::ScaleModes::FitSelected);
+ theViewportDims = GetPreviewViewportDimensions();
+ } else {
+ m_Context.SetScaleMode(qt3ds::render::ScaleModes::ExactSize);
+ }
+
+ static const QT3DSVec4 matteColor(CStudioPreferences::matteColor().redF(),
+ CStudioPreferences::matteColor().greenF(),
+ CStudioPreferences::matteColor().blueF(), 1.0f);
+ m_Context.SetMatteColor(matteColor);
+ // Ensure the camera points where it should
+ if (m_EditCameraEnabled && !scenePreviewPass) {
+ m_EditCameraInfo.ApplyToCamera(m_EditCamera, theViewportDims);
+ m_EditLight.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty);
+ }
+
+ if (m_Scene) {
+ CStudioProjectSettings *theSettings = m_Doc.GetCore()->GetStudioProjectSettings();
+ QSize thePresSize = theSettings->getPresentationSize();
+ // The presentation sizes are used for when we have to render a layer offscreen.
+ // If their width and height isn't set, then they use the presentation dimensions.
+ m_Presentation.m_PresentationDimensions
+ = QT3DSVec2((QT3DSF32)thePresSize.width(), (QT3DSF32)thePresSize.height());
+ m_Context.SetWindowDimensions(
+ QSize((QT3DSU32)theViewportDims.x, (QT3DSU32)theViewportDims.y));
+ m_Context.SetPresentationDimensions(
+ QSize((QT3DSU32)m_Presentation.m_PresentationDimensions.x,
+ (QT3DSU32)m_Presentation.m_PresentationDimensions.y));
+
+ // set if we draw geometry in wireframe mode
+ m_Context.SetWireframeMode(CStudioPreferences::IsWireframeModeOn());
+
+ if (m_EditCameraEnabled && !scenePreviewPass) {
+ m_Presentation.m_PresentationDimensions = theViewportDims;
+ m_Context.SetPresentationDimensions(
+ QSize((QT3DSU32)theViewportDims.x, (QT3DSU32)theViewportDims.y));
+ m_Context.SetSceneColor(QT3DSVec4(0.0f, 0.0f, 0.0f, 1.0f));
+ } else {
+
+ TIdentifier theRoot = m_AssetGraph.GetRoot(0);
+ SGraphObjectTranslator *theSceneTranslator = GetOrCreateTranslator(theRoot);
+ if (theSceneTranslator) {
+ SScene &theScene = static_cast<SScene &>(theSceneTranslator->GetGraphObject());
+ if (scenePreviewPass) {
+ if (theScene.m_UseClearColor)
+ m_Context.SetMatteColor(theScene.m_ClearColor);
+ else
+ m_Context.SetMatteColor(QT3DSVec4(0.0f, 0.0f, 0.0f, 1.0f));
+ } else {
+ if (theScene.m_UseClearColor)
+ m_Context.SetSceneColor(theScene.m_ClearColor);
+ else
+ m_Context.SetSceneColor(QT3DSVec4(QT3DSVec3(0.0f), 1.0f));
+ }
+ }
+ }
+ }
+ if (m_EditCameraEnabled == false && g_StudioApp.IsAuthorZoom()) {
+ if (m_Presentation.m_PresentationDimensions.x > theViewportDims.x
+ || m_Presentation.m_PresentationDimensions.y > theViewportDims.y) {
+ m_Context.SetScaleMode(qt3ds::render::ScaleModes::FitSelected);
+ }
+ }
+}
+
+static void CreatePixelRect(STranslation &inTranslation, QT3DSF32 left, QT3DSF32 right, QT3DSF32 bottom,
+ QT3DSF32 top, QT3DSVec4 color)
+{
+ SPGRect *theRect = QT3DS_NEW(inTranslation.m_GuideAllocator, SPGRect)();
+ theRect->m_Left = left;
+ theRect->m_Right = right;
+ theRect->m_Top = top;
+ theRect->m_Bottom = bottom;
+ theRect->m_FillColor = color;
+ inTranslation.m_GuideContainer.push_back(theRect);
+}
+
+static void CreatePixelVertLine(STranslation &inTranslation, QT3DSF32 inXPos, QT3DSF32 inBottom,
+ QT3DSF32 inTop, QT3DSVec4 color)
+{
+ SPGVertLine *theLine = QT3DS_NEW(inTranslation.m_GuideAllocator, SPGVertLine)();
+ theLine->m_X = inXPos;
+ theLine->m_Bottom = inBottom;
+ theLine->m_Top = inTop;
+ theLine->m_LineColor = color;
+ inTranslation.m_GuideContainer.push_back(theLine);
+}
+
+static void CreatePixelHorzLine(STranslation &inTranslation, QT3DSF32 inYPos, QT3DSF32 inLeft,
+ QT3DSF32 inRight, QT3DSVec4 color)
+{
+ SPGHorzLine *theLine = QT3DS_NEW(inTranslation.m_GuideAllocator, SPGHorzLine)();
+ theLine->m_Y = inYPos;
+ theLine->m_Left = inLeft;
+ theLine->m_Right = inRight;
+ theLine->m_LineColor = color;
+ inTranslation.m_GuideContainer.push_back(theLine);
+}
+
+static void CreateTopBottomTickMarks(STranslation &inTranslation, QT3DSF32 posX, QT3DSF32 innerBottom,
+ QT3DSF32 innerTop, QT3DSF32 outerBottom, QT3DSF32 outerTop,
+ QT3DSF32 lineHeight, QT3DSVec4 lineColor)
+{
+ CreatePixelVertLine(inTranslation, posX, innerBottom - lineHeight, innerBottom, lineColor);
+ CreatePixelVertLine(inTranslation, posX, innerTop, innerTop + lineHeight, lineColor);
+}
+
+static void DrawTickMarksOnHorizontalRects(STranslation &inTranslation, QT3DSF32 innerLeft,
+ QT3DSF32 innerRight, QT3DSF32 innerBottom, QT3DSF32 innerTop,
+ QT3DSF32 outerBottom, QT3DSF32 outerTop, QT3DSVec4 lineColor)
+{
+ QT3DSF32 centerPosX = floor(innerLeft + (innerRight - innerLeft) / 2.0f + .5f);
+ CreateTopBottomTickMarks(inTranslation, centerPosX, innerBottom, innerTop, outerBottom,
+ outerTop, 15, lineColor);
+ for (QT3DSU32 incrementor = 10;
+ (centerPosX + incrementor) < innerRight && (centerPosX - incrementor) > innerLeft;
+ incrementor += 10) {
+ QT3DSF32 rightEdge = centerPosX + incrementor;
+ QT3DSF32 leftEdge = centerPosX - incrementor;
+ QT3DSF32 lineHeight = 0;
+ if (incrementor % 100 == 0)
+ lineHeight = 11;
+ else if (incrementor % 20)
+ lineHeight = 4;
+ else
+ lineHeight = 2;
+
+ if (rightEdge < innerRight)
+ CreateTopBottomTickMarks(inTranslation, rightEdge, innerBottom, innerTop, outerBottom,
+ outerTop, lineHeight, lineColor);
+ if (leftEdge > innerLeft)
+ CreateTopBottomTickMarks(inTranslation, leftEdge, innerBottom, innerTop, outerBottom,
+ outerTop, lineHeight, lineColor);
+ }
+}
+
+static void CreateLeftRightTickMarks(STranslation &inTranslation, QT3DSF32 inYPos, QT3DSF32 innerLeft,
+ QT3DSF32 innerRight, QT3DSF32 outerLeft, QT3DSF32 outerRight,
+ QT3DSF32 lineLength, QT3DSVec4 lineColor)
+{
+ CreatePixelHorzLine(inTranslation, inYPos, innerLeft - lineLength, innerLeft, lineColor);
+ CreatePixelHorzLine(inTranslation, inYPos, innerRight, innerRight + lineLength, lineColor);
+}
+
+static void DrawTickMarksOnVerticalRects(STranslation &inTranslation, QT3DSF32 innerLeft,
+ QT3DSF32 innerRight, QT3DSF32 innerBottom, QT3DSF32 innerTop,
+ QT3DSF32 outerLeft, QT3DSF32 outerRight, QT3DSVec4 lineColor)
+{
+ QT3DSF32 centerPosY = floor(innerBottom + (innerTop - innerBottom) / 2.0f + .5f);
+ CreateLeftRightTickMarks(inTranslation, centerPosY, innerLeft, innerRight, outerLeft,
+ outerRight, 15, lineColor);
+ for (QT3DSU32 incrementor = 10;
+ (centerPosY + incrementor) < innerTop && (centerPosY - incrementor) > innerBottom;
+ incrementor += 10) {
+ QT3DSF32 topEdge = centerPosY + incrementor;
+ QT3DSF32 bottomEdge = centerPosY - incrementor;
+ QT3DSF32 lineHeight = 0;
+ if (incrementor % 100 == 0)
+ lineHeight = 11;
+ else if (incrementor % 20)
+ lineHeight = 4;
+ else
+ lineHeight = 2;
+
+ if (topEdge < innerTop)
+ CreateLeftRightTickMarks(inTranslation, topEdge, innerLeft, innerRight, outerLeft,
+ outerRight, lineHeight, lineColor);
+ if (bottomEdge > innerBottom)
+ CreateLeftRightTickMarks(inTranslation, bottomEdge, innerLeft, innerRight, outerLeft,
+ outerRight, lineHeight, lineColor);
+ }
+}
+
+class IGuideElementFactory
+{
+protected:
+ virtual ~IGuideElementFactory() {}
+public:
+ virtual void CreateLine(QT3DSF32 inPos) = 0;
+ virtual void CreateRect(QT3DSF32 inPosMin, QT3DSF32 inPosMax) = 0;
+};
+
+static void CreateGuide(IGuideElementFactory &inFactory, QT3DSF32 inPos, QT3DSF32 inWidth)
+{
+ QT3DSF32 halfWidth = inWidth / 2.0f;
+ QT3DSF32 leftLine = floor(inPos + 1.0f - halfWidth);
+ inFactory.CreateLine(leftLine);
+ // Then we are done if not enough width
+ if (inWidth < 2.0f)
+ return;
+
+ QT3DSF32 rightLine = leftLine + inWidth - 1;
+ inFactory.CreateLine(rightLine);
+
+ if (inWidth < 3.0f)
+ return;
+ QT3DSF32 rectStart = leftLine + 1;
+ QT3DSF32 rectStop = rectStart + inWidth - 2.0f;
+ inFactory.CreateRect(rectStart, rectStop);
+}
+
+struct SHorizontalGuideFactory : public IGuideElementFactory
+{
+ STranslation &m_Translation;
+ QT3DSF32 m_Start;
+ QT3DSF32 m_Stop;
+ QT3DSVec4 m_LineColor;
+ QT3DSVec4 m_FillColor;
+ SHorizontalGuideFactory(STranslation &trans, QT3DSF32 start, QT3DSF32 stop, QT3DSVec4 lineColor,
+ QT3DSVec4 fillColor)
+ : m_Translation(trans)
+ , m_Start(start)
+ , m_Stop(stop)
+ , m_LineColor(lineColor)
+ , m_FillColor(fillColor)
+ {
+ }
+ void CreateLine(QT3DSF32 inPos) override
+ {
+ CreatePixelHorzLine(m_Translation, inPos, m_Start, m_Stop, m_LineColor);
+ }
+
+ void CreateRect(QT3DSF32 inPosMin, QT3DSF32 inPosMax) override
+ {
+ CreatePixelRect(m_Translation, m_Start, m_Stop, inPosMin, inPosMax, m_FillColor);
+ }
+};
+
+struct SVerticalGuideFactory : public IGuideElementFactory
+{
+ STranslation &m_Translation;
+ QT3DSF32 m_Start;
+ QT3DSF32 m_Stop;
+ QT3DSVec4 m_LineColor;
+ QT3DSVec4 m_FillColor;
+ SVerticalGuideFactory(STranslation &trans, QT3DSF32 start, QT3DSF32 stop, QT3DSVec4 lineColor,
+ QT3DSVec4 fillColor)
+ : m_Translation(trans)
+ , m_Start(start)
+ , m_Stop(stop)
+ , m_LineColor(lineColor)
+ , m_FillColor(fillColor)
+ {
+ }
+ void CreateLine(QT3DSF32 inPos) override
+ {
+ CreatePixelVertLine(m_Translation, inPos, m_Start, m_Stop, m_LineColor);
+ }
+
+ void CreateRect(QT3DSF32 inPosMin, QT3DSF32 inPosMax) override
+ {
+ CreatePixelRect(m_Translation, inPosMin, inPosMax, m_Start, m_Stop, m_FillColor);
+ }
+};
+
+qt3ds::render::NVRenderRect STranslation::GetPreviewViewport() const
+{
+ QT3DSVec2 vp = GetPreviewViewportDimensions();
+ return qt3ds::render::NVRenderRect(0, 0, vp.x, vp.y);
+}
+
+qt3ds::render::NVRenderRect STranslation::GetOverlayPreviewViewport() const
+{
+ QT3DSVec2 vp = GetOverlayPreviewDimensions();
+ return qt3ds::render::NVRenderRect(0, 0, vp.x, vp.y);
+}
+
+bool STranslation::hasRoomForOverlayPreview() const
+{
+ QT3DSVec2 vp(GetViewportDimensions());
+ return vp.x > m_overlayPreviewSize && vp.y > m_overlayPreviewSize;
+}
+
+void STranslation::Render(int inWidgetId, bool inDrawGuides, bool scenePreviewPass,
+ bool overlayPreview)
+{
+ // For now, we just render.
+ // Next step will be to get the bounding boxes and such setup.
+ // but we will want a custom renderer to do that.
+ if (m_Scene) {
+ // Note that begin frame is called before we allocate the bounding box and axis widgets so
+ // that we can take advantage of the renderer's per-frame-allocator.
+ m_Context.BeginFrame(true);
+
+ qt3dsdm::TInstanceHandleList theHandles = m_Doc.GetSelectedValue().GetSelectedInstances();
+
+ if (scenePreviewPass) {
+ qt3ds::render::NVRenderContext &renderContext(m_Context.GetRenderContext());
+ QT3DSVec2 previewDims(GetPreviewViewportDimensions());
+ if (m_previewFboDimensions != previewDims) {
+ m_previewFboDimensions = previewDims;
+ if (m_previewFbo)
+ m_Context.GetResourceManager().Release(*m_previewFbo);
+ if (m_previewRenderBuffer)
+ m_Context.GetResourceManager().Release(*m_previewRenderBuffer);
+ if (m_previewTexture)
+ m_Context.GetResourceManager().Release(*m_previewTexture);
+ m_previewFbo = nullptr;
+ m_previewRenderBuffer = nullptr;
+ m_previewTexture = nullptr;
+ }
+ if (!m_previewFbo)
+ m_previewFbo = m_Context.GetResourceManager().AllocateFrameBuffer();
+ if (!m_previewTexture) {
+ m_previewTexture = renderContext.CreateTexture2D();
+ m_previewTexture->SetTextureData(qt3ds::foundation::NVDataRef<qt3ds::QT3DSU8>(),
+ 0, previewDims.x, previewDims.y,
+ qt3ds::render::NVRenderTextureFormats::RGBA8);
+ m_previewFbo->Attach(
+ qt3ds::render::NVRenderFrameBufferAttachments::Color0,
+ qt3ds::render::NVRenderTextureOrRenderBuffer(*m_previewTexture));
+ }
+ if (!m_previewRenderBuffer) {
+ m_previewRenderBuffer = m_Context.GetResourceManager().AllocateRenderBuffer(
+ previewDims.x, previewDims.y,
+ qt3ds::render::NVRenderRenderBufferFormats::Depth24);
+ m_previewFbo->Attach(
+ qt3ds::render::NVRenderFrameBufferAttachments::Depth,
+ qt3ds::render::NVRenderTextureOrRenderBuffer(*m_previewRenderBuffer));
+ }
+ renderContext.SetRenderTarget(m_previewFbo);
+ } else {
+ // Render the bounding boxes and extra widgets.
+ // This is called *before* the render because these sort of appendages need to be added
+ // to the layer renderables.
+
+ // Don't show the bounding box or pivot for the component we are *in* the component
+ SGraphObjectTranslator *theTranslator = nullptr;
+ long theToolMode = g_StudioApp.GetToolMode();
+ int theCameraToolMode = m_EditCameraEnabled
+ ? (theToolMode & STUDIO_CAMERATOOL_MASK) : 0;
+ bool shouldDisplayWidget = false;
+ if (theCameraToolMode == 0) {
+ switch (theToolMode) {
+ default:
+ break;
+ case STUDIO_TOOLMODE_MOVE:
+ case STUDIO_TOOLMODE_ROTATE:
+ case STUDIO_TOOLMODE_SCALE:
+ shouldDisplayWidget = true;
+ break;
+ };
+ }
+
+ bool selectedPath = false;
+
+ for (size_t selectedIdx = 0, selectedEnd = theHandles.size(); selectedIdx < selectedEnd;
+ ++selectedIdx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance = theHandles[selectedIdx];
+ if (theInstance
+ != m_Doc.GetDocumentReader().GetComponentForSlide(m_Doc.GetActiveSlide())) {
+ if (m_Doc.GetDocumentReader().GetObjectTypeName(theInstance)
+ == L"PathAnchorPoint") {
+ theInstance = m_AssetGraph.GetParent(m_AssetGraph.GetParent(theInstance));
+ shouldDisplayWidget = false;
+ }
+ theTranslator = GetOrCreateTranslator(theInstance);
+ // Get the tool mode right now.
+ if (theTranslator) {
+ GraphObjectTypes::Enum theType(theTranslator->GetGraphObject().m_Type);
+ if (CStudioPreferences::IsBoundingBoxesOn()) {
+ switch (theType) {
+ case GraphObjectTypes::Node:
+ DrawGroupBoundingBoxes(*theTranslator);
+ break;
+ case GraphObjectTypes::Text:
+ case GraphObjectTypes::Model:
+ case GraphObjectTypes::Layer:
+ case GraphObjectTypes::Light:
+ case GraphObjectTypes::Path:
+ DrawNonGroupBoundingBoxes(*theTranslator);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Don't draw the axis if there is a widget.
+ if (CStudioPreferences::ShouldDisplayPivotPoint()) {
+ switch (theTranslator->GetGraphObject().m_Type) {
+ case GraphObjectTypes::Node:
+ case GraphObjectTypes::Text:
+ case GraphObjectTypes::Model:
+ drawPivot(*theTranslator);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (theType == GraphObjectTypes::Path && selectedPath == false) {
+ selectedPath = true;
+ if (!m_PathWidget) {
+ m_PathWidget = qt3ds::widgets::IPathWidget::CreatePathWidget(
+ m_Context.GetAllocator(), m_Context);
+ }
+ m_PathWidget->SetNode(
+ static_cast<SNode &>(theTranslator->GetGraphObject()));
+ m_Context.GetRenderer().AddRenderWidget(*m_PathWidget);
+ }
+ }
+ }
+ }
+
+ if (theHandles.size() > 1)
+ theTranslator = nullptr;
+
+ qt3ds::widgets::IStudioWidget *theNextWidget(nullptr);
+ if (theTranslator
+ && GraphObjectTypes::IsNodeType(theTranslator->GetGraphObject().m_Type)
+ && theTranslator->GetGraphObject().m_Type != GraphObjectTypes::Layer) {
+
+ qt3ds::render::SNode &theNode(
+ static_cast<qt3ds::render::SNode &>(theTranslator->GetGraphObject()));
+ const GraphObjectTypes::Enum type = theTranslator->GetGraphObject().m_Type;
+
+ // Don't draw widgets for non-visible nodes
+ bool isActive = theNode.m_Flags.IsActive();
+ // Light and camera nodes are never active, so check from doc
+ if (type == GraphObjectTypes::Camera || type == GraphObjectTypes::Light)
+ isActive = m_Reader.IsCurrentlyActive(theHandles[0]);
+ shouldDisplayWidget = shouldDisplayWidget && isActive;
+
+ SCamera *theRenderCamera = m_Context.GetRenderer().GetCameraForNode(theNode);
+ bool isActiveCamera = theRenderCamera == (static_cast<SCamera *>(&theNode));
+ if (shouldDisplayWidget && !isActiveCamera
+ && ((type == GraphObjectTypes::Camera && m_EditCameraEnabled)
+ || type != GraphObjectTypes::Camera)) {
+ switch (theToolMode) {
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ case STUDIO_TOOLMODE_MOVE:
+ // Render translation widget
+ if (!m_TranslationWidget) {
+ m_TranslationWidget
+ = qt3ds::widgets::IStudioWidget::CreateTranslationWidget(
+ m_Context.GetAllocator());
+ }
+ theNextWidget = m_TranslationWidget.mPtr;
+ break;
+ case STUDIO_TOOLMODE_ROTATE:
+ if (!m_RotationWidget) {
+ m_RotationWidget = qt3ds::widgets::IStudioWidget::CreateRotationWidget(
+ m_Context.GetAllocator());
+ }
+ theNextWidget = m_RotationWidget.mPtr;
+ break;
+
+ case STUDIO_TOOLMODE_SCALE:
+ if (!m_ScaleWidget) {
+ m_ScaleWidget = qt3ds::widgets::IStudioWidget::CreateScaleWidget(
+ m_Context.GetAllocator());
+ }
+ theNextWidget = m_ScaleWidget.mPtr;
+ break;
+ }
+
+ if (theNextWidget) {
+ SNode &node = static_cast<SNode &>(theTranslator->GetGraphObject());
+ theNextWidget->SetNode(node);
+ m_Context.GetRenderer().AddRenderWidget(*theNextWidget);
+ }
+ }
+ }
+ if (m_LastRenderedWidget.mPtr && m_LastRenderedWidget.mPtr != theNextWidget)
+ ResetWidgets();
+
+ m_LastRenderedWidget = theNextWidget;
+ if (m_LastRenderedWidget) {
+ m_LastRenderedWidget->SetSubComponentId(inWidgetId);
+ switch (g_StudioApp.GetManipulationMode()) {
+ case StudioManipulationModes::Local:
+ m_LastRenderedWidget->SetRenderWidgetMode(
+ qt3ds::render::RenderWidgetModes::Local);
+ break;
+ case StudioManipulationModes::Global:
+ m_LastRenderedWidget->SetRenderWidgetMode(
+ qt3ds::render::RenderWidgetModes::Global);
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ }
+ }
+ Option<NVRenderRect> viewport = m_Context.GetRenderContext().GetViewport();
+ if (scenePreviewPass) {
+ m_Context.GetRenderContext().SetViewport(GetPreviewViewport());
+ m_Context.SetSceneColor(Option<QT3DSVec4>());
+ }
+
+ m_Scene->PrepareForRender(scenePreviewPass ? GetPreviewViewportDimensions()
+ : GetViewportDimensions(), m_Context);
+
+ m_Context.RunRenderTasks();
+
+ if (!scenePreviewPass && m_EditCameraEnabled) {
+ if (m_GradientWidget == nullptr)
+ m_GradientWidget = qt3ds::widgets::SGradientWidget
+ ::CreateGradientWidget(m_Context.GetAllocator());
+ // render gradient background
+ SNode *node = GetEditCameraLayer();
+ m_GradientWidget->SetNode(*node);
+ m_GradientWidget->Render(m_Context.GetRenderWidgetContext(),
+ m_Context.GetRenderContext(),
+ m_EditCameraInfo.IsOrthographic());
+ }
+
+ m_Scene->Render(scenePreviewPass
+ ? GetPreviewViewportDimensions()
+ : GetViewportDimensions(), m_Context, SScene::DoNotClear);
+
+ if (!scenePreviewPass && m_editModeCamerasAndLights.size() > 0) {
+ if (!m_VisualAidWidget) {
+ m_VisualAidWidget = qt3ds::widgets::SVisualAidWidget
+ ::CreateVisualAidWidget(m_Context.GetAllocator());
+ }
+ for (SGraphObjectTranslator *translator : m_editModeCamerasAndLights) {
+ SGraphObject &object = translator->GetGraphObject();
+ qt3dsdm::Qt3DSDMInstanceHandle handle = translator->GetInstanceHandle();
+ m_VisualAidWidget->setSelected(false);
+
+ for (int j = 0; j < theHandles.size(); ++j) {
+ if (handle == theHandles[j]) {
+ m_VisualAidWidget->setSelected(true);
+ break;
+ }
+ }
+
+ if (object.m_Type == GraphObjectTypes::Camera) {
+ if (m_InnerRect.isNull()) {
+ // this happens when the initial view is not the camera view
+ NVRenderRect thePresentationViewport = m_Context.GetPresentationViewport();
+ m_InnerRect.m_Left = thePresentationViewport.m_X;
+ m_InnerRect.m_Right = thePresentationViewport.m_X
+ + thePresentationViewport.m_Width;
+ m_InnerRect.m_Bottom = thePresentationViewport.m_Y;
+ m_InnerRect.m_Top = thePresentationViewport.m_Y
+ + thePresentationViewport.m_Height;
+ }
+
+ QT3DSVec2 dim = QT3DSVec2(m_InnerRect.m_Right - m_InnerRect.m_Left,
+ m_InnerRect.m_Top - m_InnerRect.m_Bottom);
+ NVRenderRectF theViewport(0, 0, dim.x, dim.y);
+ static_cast<SCamera *>(&object)->CalculateGlobalVariables(theViewport, dim);
+ }
+ m_VisualAidWidget->SetNode(static_cast<SNode *>(&object));
+ m_VisualAidWidget->Render(m_Context.GetRenderWidgetContext(),
+ m_Context.GetRenderContext());
+ }
+ }
+
+ if (inDrawGuides && !m_EditCameraEnabled && !g_StudioApp.IsAuthorZoom()) {
+ m_GuideContainer.clear();
+ // Figure out the matte area.
+ NVRenderRect theContextViewport = m_Context.GetContextViewport();
+ NVRenderRect thePresentationViewport = m_Context.GetPresentationViewport();
+ m_Context.GetRenderContext().SetViewport(theContextViewport);
+ QT3DSI32 innerLeft = thePresentationViewport.m_X;
+ QT3DSI32 innerRight = thePresentationViewport.m_X + thePresentationViewport.m_Width;
+ QT3DSI32 innerBottom = thePresentationViewport.m_Y;
+ QT3DSI32 innerTop = thePresentationViewport.m_Y + thePresentationViewport.m_Height;
+
+ QT3DSI32 outerLeft = innerLeft - 16;
+ QT3DSI32 outerRight = innerRight + 16;
+ QT3DSI32 outerBottom = innerBottom - 16;
+ QT3DSI32 outerTop = innerTop + 16;
+ // Retain the rects for picking purposes.
+ m_InnerRect = SRulerRect(innerLeft, innerTop, innerRight, innerBottom);
+ m_OuterRect = SRulerRect(outerLeft, outerTop, outerRight, outerBottom);
+
+ // Draw tick marks around the presentation
+ CreatePixelRect(*this, (QT3DSF32)outerLeft, (QT3DSF32)innerLeft, (QT3DSF32)innerBottom,
+ (QT3DSF32)innerTop, m_rectColor);
+ CreatePixelRect(*this, (QT3DSF32)innerRight, (QT3DSF32)outerRight,
+ (QT3DSF32)innerBottom, (QT3DSF32)innerTop, m_rectColor);
+ CreatePixelRect(*this, (QT3DSF32)innerLeft, (QT3DSF32)innerRight, (QT3DSF32)outerBottom,
+ (QT3DSF32)innerBottom, m_rectColor);
+ CreatePixelRect(*this, (QT3DSF32)innerLeft, (QT3DSF32)innerRight, (QT3DSF32)innerTop,
+ (QT3DSF32)outerTop, m_rectColor);
+ DrawTickMarksOnHorizontalRects(*this, (QT3DSF32)innerLeft, (QT3DSF32)innerRight,
+ (QT3DSF32)innerBottom, (QT3DSF32)innerTop,
+ (QT3DSF32)outerBottom, (QT3DSF32)outerTop, m_lineColor);
+ DrawTickMarksOnVerticalRects(*this, (QT3DSF32)innerLeft, (QT3DSF32)innerRight,
+ (QT3DSF32)innerBottom, (QT3DSF32)innerTop,
+ (QT3DSF32)outerLeft, (QT3DSF32)outerRight, m_lineColor);
+ qt3dsdm::TGuideHandleList theGuides = m_Doc.GetDocumentReader().GetGuides();
+ qt3dsdm::Qt3DSDMGuideHandle theSelectedGuide;
+ Q3DStudio::SSelectedValue theSelection = m_Doc.GetSelectedValue();
+ if (theSelection.getType() == Q3DStudio::SelectedValueTypes::Guide)
+ theSelectedGuide = theSelection.getData<qt3dsdm::Qt3DSDMGuideHandle>();
+
+ // Draw guides
+ for (size_t guideIdx = 0, guideEnd = theGuides.size(); guideIdx < guideEnd;
+ ++guideIdx) {
+ qt3dsdm::SGuideInfo theInfo =
+ m_Doc.GetDocumentReader().GetGuideInfo(theGuides[guideIdx]);
+ bool isGuideSelected = theGuides[guideIdx] == theSelectedGuide;
+ QT3DSVec4 theColor = isGuideSelected ? m_selectedGuideColor : m_guideColor;
+ QT3DSVec4 theFillColor = isGuideSelected ? m_selectedGuideFillColor
+ : m_guideFillColor;
+ switch (theInfo.m_Direction) {
+ case qt3dsdm::GuideDirections::Horizontal: {
+ SHorizontalGuideFactory theFactory(*this, (QT3DSF32)innerLeft, (QT3DSF32)innerRight,
+ theColor, theFillColor);
+ CreateGuide(theFactory, (QT3DSF32)m_InnerRect.m_Bottom + theInfo.m_Position,
+ (QT3DSF32)theInfo.m_Width);
+ } break;
+ case qt3dsdm::GuideDirections::Vertical: {
+ SVerticalGuideFactory theFactory(*this, (QT3DSF32)innerBottom, (QT3DSF32)innerTop,
+ theColor, theFillColor);
+ CreateGuide(theFactory, (QT3DSF32)m_InnerRect.m_Left + theInfo.m_Position,
+ (QT3DSF32)theInfo.m_Width);
+ } break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ }
+ m_Context.GetPixelGraphicsRenderer().Render(
+ qt3ds::foundation::toDataRef(m_GuideContainer.data(), m_GuideContainer.size()));
+
+ m_GuideContainer.clear();
+ m_GuideAllocator.reset();
+ }
+
+ if (!scenePreviewPass && m_previewTexture) {
+ if (overlayPreview) {
+ // Draw the overlay framebuffer
+ qt3ds::render::NVRenderContext &renderContext(m_Context.GetRenderContext());
+ renderContext.SetViewport(GetOverlayPreviewViewport());
+ qt3ds::render::SCamera camera;
+ camera.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty);
+ camera.m_Flags.SetOrthographic(true);
+ QT3DSVec2 previewDims(GetOverlayPreviewDimensions());
+ camera.CalculateGlobalVariables(
+ render::NVRenderRectF(0, 0, previewDims.x, previewDims.y), previewDims);
+ QT3DSMat44 theVP;
+ camera.CalculateViewProjectionMatrix(theVP);
+ renderContext.SetCullingEnabled(false);
+ renderContext.SetBlendingEnabled(false);
+ renderContext.SetDepthTestEnabled(false);
+ renderContext.SetDepthWriteEnabled(false);
+ m_Context.GetRenderer().RenderQuad(previewDims, theVP, *m_previewTexture);
+ } else {
+ // Hack: For some reason, the m_previewTexture is only valid later if it is
+ // actually drawn somewhere during the main render pass, so draw a dummy quad
+ m_Context.GetRenderContext().SetViewport(NVRenderRect(0, 0, 0, 0));
+ m_Context.GetRenderer().RenderQuad(QT3DSVec2(0.0f), QT3DSMat44(),
+ *m_previewTexture);
+ }
+ }
+
+ if (scenePreviewPass)
+ m_Context.GetRenderContext().SetRenderTarget(nullptr);
+
+ m_Context.EndFrame();
+ m_Context.GetRenderContext().SetViewport(viewport);
+ QT3DSVec2 theViewportDims(GetViewportDimensions());
+ m_Context.SetWindowDimensions(QSize((QT3DSU32)theViewportDims.x,
+ (QT3DSU32)theViewportDims.y));
+ CStudioProjectSettings *theSettings = m_Doc.GetCore()->GetStudioProjectSettings();
+ QSize thePresSize = theSettings->getPresentationSize();
+ m_Presentation.m_PresentationDimensions =
+ QT3DSVec2((QT3DSF32)thePresSize.width(), (QT3DSF32)thePresSize.height());
+
+ if (m_ZoomRender.hasValue()) {
+ RenderZoomRender(*m_ZoomRender);
+ m_ZoomRender = Empty();
+ }
+
+ // Render the pick buffer, useful for debugging why a widget wasn't hit.
+ /*
+ if ( m_PickBuffer )
+ {
+ qt3ds::render::NVRenderContext& theRenderContext( m_Context.GetRenderContext() );
+ qt3ds::render::STextureDetails thePickDetails = m_PickBuffer->GetTextureDetails();
+ theRenderContext.SetViewport( qt3ds::render::NVRenderRect( 0, 0, thePickDetails.m_Width,
+ thePickDetails.m_Height ) );
+ qt3ds::render::SCamera theCamera;
+ theCamera.MarkDirty( qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty );
+ theCamera.m_Flags.SetOrthographic( true );
+ QT3DSVec2 theDimensions( (QT3DSF32)thePickDetails.m_Width, (QT3DSF32)thePickDetails.m_Height );
+ theCamera.CalculateGlobalVariables( render::NVRenderRectF( 0, 0, theDimensions.x,
+ theDimensions.y ), theDimensions );
+ QT3DSMat44 theVP;
+ theCamera.CalculateViewProjectionMatrix( theVP );
+ theRenderContext.SetCullingEnabled( false );
+ theRenderContext.SetBlendingEnabled( false );
+ theRenderContext.SetDepthTestEnabled( false );
+ theRenderContext.SetDepthWriteEnabled( false );
+ m_Context.GetRenderer().RenderQuad( theDimensions, theVP, *m_PickBuffer );
+ }*/
+ }
+}
+
+void STranslation::ResetWidgets()
+{
+ if (m_ScaleWidget)
+ m_ScaleWidget->SetAxisScale(QT3DSVec3(1, 1, 1));
+ if (m_RotationWidget)
+ m_RotationWidget->ClearRotationEdges();
+ m_CumulativeRotation = 0.0f;
+}
+
+void STranslation::DoPrepareForDrag(SNode *inSelectedNode)
+{
+ if (inSelectedNode == nullptr)
+ return;
+
+ m_MouseDownNode = *inSelectedNode;
+ m_MouseDownParentGlobalTransformInverse = Empty();
+ m_MouseDownParentRotationInverse = Empty();
+ m_MouseDownGlobalRotation = Empty();
+ // Orphan this node manually since it is a straight copy
+ m_MouseDownNode.m_Parent = nullptr;
+ m_MouseDownNode.m_FirstChild = nullptr;
+ m_MouseDownNode.m_NextSibling = nullptr;
+ SCamera *theCamera = m_Context.GetRenderer().GetCameraForNode(*inSelectedNode);
+ m_CumulativeRotation = 0.0f;
+ if (theCamera == nullptr)
+ return;
+ m_MouseDownCamera = *theCamera;
+ m_LastPathDragValue = Empty();
+}
+
+void STranslation::EndDrag()
+{
+ ResetWidgets();
+}
+
+bool STranslation::IsPathWidgetActive()
+{
+ qt3dsdm::TInstanceHandleList theHandles = m_Doc.GetSelectedValue().GetSelectedInstances();
+ for (size_t selectedIdx = 0, selectedEnd = theHandles.size(); selectedIdx < selectedEnd;
+ ++selectedIdx) {
+ qt3dsdm::Qt3DSDMInstanceHandle theInstance(theHandles[selectedIdx]);
+ if (m_Doc.GetDocumentReader().GetObjectTypeName(theInstance) == L"PathAnchorPoint")
+ theInstance = m_AssetGraph.GetParent(m_AssetGraph.GetParent(theInstance));
+ SGraphObjectTranslator *theTranslator = GetOrCreateTranslator(theInstance);
+ if (theTranslator && theTranslator->GetGraphObject().m_Type == GraphObjectTypes::Path)
+ return true;
+ }
+ return false;
+}
+
+inline qt3ds::render::SLayer *GetLayerForNode(const qt3ds::render::SNode &inNode)
+{
+ SNode *theNode;
+ // empty loop intentional
+ for (theNode = const_cast<SNode *>(&inNode);
+ theNode && theNode->m_Type != GraphObjectTypes::Layer; theNode = theNode->m_Parent) {
+ }
+ if (theNode && theNode->m_Type == GraphObjectTypes::Layer)
+ return static_cast<SLayer *>(theNode);
+ return nullptr;
+}
+
+void STranslation::RenderZoomRender(SZoomRender &inRender)
+{
+ SLayer *theLayer(inRender.m_Layer);
+ CPt thePoint(inRender.m_Point);
+ if (theLayer) {
+ qt3ds::render::IQt3DSRenderer &theRenderer(m_Context.GetRenderer());
+ Option<qt3ds::render::SLayerPickSetup> thePickSetup(
+ theRenderer.GetLayerPickSetup(*theLayer, QT3DSVec2((QT3DSF32)thePoint.x, (QT3DSF32)thePoint.y),
+ QSize(16, 16)));
+ if (thePickSetup.hasValue()) {
+ qt3ds::render::NVRenderContext &theRenderContext(m_Context.GetRenderContext());
+ theRenderContext.SetViewport(qt3ds::render::NVRenderRect(0, 0, 100, 100));
+ theRenderContext.SetScissorRect(qt3ds::render::NVRenderRect(0, 0, 100, 100));
+ theRenderContext.SetDepthWriteEnabled(true);
+ theRenderContext.SetScissorTestEnabled(true);
+ theRenderContext.SetClearColor(QT3DSVec4(.2f, .2f, .2f, 0.0f));
+ theRenderContext.Clear(qt3ds::render::NVRenderClearFlags(
+ qt3ds::render::NVRenderClearValues::Color | qt3ds::render::NVRenderClearValues::Depth));
+ theRenderer.RunLayerRender(*theLayer, thePickSetup->m_ViewProjection);
+ theRenderContext.SetScissorTestEnabled(false);
+ }
+ }
+}
+
+void STranslation::DrawBoundingBox(SNode &inNode, QT3DSVec3 inColor)
+{
+ qt3ds::NVBounds3 theBounds = inNode.GetBounds(m_Context.GetBufferManager(),
+ m_Context.GetPathManager(), true, this);
+ qt3ds::render::IRenderWidget &theBBoxWidget = qt3ds::render::IRenderWidget::CreateBoundingBoxWidget(
+ inNode, theBounds, inColor, m_Context.GetRenderer().GetPerFrameAllocator());
+ m_Context.GetRenderer().AddRenderWidget(theBBoxWidget);
+}
+
+void STranslation::drawPivot(SGraphObjectTranslator &inTranslator)
+{
+ if (GraphObjectTypes::IsNodeType(inTranslator.GetGraphObject().m_Type)) {
+ qt3ds::render::IRenderWidget &theAxisWidget = qt3ds::render::IRenderWidget::CreateAxisWidget(
+ static_cast<SNode &>(inTranslator.GetGraphObject()),
+ m_Context.GetRenderer().GetPerFrameAllocator());
+ m_Context.GetRenderer().AddRenderWidget(theAxisWidget);
+ } else {
+ QT3DS_ASSERT(false);
+ }
+}
+
+void STranslation::SetViewport(qt3ds::QT3DSF32 inWidth, qt3ds::QT3DSF32 inHeight)
+{
+ m_Viewport = QT3DSVec2(inWidth, inHeight);
+ if (m_EditCameraEnabled) {
+ // Update inner rect as it is used to calculate camera frustrum for visual aid widget
+ QSize theSize = g_StudioApp.GetCore()->GetStudioProjectSettings()->getPresentationSize();
+ m_InnerRect.m_Top = 0;
+ m_InnerRect.m_Bottom = theSize.height();
+ m_InnerRect.m_Left = 0;
+ m_InnerRect.m_Right = theSize.width();
+ }
+}
+
+Option<QT3DSU32> STranslation::PickWidget(CPt inMouseCoords, TranslationSelectMode::Enum,
+ qt3ds::widgets::IStudioWidgetBase &inWidget)
+{
+ SNode &theNode = inWidget.GetNode();
+ SGraphObjectTranslator *theWidgetTranslator =
+ theNode.m_UserData.DynamicCast<SGraphObjectTranslator>();
+ SLayer *theLayer = GetLayerForNode(theNode);
+ if (theLayer && theWidgetTranslator) {
+ Option<qt3ds::render::SLayerPickSetup> thePickSetup(
+ m_Context.GetRenderer().GetLayerPickSetup(
+ *theLayer, QT3DSVec2((QT3DSF32)inMouseCoords.x, (QT3DSF32)inMouseCoords.y),
+ QSize(4, 4)));
+ if (thePickSetup.hasValue()) {
+ qt3ds::render::NVRenderContext &theContext(m_Context.GetRenderContext());
+ qt3ds::render::NVRenderContextScopedProperty<qt3ds::render::NVRenderFrameBuffer *>
+ __currentrt(theContext, &qt3ds::render::NVRenderContext::GetRenderTarget,
+ &qt3ds::render::NVRenderContext::SetRenderTarget);
+ qt3ds::render::NVRenderFrameBuffer *theFBO =
+ m_Context.GetResourceManager().AllocateFrameBuffer();
+ const QT3DSU32 fboDims = 8;
+ if (!m_PickBuffer) {
+ m_PickBuffer = theContext.CreateTexture2D();
+ m_PickBuffer->SetTextureData(qt3ds::foundation::NVDataRef<qt3ds::QT3DSU8>(), 0, fboDims,
+ fboDims,
+ qt3ds::render::NVRenderTextureFormats::LuminanceAlpha8);
+ }
+ qt3ds::render::NVRenderRenderBuffer *theRenderBuffer =
+ m_Context.GetResourceManager().AllocateRenderBuffer(
+ fboDims, fboDims, qt3ds::render::NVRenderRenderBufferFormats::Depth16);
+ theFBO->Attach(qt3ds::render::NVRenderFrameBufferAttachments::Color0,
+ qt3ds::render::NVRenderTextureOrRenderBuffer(*m_PickBuffer));
+ theFBO->Attach(qt3ds::render::NVRenderFrameBufferAttachments::Depth,
+ qt3ds::render::NVRenderTextureOrRenderBuffer(*theRenderBuffer));
+ qt3ds::render::NVRenderRect theViewport(0, 0, fboDims, fboDims);
+ theContext.SetViewport(theViewport);
+ theContext.SetDepthWriteEnabled(true);
+ theContext.SetDepthTestEnabled(true);
+ theContext.SetScissorTestEnabled(false);
+ theContext.SetBlendingEnabled(false);
+ theContext.SetClearColor(QT3DSVec4(0, 0, 0, 0));
+ theContext.Clear(qt3ds::render::NVRenderClearFlags(
+ qt3ds::render::NVRenderClearValues::Color | qt3ds::render::NVRenderClearValues::Depth));
+ inWidget.RenderPick(thePickSetup->m_ProjectionPreMultiply, theContext,
+ QSize(4, 4));
+ // Now read the pixels back.
+ m_PixelBuffer.resize(fboDims * fboDims * 3);
+ theContext.ReadPixels(theViewport, qt3ds::render::NVRenderReadPixelFormats::RGB8,
+ m_PixelBuffer);
+ m_Context.GetResourceManager().Release(*theFBO);
+ m_Context.GetResourceManager().Release(*theRenderBuffer);
+ eastl::hash_map<QT3DSU32, QT3DSU32> tallies;
+ QT3DSU32 numPixels = fboDims * fboDims;
+ for (QT3DSU32 idx = 0; idx < numPixels; ++idx) {
+ qt3ds::QT3DSU16 theChannelAmount =
+ m_PixelBuffer[idx * 3] + (m_PixelBuffer[idx * 3 + 1] << 8);
+ if (theChannelAmount)
+ tallies.insert(eastl::make_pair(theChannelAmount, (QT3DSU32)0)).first->second += 1;
+ }
+ QT3DSU32 tallyMaxTally = 0;
+ QT3DSU32 tallyMaxIdx = 0;
+ for (eastl::hash_map<QT3DSU32, QT3DSU32>::iterator iter = tallies.begin(),
+ end = tallies.end();
+ iter != end; ++iter) {
+ if (iter->second > tallyMaxTally) {
+ tallyMaxTally = iter->second;
+ tallyMaxIdx = iter->first;
+ }
+ }
+ if (tallyMaxIdx > 0) {
+ return tallyMaxIdx;
+ }
+ }
+ }
+ return Empty();
+}
+
+SStudioPickValue STranslation::Pick(CPt inMouseCoords, TranslationSelectMode::Enum inSelectMode,
+ bool ignoreWidgets)
+{
+ bool requestRender = false;
+
+ if (!ignoreWidgets) {
+ if (m_Doc.GetDocumentReader().AreGuidesEditable()) {
+ qt3dsdm::TGuideHandleList theGuides = m_Doc.GetDocumentReader().GetGuides();
+ CPt renderSpacePt(inMouseCoords.x - (long)m_InnerRect.m_Left,
+ (long)GetViewportDimensions().y - inMouseCoords.y
+ - (long)m_InnerRect.m_Bottom);
+ for (size_t guideIdx = 0, guideEnd = theGuides.size();
+ guideIdx < guideEnd; ++guideIdx) {
+ qt3dsdm::SGuideInfo theGuideInfo =
+ m_Doc.GetDocumentReader().GetGuideInfo(theGuides[guideIdx]);
+ float width = (theGuideInfo.m_Width / 2.0f) + 2.0f;
+ switch (theGuideInfo.m_Direction) {
+ case qt3dsdm::GuideDirections::Horizontal:
+ if (fabs((float)renderSpacePt.y - theGuideInfo.m_Position) <= width)
+ return theGuides[guideIdx];
+ break;
+ case qt3dsdm::GuideDirections::Vertical:
+ if (fabs((float)renderSpacePt.x - theGuideInfo.m_Position) <= width)
+ return theGuides[guideIdx];
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (IsPathWidgetActive()) {
+ Option<QT3DSU32> picked = PickWidget(inMouseCoords, inSelectMode, *m_PathWidget);
+ if (picked.hasValue()) {
+ RequestRender();
+ DoPrepareForDrag(&m_PathWidget->GetNode());
+ return m_PathWidget->PickIndexToPickValue(*picked);
+ }
+ }
+ // Pick against the widget first if possible.
+ if (m_LastRenderedWidget && (m_LastRenderedWidget->GetNode().m_Flags.IsActive()
+ || m_LastRenderedWidget->GetNode().m_Type
+ == GraphObjectTypes::Light
+ || m_LastRenderedWidget->GetNode().m_Type
+ == GraphObjectTypes::Camera)) {
+ Option<QT3DSU32> picked = PickWidget(inMouseCoords, inSelectMode,
+ *m_LastRenderedWidget);
+ if (picked.hasValue()) {
+ RequestRender();
+ DoPrepareForDrag(&m_LastRenderedWidget->GetNode());
+ return m_LastRenderedWidget->PickIndexToPickValue(*picked);
+ }
+ }
+ }
+ // Pick against Lights and Cameras
+ // This doesn't use the color picker or renderer pick
+ float lastDist = 99999999999999.0f;
+ int lastIndex = -1;
+ for (int i = 0; i < int(m_editModeCamerasAndLights.size()); ++i) {
+ const QT3DSVec2 mouseCoords((QT3DSF32)inMouseCoords.x, (QT3DSF32)inMouseCoords.y);
+ float dist;
+ SGraphObject &object = m_editModeCamerasAndLights[i]->GetGraphObject();
+ m_VisualAidWidget->SetNode(static_cast<SNode *>(&object));
+ if (m_VisualAidWidget->pick(m_Context.GetRenderer().GetRenderWidgetContext(),
+ dist, GetViewportDimensions(), mouseCoords)) {
+ if (dist < lastDist) {
+ lastDist = dist;
+ lastIndex = i;
+ }
+ }
+ }
+
+ if (m_Scene && m_Scene->m_FirstChild) {
+ qt3ds::render::Qt3DSRenderPickResult thePickResult =
+ m_Context.GetRenderer().Pick(*m_Scene->m_FirstChild, GetViewportDimensions(),
+ QT3DSVec2((QT3DSF32)inMouseCoords.x, (QT3DSF32)inMouseCoords.y));
+ if (thePickResult.m_HitObject) {
+ const SGraphObject &theObject = *thePickResult.m_HitObject;
+
+ // check hit distance to cameras and lights
+ if (lastIndex != -1 && thePickResult.m_CameraDistanceSq > lastDist * lastDist) {
+ DoPrepareForDrag(static_cast<SNode *>(
+ &(m_editModeCamerasAndLights[lastIndex]->GetGraphObject())));
+ return m_editModeCamerasAndLights[lastIndex]->GetInstanceHandle();
+ }
+
+ if (theObject.m_Type == GraphObjectTypes::Model
+ || theObject.m_Type == GraphObjectTypes::Text
+ || theObject.m_Type == GraphObjectTypes::Path) {
+ const SNode &theTranslatorModel(static_cast<const SNode &>(theObject));
+ SGraphObjectTranslator *theTranslator =
+ theTranslatorModel.m_UserData.DynamicCast<SGraphObjectTranslator>();
+ const SNode *theModelPtr = &theTranslatorModel;
+ if (theTranslator->GetPossiblyAliasedInstanceHandle()
+ != theTranslator->GetInstanceHandle()) {
+ theTranslator =
+ GetOrCreateTranslator(theTranslator->GetPossiblyAliasedInstanceHandle());
+ theModelPtr =
+ static_cast<const SNode *>(&theTranslator->GetNonAliasedGraphObject());
+ }
+ Qt3DSDMInstanceHandle theActiveComponent =
+ m_Reader.GetComponentForSlide(m_Doc.GetActiveSlide());
+ if (inSelectMode == TranslationSelectMode::Group) {
+ // Bounce up the hierarchy till one of two conditions are met
+ // the parent is a layer or the our component is the active component
+ // but the parent's is not.
+ while (theTranslator && GraphObjectTypes::IsNodeType(
+ theTranslator->GetGraphObject().m_Type)) {
+ SNode *myNode = static_cast<SNode *>(&theTranslator->GetGraphObject());
+ if (myNode->m_Parent == nullptr) {
+ theTranslator = nullptr;
+ break;
+ }
+ SNode *parentNode = myNode->m_Parent;
+ SGraphObjectTranslator *theParentTranslator =
+ parentNode->m_UserData.DynamicCast<SGraphObjectTranslator>();
+ Qt3DSDMInstanceHandle myComponent =
+ m_Reader.GetAssociatedComponent(theTranslator->GetInstanceHandle());
+ Qt3DSDMInstanceHandle myParentComponent = m_Reader.GetAssociatedComponent(
+ theParentTranslator->GetInstanceHandle());
+ if (parentNode->m_Type == GraphObjectTypes::Layer) {
+ if (myParentComponent != theActiveComponent)
+ theTranslator = nullptr;
+ break;
+ }
+ if (myComponent == theActiveComponent
+ && myParentComponent != theActiveComponent)
+ break;
+ theTranslator = theParentTranslator;
+ }
+ } else {
+ // Bounce up until we get into the active component and then stop.
+ while (inSelectMode == TranslationSelectMode::Single && theTranslator
+ && GraphObjectTypes::IsNodeType(theTranslator->GetGraphObject().m_Type)
+ && m_Reader.GetAssociatedComponent(theTranslator->GetInstanceHandle())
+ != theActiveComponent) {
+ SNode *theNode = static_cast<SNode *>(&theTranslator->GetGraphObject());
+ theNode = theNode->m_Parent;
+ if (theNode && theNode->m_Type != GraphObjectTypes::Layer)
+ theTranslator =
+ theNode->m_UserData.DynamicCast<SGraphObjectTranslator>();
+ else
+ theTranslator = nullptr;
+ }
+ }
+
+ if (theTranslator) {
+ QT3DS_ASSERT(GraphObjectTypes::IsNodeType(theTranslator->GetGraphObject().m_Type));
+ DoPrepareForDrag(static_cast<SNode *>(&theTranslator->GetGraphObject()));
+ return theTranslator->GetInstanceHandle();
+ }
+ }
+ }
+ if (requestRender)
+ RequestRender();
+ }
+
+ if (lastIndex != -1) {
+ DoPrepareForDrag(static_cast<SNode *>(
+ &(m_editModeCamerasAndLights[lastIndex]->GetGraphObject())));
+ return m_editModeCamerasAndLights[lastIndex]->GetInstanceHandle();
+ }
+
+ return SStudioPickValue();
+}
+
+qt3ds::foundation::Option<qt3dsdm::SGuideInfo> STranslation::PickRulers(CPt inMouseCoords)
+{
+ CPt renderSpacePt(inMouseCoords.x, (long)GetViewportDimensions().y - inMouseCoords.y);
+ // If mouse is inside outer rect but outside inner rect.
+ if (m_OuterRect.Contains(renderSpacePt.x, renderSpacePt.y)
+ && !m_InnerRect.Contains(renderSpacePt.x, renderSpacePt.y)) {
+ std::shared_ptr<qt3dsdm::IGuideSystem> theGuideSystem =
+ m_StudioSystem.GetFullSystem()->GetCoreSystem()->GetGuideSystem();
+ if (renderSpacePt.x >= m_InnerRect.m_Left && renderSpacePt.x <= m_InnerRect.m_Right) {
+ return qt3dsdm::SGuideInfo((QT3DSF32)renderSpacePt.y - (QT3DSF32)m_InnerRect.m_Bottom,
+ qt3dsdm::GuideDirections::Horizontal);
+ } else if (renderSpacePt.y >= m_InnerRect.m_Bottom
+ && renderSpacePt.y <= m_InnerRect.m_Top) {
+ return qt3dsdm::SGuideInfo((QT3DSF32)renderSpacePt.x - (QT3DSF32)m_InnerRect.m_Left,
+ qt3dsdm::GuideDirections::Vertical);
+ }
+ }
+ return qt3ds::foundation::Option<qt3dsdm::SGuideInfo>();
+}
+
+QT3DSVec3 STranslation::GetIntendedPosition(qt3dsdm::Qt3DSDMInstanceHandle inInstance, CPt inPos)
+{
+ ClearDirtySet();
+ SGraphObjectTranslator *theTranslator = GetOrCreateTranslator(inInstance);
+ if (theTranslator == nullptr)
+ return QT3DSVec3(0, 0, 0);
+ if (GraphObjectTypes::IsNodeType(theTranslator->GetGraphObject().m_Type) == false)
+ return QT3DSVec3(0, 0, 0);
+ SNode *theNode = static_cast<SNode *>(&theTranslator->GetGraphObject());
+ SCamera *theCamera = m_Context.GetRenderer().GetCameraForNode(*theNode);
+ {
+ // Get the node's parent
+ Qt3DSDMInstanceHandle theParent = m_AssetGraph.GetParent(inInstance);
+ SGraphObjectTranslator *theParentTranslator = GetOrCreateTranslator(theParent);
+ if (theParentTranslator
+ && GraphObjectTypes::IsNodeType(theParentTranslator->GetGraphObject().m_Type))
+ theCamera = m_Context.GetRenderer().GetCameraForNode(
+ *static_cast<SNode *>(&theParentTranslator->GetGraphObject()));
+ }
+ if (theCamera == nullptr)
+ return QT3DSVec3(0, 0, 0);
+
+ QT3DSVec3 theGlobalPos(theNode->GetGlobalPos());
+ return m_Context.GetRenderer().UnprojectToPosition(*theCamera, theGlobalPos,
+ QT3DSVec2((QT3DSF32)inPos.x, (QT3DSF32)inPos.y));
+}
+
+static void CheckLockToAxis(QT3DSF32 &inXDistance, QT3DSF32 &inYDistance, bool inLockToAxis)
+{
+ if (inLockToAxis) {
+ if (fabs(inXDistance) > fabs(inYDistance))
+ inYDistance = 0;
+ else
+ inXDistance = 0;
+ }
+}
+
+void STranslation::ApplyPositionalChange(QT3DSVec3 inDiff, SNode &inNode,
+ CUpdateableDocumentEditor &inEditor)
+{
+ if (m_MouseDownParentGlobalTransformInverse.isEmpty()) {
+ if (inNode.m_Parent)
+ m_MouseDownParentGlobalTransformInverse =
+ inNode.m_Parent->m_GlobalTransform.getInverse();
+ else
+ m_MouseDownParentGlobalTransformInverse = QT3DSMat44::createIdentity();
+ }
+ QT3DSMat44 theGlobalTransform = m_MouseDownNode.m_GlobalTransform;
+ QT3DSMat44 theNewLocalTransform =
+ m_MouseDownParentGlobalTransformInverse.getValue() * theGlobalTransform;
+ QT3DSVec3 theOldPos = theNewLocalTransform.column3.getXYZ();
+ theOldPos.z *= -1;
+
+ theGlobalTransform.column3 += QT3DSVec4(inDiff, 0.0f);
+ theNewLocalTransform = m_MouseDownParentGlobalTransformInverse.getValue() * theGlobalTransform;
+ QT3DSVec3 thePos = theNewLocalTransform.column3.getXYZ();
+ thePos.z *= -1;
+
+ QT3DSVec3 theDiff = thePos - theOldPos;
+
+ SetPosition(m_MouseDownNode.m_Position + theDiff, inEditor);
+}
+
+void STranslation::TranslateSelectedInstanceAlongCameraDirection(
+ CPt inOriginalCoords, CPt inMouseCoords, CUpdateableDocumentEditor &inEditor)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+ SCamera *theCamera = m_Context.GetRenderer().GetCameraForNode(*theNode);
+ if (theCamera == nullptr)
+ return;
+ QT3DSF32 theYDistance = QT3DSF32(inMouseCoords.y - inOriginalCoords.y);
+ if (fabs(theYDistance) == 0)
+ return;
+
+ QT3DSF32 theMouseMultiplier = 1.0f / 2.0f;
+ QT3DSF32 theDistanceMultiplier = 1.0f + theYDistance * theMouseMultiplier;
+ QT3DSVec3 theCameraDir = m_MouseDownCamera.GetDirection();
+
+ QT3DSVec3 theDiff = theCameraDir * theDistanceMultiplier;
+ ApplyPositionalChange(theDiff, *theNode, inEditor);
+}
+
+void STranslation::TranslateSelectedInstance(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor, bool inLockToAxis)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+ qt3ds::render::IQt3DSRenderer &theRenderer(m_Context.GetRenderer());
+
+ QT3DSF32 theXDistance = QT3DSF32(inMouseCoords.x - inOriginalCoords.x);
+ QT3DSF32 theYDistance = QT3DSF32(inMouseCoords.y - inOriginalCoords.y);
+ if (fabs(theXDistance) == 0 && fabs(theYDistance) == 0)
+ return;
+
+ CheckLockToAxis(theXDistance, theYDistance, inLockToAxis);
+
+ inMouseCoords.x = inOriginalCoords.x + (long)theXDistance;
+ inMouseCoords.y = inOriginalCoords.y + (long)theYDistance;
+ QT3DSVec3 theNodeGlobal = m_MouseDownNode.GetGlobalPos();
+ QT3DSVec3 theOriginalPos = theRenderer.UnprojectToPosition(
+ *theNode, theNodeGlobal, QT3DSVec2((QT3DSF32)inOriginalCoords.x, (QT3DSF32)inOriginalCoords.y));
+ QT3DSVec3 theNewPos = theRenderer.UnprojectToPosition(
+ *theNode, theNodeGlobal, QT3DSVec2((QT3DSF32)inMouseCoords.x, (QT3DSF32)inMouseCoords.y));
+
+ QT3DSVec3 theDiff = theNewPos - theOriginalPos;
+ ApplyPositionalChange(theDiff, *theNode, inEditor);
+}
+
+void STranslation::ScaleSelectedInstanceZ(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+
+ // Scale scales uniformly and responds to mouse Y only.
+ QT3DSF32 theYDistance = (QT3DSF32)inMouseCoords.y - (QT3DSF32)inOriginalCoords.y;
+ if (fabs(theYDistance) == 0)
+ return;
+
+ QT3DSF32 theMouseMultiplier = 1.0f / 40.0f;
+ QT3DSF32 theScaleMultiplier = 1.0f + theYDistance * theMouseMultiplier;
+
+ SetScale(QT3DSVec3(m_MouseDownNode.m_Scale.x, m_MouseDownNode.m_Scale.y,
+ m_MouseDownNode.m_Scale.z * theScaleMultiplier),
+ inEditor);
+}
+
+void STranslation::ScaleSelectedInstance(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+
+ // Scale scales uniformly and responds to mouse Y only.
+ QT3DSF32 theYDistance = (QT3DSF32)inMouseCoords.y - (QT3DSF32)inOriginalCoords.y;
+ if (fabs(theYDistance) == 0)
+ return;
+
+ QT3DSF32 theMouseMultiplier = 1.0f / 40.0f;
+ QT3DSF32 theScaleMultiplier = 1.0f + theYDistance * theMouseMultiplier;
+
+ SetScale(m_MouseDownNode.m_Scale * theScaleMultiplier, inEditor);
+}
+
+void STranslation::CalculateNodeGlobalRotation(SNode &inNode)
+{
+ if (inNode.m_Parent)
+ CalculateNodeGlobalRotation(*inNode.m_Parent);
+ if (m_MouseDownParentRotationInverse.isEmpty()) {
+ m_MouseDownParentRotationInverse = QT3DSMat33::createIdentity();
+ m_MouseDownGlobalRotation = QT3DSMat33::createIdentity();
+ }
+
+ QT3DSMat44 localRotation;
+ inNode.CalculateRotationMatrix(localRotation);
+ if (inNode.m_Flags.IsLeftHanded())
+ SNode::FlipCoordinateSystem(localRotation);
+ QT3DSMat33 theRotation;
+ SNode::GetMatrixUpper3x3(theRotation, localRotation);
+
+ m_MouseDownParentRotationInverse = m_MouseDownGlobalRotation;
+ m_MouseDownGlobalRotation = m_MouseDownGlobalRotation.getValue() * theRotation;
+}
+
+void STranslation::ApplyRotationToSelectedInstance(const QT3DSQuat &inFinalRotation, SNode &inNode,
+ CUpdateableDocumentEditor &inEditor,
+ bool inIsMouseRelative)
+{
+ if (m_MouseDownParentRotationInverse.isEmpty()) {
+ CalculateNodeGlobalRotation(inNode);
+ m_MouseDownParentRotationInverse = m_MouseDownParentRotationInverse->getInverse();
+ }
+ QT3DSMat33 theRotationMatrix(inFinalRotation);
+
+ QT3DSMat33 theFinalGlobal = theRotationMatrix * m_MouseDownGlobalRotation.getValue();
+ QT3DSMat33 theLocalGlobal = m_MouseDownParentRotationInverse.getValue() * theFinalGlobal;
+ QT3DSVec3 theRotations = inNode.GetRotationVectorFromRotationMatrix(theLocalGlobal);
+ theRotations = qt3ds::render::SRotationHelper::ToNearestAngle(inNode.m_Rotation, theRotations,
+ inNode.m_RotationOrder);
+ SetRotation(theRotations, inEditor);
+ // Trackball rotation is relative to the previous mouse position.
+ // Rotation manipulator rotation is relative to only the original mouse down position
+ // so inIsMouseRelative is false for rotations that are relative to the original mouse down
+ // position.
+ if (inIsMouseRelative)
+ m_MouseDownGlobalRotation = theFinalGlobal;
+}
+
+void STranslation::RotateSelectedInstanceAboutCameraDirectionVector(
+ CPt inPreviousMouseCoords, CPt inMouseCoords, CUpdateableDocumentEditor &inEditor)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+ SCamera *theCamera = m_Context.GetRenderer().GetCameraForNode(*theNode);
+ if (theCamera == nullptr)
+ return;
+
+ QT3DSVec3 theDirection = m_MouseDownCamera.GetDirection();
+ QT3DSF32 theYDistance = (QT3DSF32)inMouseCoords.y - (QT3DSF32)inPreviousMouseCoords.y;
+ QT3DSQuat theYRotation(-1.0f * theYDistance * g_RotationScaleFactor, theDirection);
+
+ ApplyRotationToSelectedInstance(theYRotation, *theNode, inEditor);
+}
+
+// This method never feels right to me. It is difficult to apply it to a single axis (of course for
+// that you can use the inspector palette).
+void STranslation::RotateSelectedInstance(CPt inOriginalCoords, CPt inPreviousCoords,
+ CPt inMouseCoords, CUpdateableDocumentEditor &inEditor,
+ bool inLockToAxis)
+{
+ SNode *theNode = GetSelectedNode();
+ if (theNode == nullptr)
+ return;
+ SCamera *theCamera = m_Context.GetRenderer().GetCameraForNode(*theNode);
+ if (theCamera == nullptr)
+ return;
+ // We want to do a similar translation to what we did below but we need to calculate the
+ // parent's global rotation without scale included.
+
+ QT3DSF32 theXDistance = (QT3DSF32)inMouseCoords.x - (QT3DSF32)inPreviousCoords.x;
+ QT3DSF32 theYDistance = (QT3DSF32)inMouseCoords.y - (QT3DSF32)inPreviousCoords.y;
+ bool xIsZero = fabs(theXDistance) < .001f;
+ bool yIsZero = fabs(theYDistance) < .001f;
+ if (xIsZero && yIsZero)
+ return;
+
+ if (inLockToAxis) {
+ QT3DSF32 originalDistX = (QT3DSF32)inMouseCoords.x - (QT3DSF32)inOriginalCoords.x;
+ QT3DSF32 originalDistY = (QT3DSF32)inMouseCoords.y - (QT3DSF32)inOriginalCoords.y;
+ if (inLockToAxis) {
+ if (fabs(originalDistX) > fabs(originalDistY))
+ theYDistance = 0;
+ else
+ theXDistance = 0;
+ }
+ }
+
+ QT3DSVec3 theXAxis = m_MouseDownCamera.m_GlobalTransform.column0.getXYZ();
+ QT3DSVec3 theYAxis = m_MouseDownCamera.m_GlobalTransform.column1.getXYZ();
+
+ QT3DSVec3 theFinalAxis = theXDistance * theYAxis + theYDistance * theXAxis;
+ QT3DSF32 theTotalDistance = theFinalAxis.normalize();
+ QT3DSQuat theRotation(theTotalDistance * g_RotationScaleFactor / 2.0f, theFinalAxis);
+
+ ApplyRotationToSelectedInstance(theRotation, *theNode, inEditor);
+}
+
+inline void NiceAdd(QT3DSF32 &ioValue, QT3DSF32 inIncrement)
+{
+ QT3DSF32 temp = ioValue + inIncrement;
+ // Round to nearest .5
+ QT3DSF32 sign = temp >= 0 ? 1.0f : -1.0f;
+ QT3DSU32 largerValue = (QT3DSU32)(fabs(temp * 10.0f));
+
+ QT3DSU32 leftover = largerValue % 10;
+ // Round down to zero
+ largerValue -= leftover;
+ if (leftover < 2)
+ leftover = 0;
+ else if (leftover > 7) {
+ leftover = 0;
+ largerValue += 10;
+ } else
+ leftover = 5;
+ largerValue += leftover;
+
+ ioValue = sign * (QT3DSF32)largerValue / 10.0f;
+}
+
+static inline QT3DSVec3 GetAxis(QT3DSU32 inIndex, QT3DSMat33 &inMatrix)
+{
+ QT3DSVec3 retval(0, 0, 0);
+ switch (inIndex) {
+ case 0:
+ retval = inMatrix.column0;
+ break;
+ case 1:
+ retval = inMatrix.column1;
+ break;
+ case 2:
+ retval = inMatrix.column2;
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ retval.normalize();
+ return retval;
+}
+
+inline Option<QT3DSF32> GetScaleAlongAxis(const QT3DSVec3 &inAxis, const QT3DSVec3 &inObjToOriginal,
+ const QT3DSVec3 &inObjToCurrent)
+{
+ QT3DSF32 lhs = inAxis.dot(inObjToCurrent);
+ QT3DSF32 rhs = inAxis.dot(inObjToOriginal);
+ if (fabs(rhs) > .001f)
+ return lhs / rhs;
+ return Empty();
+}
+
+// Make a nice rotation from the incoming rotation
+static inline QT3DSF32 MakeNiceRotation(QT3DSF32 inAngle)
+{
+ TODEG(inAngle);
+ inAngle *= 10.0f;
+ QT3DSF32 sign = inAngle > 0.0f ? 1.0f : -1.0f;
+ // Attempt to ensure angle is prtty clean
+ QT3DSU32 clampedAngle = (QT3DSU32)(fabs(inAngle) + .5f);
+ QT3DSU32 leftover = clampedAngle % 10;
+ clampedAngle -= leftover;
+ if (leftover <= 2)
+ leftover = 0;
+ else if (leftover <= 7)
+ leftover = 5;
+ else
+ leftover = 10;
+ clampedAngle += leftover;
+ QT3DSF32 retval = (QT3DSF32)clampedAngle;
+ retval = (retval * sign) / 10.0f;
+ TORAD(retval);
+ return retval;
+}
+
+static inline QT3DSF32 ShortestAngleDifference(QT3DSF32 inCumulative, QT3DSF32 inNewTotal)
+{
+ QT3DSF32 diff = qt3ds::render::SRotationHelper::ToMinimalAngle(inNewTotal - inCumulative);
+ return inCumulative + diff;
+}
+
+Option<SDragPreparationResult>
+STranslation::PrepareWidgetDrag(qt3ds::widgets::StudioWidgetComponentIds::Enum inComponentId,
+ qt3ds::widgets::StudioWidgetTypes::Enum inWidgetId,
+ qt3ds::render::RenderWidgetModes::Enum inWidgetMode, SNode &inNode,
+ CPt inOriginalCoords, CPt inPreviousMouseCoords, CPt inMouseCoords)
+{
+ SDragPreparationResult retval;
+ retval.m_ComponentId = inComponentId;
+ retval.m_WidgetType = inWidgetId;
+ retval.m_WidgetMode = inWidgetMode;
+ qt3ds::render::IQt3DSRenderer &theRenderer(m_Context.GetRenderer());
+ retval.m_Renderer = &theRenderer;
+ retval.m_Node = &inNode;
+ retval.m_Layer = GetLayerForNode(inNode);
+ retval.m_Camera = theRenderer.GetCameraForNode(inNode);
+ QSize theUnsignedDimensions(m_Context.GetWindowDimensions());
+ QT3DSVec2 theWindowDimensions((QT3DSF32)theUnsignedDimensions.width(),
+ (QT3DSF32)theUnsignedDimensions.height());
+ if (retval.m_Camera == nullptr || retval.m_Layer == nullptr)
+ return Empty();
+
+ SCamera &theCamera(*retval.m_Camera);
+ SLayer &theLayer(*retval.m_Layer);
+ QT3DSVec2 theLayerOriginalCoords = m_Context.GetRenderer().GetLayerMouseCoords(
+ *retval.m_Layer, QT3DSVec2((QT3DSF32)inOriginalCoords.x, (QT3DSF32)inOriginalCoords.y),
+ theWindowDimensions, true);
+
+ QT3DSVec2 theLayerMouseCoords = m_Context.GetRenderer().GetLayerMouseCoords(
+ *retval.m_Layer, QT3DSVec2((QT3DSF32)inMouseCoords.x, (QT3DSF32)inMouseCoords.y),
+ theWindowDimensions, true);
+
+ QT3DSVec2 thePreviousLayerMouseCoords = m_Context.GetRenderer().GetLayerMouseCoords(
+ *retval.m_Layer, QT3DSVec2((QT3DSF32)inPreviousMouseCoords.x, (QT3DSF32)inPreviousMouseCoords.y),
+ theWindowDimensions, true);
+ QT3DSMat44 theGlobalTransform(QT3DSMat44::createIdentity());
+ if (inWidgetMode == qt3ds::render::RenderWidgetModes::Local) {
+ theGlobalTransform = m_MouseDownNode.m_GlobalTransform;
+ }
+ retval.m_GlobalTransform = theGlobalTransform;
+ QT3DSMat33 theNormalMat(theGlobalTransform.column0.getXYZ(), theGlobalTransform.column1.getXYZ(),
+ theGlobalTransform.column2.getXYZ());
+ theNormalMat = theNormalMat.getInverse().getTranspose();
+ retval.m_NormalMatrix = theNormalMat;
+ qt3ds::render::NVRenderRectF theLayerRect(theRenderer.GetLayerRect(theLayer));
+ SRay theOriginalRay =
+ theCamera.Unproject(theLayerOriginalCoords, theLayerRect, theWindowDimensions);
+ SRay theCurrentRay =
+ theCamera.Unproject(theLayerMouseCoords, theLayerRect, theWindowDimensions);
+ SRay thePreviousRay =
+ theCamera.Unproject(thePreviousLayerMouseCoords, theLayerRect, theWindowDimensions);
+ retval.m_OriginalRay = theOriginalRay;
+ retval.m_CurrentRay = theCurrentRay;
+ retval.m_PreviousRay = thePreviousRay;
+ QT3DSVec3 theAxis;
+ QT3DSVec3 thePlaneNormal;
+ bool isPlane = false;
+ QT3DSVec3 globalPos = inNode.GetGlobalPivot();
+ QT3DSVec3 camGlobalPos = theCamera.GetGlobalPos();
+ QT3DSVec3 theCamDirection;
+ retval.m_GlobalPos = globalPos;
+ retval.m_CameraGlobalPos = camGlobalPos;
+ if (theCamera.m_Flags.IsOrthographic())
+ theCamDirection = theCamera.GetDirection();
+ else {
+ theCamDirection = globalPos - camGlobalPos;
+ // The normal will be normalized below.
+ }
+ theCamDirection.normalize();
+ retval.m_CameraDirection = theCamDirection;
+ retval.m_AxisIndex = 0;
+ switch (inComponentId) {
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::XAxis:
+ theAxis = QT3DSVec3(1, 0, 0);
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::YAxis:
+ theAxis = QT3DSVec3(0, 1, 0);
+ retval.m_AxisIndex = 1;
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::ZAxis:
+ theAxis = QT3DSVec3(0, 0, -1);
+ retval.m_AxisIndex = 2;
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::XPlane:
+ thePlaneNormal = QT3DSVec3(1, 0, 0);
+ isPlane = true;
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::YPlane:
+ thePlaneNormal = QT3DSVec3(0, 1, 0);
+ isPlane = true;
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::ZPlane:
+ thePlaneNormal = QT3DSVec3(0, 0, -1);
+ isPlane = true;
+ break;
+ case qt3ds::widgets::StudioWidgetComponentIds::CameraPlane: {
+ isPlane = true;
+ thePlaneNormal = theCamDirection;
+ } break;
+ }
+ retval.m_IsPlane = isPlane;
+ if (inWidgetId == qt3ds::widgets::StudioWidgetTypes::Rotation) {
+ if (isPlane == false) {
+ theAxis = theNormalMat.transform(theAxis);
+ theAxis.normalize();
+ thePlaneNormal = theAxis;
+ retval.m_Plane = qt3ds::NVPlane(thePlaneNormal, -1.0f * thePlaneNormal.dot(globalPos));
+ } else {
+ if (inComponentId != qt3ds::widgets::StudioWidgetComponentIds::CameraPlane) {
+ thePlaneNormal = theNormalMat.transform(thePlaneNormal);
+ }
+ thePlaneNormal.normalize();
+ retval.m_Plane = qt3ds::NVPlane(thePlaneNormal, -1.0f * (thePlaneNormal.dot(globalPos)));
+ }
+ } else {
+ if (isPlane == false) {
+ theAxis = theNormalMat.transform(theAxis);
+ theAxis.normalize();
+ QT3DSVec3 theCameraToObj = globalPos - camGlobalPos;
+ QT3DSVec3 theTemp = theAxis.cross(theOriginalRay.m_Direction);
+ // When the axis is parallel to the camera, we can't drag meaningfully
+ if (theTemp.magnitudeSquared() < .05f) {
+ // Attempt to find a better axis by moving the object back towards the camera.
+ QT3DSF32 theSign = theCameraToObj.dot(theCamDirection) > 0.0 ? -1.0f : 1.0f;
+ QT3DSF32 theDistance = theCameraToObj.dot(theCamDirection);
+ QT3DSVec3 thePoint = globalPos + (theDistance * theSign) * theAxis;
+ // Check if we actually moved to right direction
+ QT3DSVec3 theNewCameraToObj = thePoint - camGlobalPos;
+ QT3DSF32 theNewDistance = theNewCameraToObj.dot(theCamDirection);
+ if (theNewDistance > theDistance)
+ thePoint = globalPos - (theDistance * theSign) * theAxis;
+
+ QT3DSVec3 theNewDir = thePoint - camGlobalPos;
+ theNewDir.normalize();
+ theTemp = theAxis.cross(theNewDir);
+ // Attempt again to find a better cross
+ if (theTemp.magnitudeSquared() < .05f)
+ return Empty();
+ }
+ thePlaneNormal = theTemp.cross(theAxis);
+ thePlaneNormal.normalize();
+ retval.m_Plane = qt3ds::NVPlane(thePlaneNormal, -1.0f * thePlaneNormal.dot(globalPos));
+ } else {
+ thePlaneNormal = theNormalMat.transform(thePlaneNormal);
+ thePlaneNormal.normalize();
+ retval.m_Plane = qt3ds::NVPlane(thePlaneNormal, -1.0f * (thePlaneNormal.dot(globalPos)));
+ }
+ }
+ retval.m_Axis = theAxis;
+ retval.m_OriginalPlaneCoords = theOriginalRay.Intersect(retval.m_Plane);
+ retval.m_CurrentPlaneCoords = theCurrentRay.Intersect(retval.m_Plane);
+ retval.m_PreviousPlaneCoords = thePreviousRay.Intersect(retval.m_Plane);
+ return retval;
+}
+
+void STranslation::PerformWidgetDrag(int inWidgetSubComponent, CPt inOriginalCoords,
+ CPt inPreviousMouseCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor)
+{
+ if (inWidgetSubComponent == 0 || m_LastRenderedWidget == nullptr) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+ Option<SDragPreparationResult> thePrepResult(PrepareWidgetDrag(
+ static_cast<qt3ds::widgets::StudioWidgetComponentIds::Enum>(inWidgetSubComponent),
+ m_LastRenderedWidget->GetWidgetType(), m_LastRenderedWidget->GetRenderWidgetMode(),
+ m_LastRenderedWidget->GetNode(), inOriginalCoords, inPreviousMouseCoords, inMouseCoords));
+ if (!thePrepResult.hasValue())
+ return;
+
+ Option<QT3DSVec3> theOriginalPlaneCoords(thePrepResult->m_OriginalPlaneCoords);
+ Option<QT3DSVec3> theCurrentPlaneCoords(thePrepResult->m_CurrentPlaneCoords);
+ QT3DSVec3 globalPos(thePrepResult->m_GlobalPos);
+ bool isPlane(thePrepResult->m_IsPlane);
+ QT3DSVec3 theAxis(thePrepResult->m_Axis);
+ QT3DSU32 axisIndex(thePrepResult->m_AxisIndex);
+ SNode *theNode(thePrepResult->m_Node);
+ SRay theCurrentRay(thePrepResult->m_CurrentRay);
+ SRay theOriginalRay(thePrepResult->m_OriginalRay);
+ QT3DSVec3 thePlaneNormal(thePrepResult->m_Plane.n);
+ QT3DSVec3 theCamDirection(thePrepResult->m_CameraDirection);
+ QT3DSVec3 camGlobalPos(thePrepResult->m_CameraGlobalPos);
+
+ switch (m_LastRenderedWidget->GetWidgetType()) {
+ default:
+ QT3DS_ASSERT(false);
+ return;
+ case qt3ds::widgets::StudioWidgetTypes::Scale: {
+ if (theOriginalPlaneCoords.hasValue() && theCurrentPlaneCoords.hasValue()) {
+ QT3DSVec3 objToOriginal = globalPos - *theOriginalPlaneCoords;
+ QT3DSVec3 objToCurrent = globalPos - *theCurrentPlaneCoords;
+ QT3DSVec3 theScaleMultiplier(1, 1, 1);
+ if (!isPlane) {
+ // Ensure that we only have a scale vector in the direction of the axis.
+ objToOriginal = theAxis * (theAxis.dot(objToOriginal));
+ objToCurrent = theAxis * (theAxis.dot(objToCurrent));
+ QT3DSF32 objToOriginalDot = theAxis.dot(objToOriginal);
+ if (fabs(objToOriginalDot) > .001f)
+ theScaleMultiplier[axisIndex] =
+ theAxis.dot(objToCurrent) / theAxis.dot(objToOriginal);
+ else
+ theScaleMultiplier[axisIndex] = 0.0f;
+ }
+
+ QT3DSMat33 theNodeAxisMatrix(theNode->m_GlobalTransform.column0.getXYZ(),
+ theNode->m_GlobalTransform.column1.getXYZ(),
+ theNode->m_GlobalTransform.column2.getXYZ());
+ theNodeAxisMatrix = theNodeAxisMatrix.getInverse().getTranspose();
+ QT3DSVec3 &theLocalXAxis(theNodeAxisMatrix.column0);
+ QT3DSVec3 &theLocalYAxis(theNodeAxisMatrix.column1);
+ QT3DSVec3 &theLocalZAxis(theNodeAxisMatrix.column2);
+ theLocalXAxis.normalize();
+ theLocalYAxis.normalize();
+ theLocalZAxis.normalize();
+
+ Option<QT3DSF32> theXScale = GetScaleAlongAxis(theLocalXAxis, objToOriginal, objToCurrent);
+ Option<QT3DSF32> theYScale = GetScaleAlongAxis(theLocalYAxis, objToOriginal, objToCurrent);
+ Option<QT3DSF32> theZScale = GetScaleAlongAxis(theLocalZAxis, objToOriginal, objToCurrent);
+ QT3DSVec3 theScale = m_MouseDownNode.m_Scale;
+ if (theXScale.isEmpty() && theYScale.isEmpty() && theZScale.isEmpty()) {
+ theScale = QT3DSVec3(0, 0, 0);
+ } else {
+ if (theXScale.hasValue())
+ theScale.x *= *theXScale;
+ if (theYScale.hasValue())
+ theScale.y *= *theYScale;
+ if (theZScale.hasValue())
+ theScale.z *= *theZScale;
+ }
+ m_LastRenderedWidget->SetAxisScale(theScaleMultiplier);
+ SetScale(theScale, inEditor);
+ }
+ } break;
+ case qt3ds::widgets::StudioWidgetTypes::Rotation: {
+ QT3DSF32 theIntersectionCosine = theOriginalRay.m_Direction.dot(thePlaneNormal);
+ QT3DSVec3 objToPrevious;
+ QT3DSVec3 objToCurrent;
+ if (fabs(theIntersectionCosine) > .08f) {
+ if (!theOriginalPlaneCoords.hasValue() || !theCurrentPlaneCoords.hasValue())
+ return;
+ objToPrevious = globalPos - *theOriginalPlaneCoords;
+ objToCurrent = globalPos - *theCurrentPlaneCoords;
+ objToPrevious.normalize();
+ QT3DSF32 lineLen = objToCurrent.normalize();
+
+ if (!thePrepResult->m_Camera->m_Flags.IsOrthographic()) {
+ // Flip object vector if coords are behind camera to get the correct angle
+ QT3DSVec3 camToCurrent = camGlobalPos - *theCurrentPlaneCoords;
+ if (camToCurrent.dot(theCamDirection) >= 0.0f) {
+ objToCurrent = -objToCurrent;
+ // Negative line length seems counterintuitive, but since the end point is
+ // behind the camera, it results in correct line when rendered
+ lineLen = -lineLen;
+ }
+ }
+
+ QT3DSF32 cosAngle = objToPrevious.dot(objToCurrent);
+ QT3DSVec3 theCrossProd = objToPrevious.cross(objToCurrent);
+ QT3DSF32 theCrossPlaneDot = theCrossProd.dot(thePlaneNormal);
+ QT3DSF32 angleSign = theCrossPlaneDot >= 0.0f ? 1.0f : -1.0f;
+ QT3DSF32 angleRad = acos(cosAngle) * angleSign;
+ angleRad = MakeNiceRotation(angleRad);
+ QT3DSQuat theRotation(angleRad, thePlaneNormal);
+
+ m_CumulativeRotation = ShortestAngleDifference(m_CumulativeRotation, angleRad);
+ m_LastRenderedWidget->SetRotationEdges(-1.0f * objToPrevious, thePlaneNormal,
+ m_CumulativeRotation, lineLen);
+ ApplyRotationToSelectedInstance(theRotation, *theNode, inEditor, false);
+ }
+ // In this case we are viewing the plane of rotation pretty much dead on, so we need to
+ // assume the camera and the object are both in the plane of rotation. In this case we
+ // *sort* of need to do trackball rotation but force it to one plane of rotation.
+ else {
+ // Setup a plane 600 units away from the camera and have the gadget run from there.
+
+ // This keeps rotation consistent.
+ QT3DSVec3 thePlaneSpot = theCamDirection * -600.0f + camGlobalPos;
+ qt3ds::NVPlane theCameraPlaneAtObject(theCamDirection,
+ -1.0f * (theCamDirection.dot(thePlaneSpot)));
+ theCurrentPlaneCoords = theCurrentRay.Intersect(theCameraPlaneAtObject);
+ theOriginalPlaneCoords = theOriginalRay.Intersect(theCameraPlaneAtObject);
+ QT3DSVec3 theChangeVector = *theOriginalPlaneCoords - *theCurrentPlaneCoords;
+ // Remove any component of the change vector that doesn't lie in the plane.
+ theChangeVector =
+ theChangeVector - theChangeVector.dot(thePlaneNormal) * thePlaneNormal;
+ QT3DSF32 theDistance = theChangeVector.normalize();
+ // We want about 90* per 200 units in imaginary 600-units-from-camera space.
+ QT3DSF32 theScaleFactor = 1.0f / 200.0f;
+ if (thePrepResult->m_Camera->m_Flags.IsOrthographic())
+ theScaleFactor = 1.0f / 100.0f;
+
+ QT3DSF32 theDeg = 90.0f * theDistance * theScaleFactor;
+ // Check the sign of the angle.
+ QT3DSVec3 theCurrentIsectDir = camGlobalPos - *theCurrentPlaneCoords;
+ QT3DSVec3 thePreviousIsectDir = camGlobalPos - *theOriginalPlaneCoords;
+ QT3DSVec3 theCrossProd = theCurrentIsectDir.cross(thePreviousIsectDir);
+ QT3DSF32 theAngleSign = theCrossProd.dot(thePlaneNormal) > 0.0f ? 1.0f : -1.0f;
+ theDeg *= theAngleSign;
+ QT3DSF32 theRad(theDeg);
+ TORAD(theRad);
+ theRad = MakeNiceRotation(theRad);
+ QT3DSQuat theRotation(theRad, thePlaneNormal);
+ ApplyRotationToSelectedInstance(theRotation, *theNode, inEditor, false);
+ }
+ } break;
+ case qt3ds::widgets::StudioWidgetTypes::Translation: {
+ if (theOriginalPlaneCoords.hasValue() && theCurrentPlaneCoords.hasValue()) {
+ QT3DSVec3 theDiff = *theCurrentPlaneCoords - *theOriginalPlaneCoords;
+ if (isPlane) {
+ ApplyPositionalChange(theDiff, *theNode, inEditor);
+ } else {
+ QT3DSVec3 theMovement = theAxis * theAxis.dot(theDiff);
+ ApplyPositionalChange(theMovement, *theNode, inEditor);
+ }
+ }
+ } break;
+ }
+}
+
+static float RoundToNearest(float inValue, float inMin, float inMax, float inRound)
+{
+ float half = (inMin + inMax) / 2.0f;
+ inValue -= half;
+ inValue = inRound * floor(inValue / inRound + .5f);
+ inValue += half;
+ inValue -= inMin;
+ return inValue;
+}
+
+void STranslation::PerformGuideDrag(Qt3DSDMGuideHandle inGuide, CPt inPoint,
+ CUpdateableDocumentEditor &inEditor)
+{
+ qt3dsdm::SGuideInfo theInfo = m_Doc.GetDocumentReader().GetGuideInfo(inGuide);
+ CPt renderSpacePt(inPoint.x, (long)GetViewportDimensions().y - inPoint.y);
+ switch (theInfo.m_Direction) {
+ case qt3dsdm::GuideDirections::Horizontal:
+ theInfo.m_Position = RoundToNearest((float)renderSpacePt.y, (float)m_InnerRect.m_Bottom,
+ (float)m_InnerRect.m_Top, 10.0f);
+ break;
+ case qt3dsdm::GuideDirections::Vertical:
+ theInfo.m_Position = RoundToNearest((float)renderSpacePt.x, (float)m_InnerRect.m_Left,
+ (float)m_InnerRect.m_Right, 10.0f);
+ break;
+ default:
+ QT3DS_ASSERT(FALSE);
+ break;
+ break;
+ }
+ inEditor.EnsureEditor(QObject::tr("Drag Guide"), __FILE__, __LINE__).UpdateGuide(inGuide,
+ theInfo);
+ inEditor.FireImmediateRefresh(qt3dsdm::Qt3DSDMInstanceHandle());
+}
+
+void STranslation::CheckGuideInPresentationRect(Qt3DSDMGuideHandle inGuide,
+ CUpdateableDocumentEditor &inEditor)
+{
+ qt3dsdm::SGuideInfo theInfo = m_Doc.GetDocumentReader().GetGuideInfo(inGuide);
+ bool inPresentation = false;
+ QT3DSF32 presHeight = (QT3DSF32)m_InnerRect.m_Top - (QT3DSF32)m_InnerRect.m_Bottom;
+ QT3DSF32 presWidth = (QT3DSF32)m_InnerRect.m_Right - (QT3DSF32)m_InnerRect.m_Left;
+ switch (theInfo.m_Direction) {
+ case qt3dsdm::GuideDirections::Horizontal:
+ inPresentation = 0.0f <= theInfo.m_Position && presHeight >= theInfo.m_Position;
+ break;
+ case qt3dsdm::GuideDirections::Vertical:
+ inPresentation = 0.0f <= theInfo.m_Position && presWidth >= theInfo.m_Position;
+ break;
+ default:
+ break;
+ }
+
+ if (!inPresentation)
+ inEditor.EnsureEditor(QObject::tr("Delete Guide"), __FILE__, __LINE__).DeleteGuide(inGuide);
+}
+
+void STranslation::PerformPathDrag(qt3ds::studio::SPathPick &inPathPick, CPt inOriginalCoords,
+ CPt inPreviousMouseCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor)
+{
+ Option<SDragPreparationResult> thePrepResult(PrepareWidgetDrag(
+ qt3ds::widgets::StudioWidgetComponentIds::ZPlane,
+ qt3ds::widgets::StudioWidgetTypes::Translation, qt3ds::render::RenderWidgetModes::Local,
+ m_PathWidget->GetNode(), inOriginalCoords, inPreviousMouseCoords, inMouseCoords));
+ if (!thePrepResult.hasValue())
+ return;
+
+ Option<QT3DSVec3> theOriginalPlaneCoords(thePrepResult->m_OriginalPlaneCoords);
+ Option<QT3DSVec3> theCurrentPlaneCoords(thePrepResult->m_CurrentPlaneCoords);
+ Option<QT3DSVec3> thePreviousPlaneCoords(thePrepResult->m_PreviousPlaneCoords);
+ if (theOriginalPlaneCoords.hasValue() && theCurrentPlaneCoords.hasValue()) {
+ QT3DSVec3 theGlobalDiff = *theCurrentPlaneCoords - *theOriginalPlaneCoords;
+ QT3DSMat44 theGlobalInverse = thePrepResult->m_GlobalTransform.getInverse();
+ QT3DSVec3 theCurrentPos = theGlobalInverse.transform(*theCurrentPlaneCoords);
+ QT3DSVec3 theOldPos = theGlobalInverse.transform(*theOriginalPlaneCoords);
+ QT3DSVec3 theDiff = theCurrentPos - theOldPos;
+ // Now find the anchor point; nontrivial.
+ SPathTranslator *theTranslator =
+ reinterpret_cast<SPathTranslator *>(thePrepResult->m_Node->m_UserData.m_UserData);
+ qt3dsdm::Qt3DSDMInstanceHandle thePathHandle = theTranslator->GetInstanceHandle();
+ qt3dsdm::Qt3DSDMInstanceHandle theAnchorHandle = GetAnchorPoint(inPathPick);
+
+ if (theAnchorHandle.Valid()) {
+ qt3dsdm::Qt3DSDMPropertyHandle thePosProperty =
+ m_ObjectDefinitions.m_PathAnchorPoint.m_Position.m_Property;
+ qt3dsdm::Qt3DSDMPropertyHandle theAngleProperty =
+ m_ObjectDefinitions.m_PathAnchorPoint.m_IncomingAngle.m_Property;
+ qt3dsdm::Qt3DSDMPropertyHandle theIncomingDistanceProperty =
+ m_ObjectDefinitions.m_PathAnchorPoint.m_IncomingDistance.m_Property;
+ qt3dsdm::Qt3DSDMPropertyHandle theOutgoingDistanceProperty =
+ m_ObjectDefinitions.m_PathAnchorPoint.m_OutgoingDistance.m_Property;
+
+ IDocumentReader &theReader(m_Doc.GetDocumentReader());
+ SFloat2 anchorPos =
+ *theReader.GetTypedInstancePropertyValue<SFloat2>(theAnchorHandle, thePosProperty);
+ QT3DSVec2 anchorPosVec = QT3DSVec2(anchorPos[0], anchorPos[1]);
+ if (m_LastPathDragValue.hasValue() == false) {
+ SPathAnchorDragInitialValue initialValue;
+ initialValue.m_Position = anchorPosVec;
+ initialValue.m_IncomingAngle = theReader.GetTypedInstancePropertyValue<float>(
+ theAnchorHandle, theAngleProperty);
+ initialValue.m_IncomingDistance = theReader.GetTypedInstancePropertyValue<float>(
+ theAnchorHandle, theIncomingDistanceProperty);
+ initialValue.m_OutgoingDistance = theReader.GetTypedInstancePropertyValue<float>(
+ theAnchorHandle, theOutgoingDistanceProperty);
+ m_LastPathDragValue = initialValue;
+ }
+ SPathAnchorDragInitialValue &lastValue(*m_LastPathDragValue);
+ QT3DSVec2 theCurrentValue;
+ switch (inPathPick.m_Property) {
+ case SPathPick::Anchor:
+ theCurrentValue = lastValue.m_Position;
+ break;
+ case SPathPick::IncomingControl:
+ theCurrentValue = qt3ds::render::IPathManagerCore::GetControlPointFromAngleDistance(
+ lastValue.m_Position, lastValue.m_IncomingAngle, lastValue.m_IncomingDistance);
+ break;
+ case SPathPick::OutgoingControl:
+ theCurrentValue = qt3ds::render::IPathManagerCore::GetControlPointFromAngleDistance(
+ lastValue.m_Position, lastValue.m_IncomingAngle + 180.0f,
+ lastValue.m_OutgoingDistance);
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ theCurrentValue[0] += theDiff.x;
+ theCurrentValue[1] += theDiff.y;
+ Q3DStudio::IDocumentEditor &theEditor = inEditor.EnsureEditor(
+ QObject::tr("Anchor Point Drag"), __FILE__, __LINE__);
+ switch (inPathPick.m_Property) {
+ case SPathPick::Anchor:
+ theEditor.SetInstancePropertyValue(theAnchorHandle, thePosProperty,
+ SFloat2(theCurrentValue.x, theCurrentValue.y));
+ break;
+ case SPathPick::IncomingControl: {
+ QT3DSVec2 angleDistance =
+ qt3ds::render::IPathManagerCore::GetAngleDistanceFromControlPoint(
+ anchorPosVec, theCurrentValue);
+ float angleDiff = angleDistance.x - lastValue.m_IncomingAngle;
+ float minimalDiff =
+ qRadiansToDegrees(qt3ds::render::SRotationHelper::ToMinimalAngle(
+ qDegreesToRadians(angleDiff)));
+ float newAngle = lastValue.m_IncomingAngle + minimalDiff;
+ theEditor.SetInstancePropertyValue(theAnchorHandle, theAngleProperty, newAngle);
+ theEditor.SetInstancePropertyValue(theAnchorHandle, theIncomingDistanceProperty,
+ angleDistance.y);
+ } break;
+ case SPathPick::OutgoingControl: {
+ QT3DSVec2 angleDistance =
+ qt3ds::render::IPathManagerCore::GetAngleDistanceFromControlPoint(
+ anchorPosVec, theCurrentValue);
+ angleDistance.x += 180.0f;
+ float angleDiff = angleDistance.x - lastValue.m_IncomingAngle;
+ float minimalDiff =
+ qRadiansToDegrees(qt3ds::render::SRotationHelper::ToMinimalAngle(
+ qDegreesToRadians(angleDiff)));
+ float newAngle = lastValue.m_IncomingAngle + minimalDiff;
+ theEditor.SetInstancePropertyValue(theAnchorHandle, theAngleProperty, newAngle);
+ theEditor.SetInstancePropertyValue(theAnchorHandle, theOutgoingDistanceProperty,
+ angleDistance.y);
+ } break;
+ }
+
+ inEditor.FireImmediateRefresh(m_AssetGraph.GetParent(theAnchorHandle));
+ }
+ }
+}
+
+SNode *STranslation::GetEditCameraLayer()
+{
+ if (m_EditCameraLayerTranslator)
+ return static_cast<SNode *>(&m_EditCameraLayerTranslator->GetGraphObject());
+ return nullptr;
+}
+
+PickTargetAreas::Enum STranslation::GetPickArea(CPt inPoint)
+{
+ qt3ds::render::NVRenderRectF displayViewport = m_Context.GetDisplayViewport();
+ QT3DSVec2 thePickPoint((QT3DSF32)inPoint.x,
+ m_Context.GetWindowDimensions().height() - (QT3DSF32)inPoint.y);
+ QT3DSF32 left = displayViewport.m_X;
+ QT3DSF32 right = displayViewport.m_X + displayViewport.m_Width;
+ QT3DSF32 top = displayViewport.m_Y + displayViewport.m_Height;
+ QT3DSF32 bottom = displayViewport.m_Y;
+ if (thePickPoint.x < left || thePickPoint.x > right || thePickPoint.y < bottom
+ || thePickPoint.y > top)
+ return PickTargetAreas::Matte;
+ return PickTargetAreas::Presentation;
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.h b/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.h
new file mode 100644
index 00000000..2d3e965a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioRendererTranslation.h
@@ -0,0 +1,714 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_RENDERER_TRANSLATION_H
+#define QT3DS_STUDIO_RENDERER_TRANSLATION_H
+#pragma once
+#include "StudioRendererImpl.h"
+#include "Qt3DSRenderLayer.h"
+#include "Qt3DSRenderer.h"
+#include "StudioWidget.h"
+#include "render/Qt3DSRenderTexture2D.h"
+#include "foundation/AutoDeallocatorAllocator.h"
+#include "foundation/FastAllocator.h"
+#include "StudioPickValues.h"
+#include "Qt3DSDMGuides.h"
+#include "PathWidget.h"
+#include "StudioPreferences.h"
+#include "StudioGradientWidget.h"
+#include "StudioVisualAidWidget.h"
+
+namespace qt3ds {
+namespace studio {
+ struct SGraphObjectTranslator;
+ extern QT3DSU32 g_GraphObjectTranslatorTag;
+ inline void InitializePointerTags(IStringTable &) { g_GraphObjectTranslatorTag = 0x0088BEEF; }
+}
+}
+namespace qt3ds {
+namespace render {
+ template <>
+ struct SPointerTag<qt3ds::studio::SGraphObjectTranslator>
+ {
+ static QT3DSU32 GetTag() { return qt3ds::studio::g_GraphObjectTranslatorTag; }
+ };
+}
+}
+
+namespace qt3ds {
+namespace studio {
+
+ typedef std::shared_ptr<qt3dsdm::ISignalConnection> TSignalConnection;
+
+ struct STranslation;
+
+ struct SGraphObjectTranslator
+ {
+ protected:
+ qt3dsdm::Qt3DSDMInstanceHandle m_InstanceHandle;
+
+ public:
+ // This will never be null. The reason it is a pointer is because
+ // alias translators need to switch which graph object they point to
+ qt3dsdm::Qt3DSDMInstanceHandle m_AliasInstanceHandle;
+ SGraphObject *m_GraphObject;
+ QT3DSU32 m_DirtyIndex;
+ SGraphObjectTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance, SGraphObject &inObj)
+ : m_InstanceHandle(inInstance)
+ , m_GraphObject(&inObj)
+ , m_DirtyIndex(QT3DS_MAX_U32)
+ {
+ m_GraphObject->m_UserData = qt3ds::render::STaggedPointer(this);
+ }
+ // The destructors will not be called at this time for most of the objects
+ // but they will be released.
+ virtual ~SGraphObjectTranslator() {}
+ // Push new data into the UIC render graph.
+ virtual void PushTranslation(STranslation &inTranslatorContext);
+ virtual void AfterRenderGraphIsBuilt(STranslation &) {}
+ virtual void SetActive(bool inActive) = 0;
+ virtual void ClearChildren() = 0;
+ virtual void AppendChild(SGraphObject &inChild) = 0;
+ virtual void ResetEffect() {}
+ virtual SGraphObject &GetGraphObject() { return *m_GraphObject; }
+ virtual SGraphObject &GetNonAliasedGraphObject() { return *m_GraphObject; }
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstanceHandle() { return m_InstanceHandle; }
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetSceneGraphInstanceHandle()
+ {
+ return m_InstanceHandle;
+ }
+ virtual qt3dsdm::Qt3DSDMInstanceHandle GetPossiblyAliasedInstanceHandle()
+ {
+ if (m_AliasInstanceHandle.Valid())
+ return m_AliasInstanceHandle;
+ return GetInstanceHandle();
+ }
+ };
+
+ struct STranslatorGetDirty
+ {
+ QT3DSU32 operator()(const SGraphObjectTranslator &inTrans) const
+ {
+ return inTrans.m_DirtyIndex;
+ }
+ };
+ struct STranslatorSetDirty
+ {
+ void operator()(SGraphObjectTranslator &inTrans, QT3DSU32 idx) const
+ {
+ inTrans.m_DirtyIndex = idx;
+ }
+ };
+
+ typedef InvasiveSet<SGraphObjectTranslator, STranslatorGetDirty, STranslatorSetDirty>
+ TTranslatorDirtySet;
+
+ struct TranslationSelectMode
+ {
+ enum Enum {
+ Group = 0,
+ Single = 1,
+ NestedComponentSingle,
+ };
+ };
+
+ struct EditCameraTypes
+ {
+ enum Enum {
+ SceneCamera = 0,
+ Perspective,
+ Orthographic,
+ Directional,
+ };
+ };
+
+ const QT3DSF32 g_EditCameraFOV = 45.0f;
+ const QT3DSF32 g_RotationScaleFactor = 2.0f * QT3DSF32(M_PI) / 180.0f;
+
+ struct SEditCameraPersistentInformation
+ {
+ QT3DSVec3 m_Position;
+ QT3DSVec3 m_Direction;
+ QT3DSF32 m_ViewRadius;
+ EditCameraTypes::Enum m_CameraType;
+ NVReal m_xRotation = 0.0f;
+ NVReal m_yRotation = 0.0f;
+ SEditCameraPersistentInformation()
+ : m_Position(0, 0, 0)
+ , m_Direction(0, 0, 0)
+ , m_ViewRadius(600)
+ , m_CameraType(EditCameraTypes::Perspective)
+ {
+ }
+
+ void ApplyToCamera(SCamera &inCamera, QT3DSVec2 inViewport)
+ {
+ // Setup shared default values.
+ inCamera.m_ClipFar = 2000000.0f;
+ inCamera.m_ClipNear = 1.0f;
+ if (m_CameraType == EditCameraTypes::Perspective) {
+ inCamera.m_FOV = g_EditCameraFOV;
+ TORAD(inCamera.m_FOV);
+ inCamera.m_Flags.SetOrthographic(false);
+ } else {
+ inCamera.m_Flags.SetOrthographic(true);
+ }
+
+ // The goal is to setup a global transform that
+ QT3DSMat44 thePivotMatrix = QT3DSMat44::createIdentity();
+ thePivotMatrix.column3.x = m_Position.x;
+ thePivotMatrix.column3.y = m_Position.y;
+ thePivotMatrix.column3.z = m_Position.z;
+ QT3DSMat44 theGlobalTransform = thePivotMatrix;
+
+ QT3DSVec3 theUpDir(0, 1, 0);
+ if (m_CameraType == EditCameraTypes::Directional) {
+ QT3DSF32 theTestLen = m_Direction.cross(theUpDir).magnitudeSquared();
+ if (theTestLen < .01f)
+ theUpDir = QT3DSVec3(0, 0, 1) * m_Direction.dot(QT3DSVec3(0, 1, 0));
+ theUpDir.normalize();
+ }
+
+ QT3DSMat33 theLookAtMatrix = inCamera.GetLookAtMatrix(theUpDir, m_Direction);
+ QT3DSMat44 theRotationTransform =
+ QT3DSMat44(theLookAtMatrix.column0, theLookAtMatrix.column1,
+ theLookAtMatrix.column2, QT3DSVec3(0, 0, 0));
+
+ if (m_CameraType != EditCameraTypes::Directional) {
+ theRotationTransform.rotate(-m_xRotation, QT3DSVec3(0.0f, 1.0f, 0.0f));
+ theRotationTransform.rotate(m_yRotation, QT3DSVec3(1.0f, 0.0f, 0.0f));
+ }
+
+ // The view radius dictates the zoom.
+ QT3DSF32 theZoom = 1.0f;
+ if (inCamera.m_Flags.IsOrthographic()) {
+ QT3DSF32 theViewport = qMin(inViewport.x, inViewport.y);
+ theZoom = (m_ViewRadius * 2.0f) / theViewport;
+ } else {
+ // We know the hypotenuse is 600.
+ // So if we want to zoom the scene, we do this.
+ theZoom = m_ViewRadius / (sinf(inCamera.m_FOV / 2.0f) * 600.f);
+ }
+ QT3DSMat44 theScaleMatrix = QT3DSMat44(QT3DSVec4(theZoom, theZoom, theZoom, 1));
+ QT3DSMat44 thePositionMatrix = QT3DSMat44::createIdentity();
+ thePositionMatrix.column3.x = m_Position.x;
+ thePositionMatrix.column3.y = m_Position.y;
+ thePositionMatrix.column3.z = m_Position.z + 600;
+ theGlobalTransform = theGlobalTransform * theRotationTransform;
+ theGlobalTransform = theGlobalTransform * theScaleMatrix;
+ theGlobalTransform = theGlobalTransform * thePivotMatrix.getInverse();
+ theGlobalTransform = theGlobalTransform * thePositionMatrix;
+ // This works because the camera has no hierarchy.
+ inCamera.m_LocalTransform = theGlobalTransform;
+ inCamera.m_Flags.SetTransformDirty(false);
+ inCamera.MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformNotDirty);
+ }
+
+ bool IsOrthographic() const { return m_CameraType != EditCameraTypes::Perspective; }
+
+ bool SupportsRotation() const { return m_CameraType != EditCameraTypes::Directional; }
+ };
+ struct MovementTypes
+ {
+ enum Enum {
+ Unknown = 0,
+ Translate,
+ TranslateAlongCameraDirection,
+ Scale,
+ ScaleZ,
+ Rotation,
+ RotationAboutCameraDirection,
+ };
+ };
+
+ struct SEditCameraLayerTranslator;
+ struct SZoomRender
+ {
+ CPt m_Point;
+ qt3ds::render::SLayer *m_Layer;
+ SZoomRender(CPt inPoint, qt3ds::render::SLayer *inLayer)
+ : m_Point(inPoint)
+ , m_Layer(inLayer)
+ {
+ }
+ SZoomRender()
+ : m_Layer(nullptr)
+ {
+ }
+ };
+
+ struct PickTargetAreas
+ {
+ enum Enum {
+ Presentation,
+ Matte,
+ };
+ };
+
+ struct SRulerRect
+ {
+ QT3DSI32 m_Left;
+ QT3DSI32 m_Top;
+ QT3DSI32 m_Right;
+ QT3DSI32 m_Bottom;
+ SRulerRect()
+ : m_Left(0)
+ , m_Top(0)
+ , m_Right(0)
+ , m_Bottom(0)
+ {
+ }
+ SRulerRect(QT3DSI32 l, QT3DSI32 t, QT3DSI32 r, QT3DSI32 b)
+ : m_Left(l)
+ , m_Top(t)
+ , m_Right(r)
+ , m_Bottom(b)
+ {
+ }
+
+ bool Contains(QT3DSI32 x, QT3DSI32 y) const
+ {
+ return x >= m_Left && x <= m_Right && y >= m_Bottom && y <= m_Top;
+ }
+
+ bool isNull() const
+ {
+ return m_Left == 0 && m_Top == 0 && m_Right == 0 && m_Bottom == 0;
+ }
+ };
+
+ struct SDragPreparationResult
+ {
+ qt3ds::render::IQt3DSRenderer *m_Renderer;
+ SNode *m_Node;
+ SLayer *m_Layer;
+ SCamera *m_Camera;
+ qt3ds::render::NVPlane m_Plane;
+ QT3DSVec3 m_GlobalPos;
+ QT3DSVec3 m_CameraGlobalPos;
+ QT3DSVec3 m_CameraDirection;
+ QT3DSVec3 m_Axis;
+ QT3DSMat44 m_GlobalTransform;
+ QT3DSMat33 m_NormalMatrix;
+ QT3DSU32 m_AxisIndex;
+ qt3ds::widgets::StudioWidgetComponentIds::Enum m_ComponentId;
+ qt3ds::widgets::StudioWidgetTypes::Enum m_WidgetType;
+ qt3ds::render::RenderWidgetModes::Enum m_WidgetMode;
+ SRay m_OriginalRay;
+ SRay m_CurrentRay;
+ SRay m_PreviousRay;
+ Option<QT3DSVec3> m_OriginalPlaneCoords;
+ Option<QT3DSVec3> m_CurrentPlaneCoords;
+ Option<QT3DSVec3> m_PreviousPlaneCoords;
+ bool m_IsPlane;
+ SDragPreparationResult() {}
+ };
+
+ struct SPathAnchorDragInitialValue
+ {
+ QT3DSVec2 m_Position;
+ float m_IncomingAngle;
+ float m_IncomingDistance;
+ float m_OutgoingDistance;
+ SPathAnchorDragInitialValue() {}
+ };
+
+ struct STranslation : public qt3ds::render::IQt3DSRenderNodeFilter
+ {
+ typedef eastl::pair<qt3dsdm::Qt3DSDMInstanceHandle, SGraphObjectTranslator *>
+ THandleTranslatorPair;
+ typedef eastl::vector<THandleTranslatorPair> THandleTranslatorPairList;
+ // Now that we have aliases, one instance handle can map to several translators. One
+ // translator, however, only
+ // maps to one instance handle.
+ typedef nvhash_map<qt3dsdm::Qt3DSDMInstanceHandle, THandleTranslatorPairList, eastl::hash<int>>
+ TInstanceToTranslatorMap;
+ IStudioRenderer &m_Renderer;
+ IQt3DSRenderContext &m_Context;
+ CDoc &m_Doc;
+ IDocumentReader &m_Reader;
+ SComposerObjectDefinitions &m_ObjectDefinitions;
+ qt3dsdm::CStudioSystem &m_StudioSystem;
+ qt3dsdm::CStudioFullSystem &m_FullSystem;
+ Q3DStudio::CGraph &m_AssetGraph;
+
+ // allocator for scene graph and translators
+ qt3ds::foundation::SSAutoDeallocatorAllocator m_Allocator;
+ // All translator related containers must come after the allocator
+ TInstanceToTranslatorMap m_TranslatorMap;
+ TTranslatorDirtySet m_DirtySet;
+ qt3ds::render::SPresentation m_Presentation;
+ qt3ds::render::SScene *m_Scene;
+ Q3DStudio::CGraphIterator m_GraphIterator;
+ nvvector<TSignalConnection> m_SignalConnections;
+ QT3DSI32 m_ComponentSecondsDepth;
+ SNode m_MouseDownNode;
+ SCamera m_MouseDownCamera;
+ Option<QT3DSMat44> m_MouseDownParentGlobalTransformInverse;
+ Option<QT3DSMat33> m_MouseDownParentRotationInverse;
+ Option<QT3DSMat33> m_MouseDownGlobalRotation;
+ QT3DSI32 m_KeyRepeat;
+ bool m_EditCameraEnabled;
+ bool m_EditLightEnabled;
+ SEditCameraPersistentInformation m_EditCameraInfo;
+ SCamera m_EditCamera;
+ SLight m_EditLight;
+ QT3DSVec2 m_Viewport;
+ SEditCameraLayerTranslator *m_EditCameraLayerTranslator;
+ Option<SZoomRender> m_ZoomRender;
+ NVScopedRefCounted<qt3ds::widgets::IStudioWidget> m_TranslationWidget;
+ NVScopedRefCounted<qt3ds::widgets::IStudioWidget> m_RotationWidget;
+ NVScopedRefCounted<qt3ds::widgets::IStudioWidget> m_ScaleWidget;
+ NVScopedRefCounted<qt3ds::widgets::IStudioWidget> m_LastRenderedWidget;
+ NVScopedRefCounted<qt3ds::widgets::SGradientWidget> m_GradientWidget;
+ NVScopedRefCounted<qt3ds::widgets::SVisualAidWidget> m_VisualAidWidget;
+
+ NVScopedRefCounted<qt3ds::widgets::IPathWidget> m_PathWidget;
+ NVScopedRefCounted<qt3ds::render::NVRenderTexture2D> m_PickBuffer;
+ Option<SPathAnchorDragInitialValue> m_LastPathDragValue;
+ nvvector<qt3ds::QT3DSU8> m_PixelBuffer;
+ nvvector<SGraphObjectTranslator *> m_editModeCamerasAndLights;
+ QT3DSF32 m_CumulativeRotation;
+ eastl::vector<qt3ds::render::SPGGraphObject *> m_GuideContainer;
+ qt3ds::foundation::SFastAllocator<> m_GuideAllocator;
+ // The rects are maintained from last render because the render context
+ // doesn't guarantee the rects it returns are valid outside of begin/end render calls.
+ SRulerRect m_OuterRect;
+ SRulerRect m_InnerRect; // presentation rect.
+
+ QT3DSVec4 m_rectColor;
+ QT3DSVec4 m_lineColor;
+ QT3DSVec4 m_guideColor;
+ QT3DSVec4 m_selectedGuideColor;
+ QT3DSVec4 m_guideFillColor;
+ QT3DSVec4 m_selectedGuideFillColor;
+
+ const float m_overlayPreviewSize = 600.0f;
+
+ NVScopedRefCounted<qt3ds::render::NVRenderTexture2D> m_previewTexture;
+ NVScopedRefCounted<qt3ds::render::NVRenderFrameBuffer> m_previewFbo;
+ NVScopedRefCounted<qt3ds::render::NVRenderRenderBuffer> m_previewRenderBuffer;
+ QT3DSVec2 m_previewFboDimensions;
+
+ STranslation(IStudioRenderer &inRenderer, IQt3DSRenderContext &inContext);
+ void MarkBeginComponentSeconds(qt3dsdm::Qt3DSDMSlideHandle) { ++m_ComponentSecondsDepth; }
+
+ void MarkComponentSeconds(qt3dsdm::Qt3DSDMSlideHandle)
+ {
+ m_ComponentSecondsDepth = qMax(0, m_ComponentSecondsDepth - 1);
+ if (m_ComponentSecondsDepth == 0)
+ RequestRender();
+ }
+
+ void ReleaseTranslation(Q3DStudio::TIdentifier inInstance);
+
+ void MarkGraphInstanceDirty(Q3DStudio::TIdentifier inInstance,
+ Q3DStudio::TIdentifier /*inParent*/)
+ {
+ MarkDirty(inInstance);
+ }
+
+ void MarkDirty(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+
+ void DoMarkDirty(qt3dsdm::Qt3DSDMInstanceHandle inInstance) {MarkDirty(inInstance);}
+
+ void MarkDirty(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, long inInstanceCount)
+ {
+ for (long idx = 0; idx < inInstanceCount; ++idx)
+ MarkDirty(inInstance[idx]);
+ }
+
+ void DrawBoundingBox(SNode &inNode, QT3DSVec3 inColor);
+
+ void DrawChildBoundingBoxes(SNode &inNode)
+ {
+ ::CColor color = CStudioPreferences::GetGroupBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+ for (SNode *theChild = inNode.m_FirstChild; theChild;
+ theChild = theChild->m_NextSibling) {
+ if (IncludeNode(*theChild))
+ DrawBoundingBox(*theChild, colorVec);
+ }
+ }
+
+ void DrawGroupBoundingBoxes(SGraphObjectTranslator &inTranslator)
+ {
+ SNode &theNode = static_cast<SNode &>(inTranslator.GetGraphObject());
+ if (theNode.m_FirstChild) {
+ ::CColor color = CStudioPreferences::GetGroupBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+ DrawBoundingBox(theNode, colorVec);
+ if (inTranslator.GetGraphObject().m_Type != GraphObjectTypes::Layer)
+ DrawChildBoundingBoxes(theNode);
+ }
+ }
+
+ void DrawNonGroupBoundingBoxes(SGraphObjectTranslator &inTranslator)
+ {
+ SNode &theNode = static_cast<SNode &>(inTranslator.GetGraphObject());
+ if (inTranslator.GetGraphObject().m_Type != GraphObjectTypes::Layer) {
+ ::CColor color = CStudioPreferences::GetSingleBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+ DrawBoundingBox(theNode, colorVec);
+ DrawChildBoundingBoxes(theNode);
+ } else {
+ ::CColor color = CStudioPreferences::GetSingleBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+ m_Context.GetRenderer().RenderLayerRect(
+ static_cast<SLayer &>(inTranslator.GetGraphObject()), colorVec);
+ }
+ }
+
+ void drawPivot(SGraphObjectTranslator &inTranslator);
+
+ void SetViewport(QT3DSF32 inWidth, QT3DSF32 inHeight);
+
+ QT3DSVec2 GetViewportDimensions() const { return m_Viewport; }
+ QT3DSVec2 GetPreviewViewportDimensions() const;
+ QT3DSVec2 GetOverlayPreviewDimensions() const;
+ qt3ds::render::NVRenderRect GetPreviewViewport() const;
+ qt3ds::render::NVRenderRect GetOverlayPreviewViewport() const;
+ bool hasRoomForOverlayPreview() const;
+
+ void ClearDirtySet()
+ {
+ // The dirty set may be modified while this operation is taking place in the case of
+ // alias nodes.
+ for (qt3ds::QT3DSU32 idx = 0; idx < (qt3ds::QT3DSU32)m_DirtySet.size(); ++idx) {
+ if (m_Reader.IsInstance(m_DirtySet[idx]->GetInstanceHandle()))
+ m_DirtySet[idx]->PushTranslation(*this);
+ }
+ m_DirtySet.clear();
+ }
+ // We build the render graph every time we render. This may seem wasteful
+ void BuildRenderGraph(qt3dsdm::Qt3DSDMInstanceHandle inParent, bool scenePreviewPass,
+ Qt3DSDMInstanceHandle inAliasHandle
+ = qt3dsdm::Qt3DSDMInstanceHandle());
+ void BuildRenderGraph(SGraphObjectTranslator &inParent, bool scenePreviewPass,
+ qt3dsdm::Qt3DSDMInstanceHandle inAliasHandle
+ = qt3dsdm::Qt3DSDMInstanceHandle());
+ void
+ DeactivateScan(SGraphObjectTranslator &inParent,
+ qt3dsdm::Qt3DSDMInstanceHandle inAliasHandle = qt3dsdm::Qt3DSDMInstanceHandle());
+ void PreRender(bool scenePreviewPass);
+ void Render(int inWidgetId, bool inDrawGuides, bool scenePreviewPass,
+ bool overlayPreview);
+ void EndRender();
+ void DoPrepareForDrag(SNode *inSelectedNode);
+ void ResetWidgets();
+ void EndDrag();
+ bool IsPathWidgetActive();
+
+ void PrepareForDrag() { DoPrepareForDrag(GetSelectedNode()); }
+
+ SStudioPickValue Pick(CPt inMouseCoords, TranslationSelectMode::Enum inSelectMode,
+ bool ignoreWidgets = false);
+ Option<QT3DSU32> PickWidget(CPt inMouseCoords, TranslationSelectMode::Enum inSelectMode,
+ qt3ds::widgets::IStudioWidgetBase &inWidget);
+
+ qt3ds::foundation::Option<qt3dsdm::SGuideInfo> PickRulers(CPt inMouseCoords);
+
+ SNode *GetSelectedNode()
+ {
+ qt3dsdm::Qt3DSDMInstanceHandle theHandle = m_Doc.GetSelectedInstance();
+ SGraphObjectTranslator *theTranslator = GetOrCreateTranslator(theHandle);
+ if (theTranslator
+ && GraphObjectTypes::IsNodeType(theTranslator->GetGraphObject().m_Type))
+ return static_cast<SNode *>(&theTranslator->GetGraphObject());
+ return nullptr;
+ }
+ static inline SFloat3 ToDataModel(const QT3DSVec3 &inValue)
+ {
+ return SFloat3(inValue.x, inValue.y, inValue.z);
+ }
+
+ static inline SFloat3 ToDataModelRotation(const QT3DSVec3 &inValue)
+ {
+ SFloat3 retval = ToDataModel(inValue);
+ TODEG(retval.m_Floats[0]);
+ TODEG(retval.m_Floats[1]);
+ TODEG(retval.m_Floats[2]);
+ return retval;
+ }
+
+ void SetPosition(const QT3DSVec3 &inPosition, CUpdateableDocumentEditor &inEditor)
+ {
+ inEditor.EnsureEditor(QObject::tr("Set Position"), __FILE__, __LINE__)
+ .SetInstancePropertyValue(m_Doc.GetSelectedInstance(),
+ m_ObjectDefinitions.m_Node.m_Position,
+ ToDataModel(inPosition));
+ inEditor.FireImmediateRefresh(m_Doc.GetSelectedInstance());
+ }
+ void SetRotation(const QT3DSVec3 &inRotation, CUpdateableDocumentEditor &inEditor)
+ {
+ inEditor.EnsureEditor(QObject::tr("Set Rotation"), __FILE__, __LINE__)
+ .SetInstancePropertyValue(m_Doc.GetSelectedInstance(),
+ m_ObjectDefinitions.m_Node.m_Rotation,
+ ToDataModelRotation(inRotation));
+ inEditor.FireImmediateRefresh(m_Doc.GetSelectedInstance());
+ }
+ void SetScale(const QT3DSVec3 &inScale, CUpdateableDocumentEditor &inEditor)
+ {
+ inEditor.EnsureEditor(QObject::tr("Set Scale"), __FILE__, __LINE__)
+ .SetInstancePropertyValue(m_Doc.GetSelectedInstance(),
+ m_ObjectDefinitions.m_Node.m_Scale, ToDataModel(inScale));
+ inEditor.FireImmediateRefresh(m_Doc.GetSelectedInstance());
+ }
+
+ QT3DSVec3 GetIntendedPosition(qt3dsdm::Qt3DSDMInstanceHandle inInstance, CPt inPos);
+
+ void ApplyPositionalChange(QT3DSVec3 inDiff, SNode &inNode,
+ CUpdateableDocumentEditor &inEditor);
+
+ void TranslateSelectedInstanceAlongCameraDirection(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ void TranslateSelectedInstance(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor, bool inLockToAxis);
+
+ void ScaleSelectedInstanceZ(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ void ScaleSelectedInstance(CPt inOriginalCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ void CalculateNodeGlobalRotation(SNode &inNode);
+
+ void ApplyRotationToSelectedInstance(const QT3DSQuat &inFinalRotation, SNode &inNode,
+ CUpdateableDocumentEditor &inEditor,
+ bool inIsMouseRelative = true);
+
+ void RotateSelectedInstanceAboutCameraDirectionVector(CPt inPreviousMouseCoords,
+ CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ // This method never feels right to me. It is difficult to apply it to a single axis (of
+ // course for that
+ // you can use the inspector palette).
+ void RotateSelectedInstance(CPt inOriginalCoords, CPt inPreviousMouseCoords,
+ CPt inMouseCoords, CUpdateableDocumentEditor &inEditor,
+ bool inLockToAxis);
+
+ Option<SDragPreparationResult>
+ PrepareWidgetDrag(qt3ds::widgets::StudioWidgetComponentIds::Enum inComponentId,
+ qt3ds::widgets::StudioWidgetTypes::Enum inWidgetId,
+ qt3ds::render::RenderWidgetModes::Enum inWidgetMode, SNode &inNode,
+ CPt inOriginalCoords, CPt inPreviousMouseCoords, CPt inMouseCoords);
+
+ void PerformWidgetDrag(int inWidgetSubComponent, CPt inOriginalCoords,
+ CPt inPreviousMouseCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ void PerformGuideDrag(Qt3DSDMGuideHandle inGuide, CPt inPoint,
+ CUpdateableDocumentEditor &inEditor);
+ void CheckGuideInPresentationRect(Qt3DSDMGuideHandle inGuide,
+ CUpdateableDocumentEditor &inEditor);
+
+ void PerformPathDrag(qt3ds::studio::SPathPick &inPathPick, CPt inOriginalCoords,
+ CPt inPreviousMouseCoords, CPt inMouseCoords,
+ CUpdateableDocumentEditor &inEditor);
+
+ void RequestRender()
+ {
+ if (m_ComponentSecondsDepth == 0)
+ m_Renderer.RequestRender();
+ }
+
+ void RenderZoomRender(SZoomRender &inRender);
+
+ // IQt3DSRenderNodeFilter
+ bool IncludeNode(const SNode &inNode) override;
+
+ PickTargetAreas::Enum GetPickArea(CPt inPoint);
+
+ SNode *GetEditCameraLayer();
+
+ void ReleaseEffect(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ // Create a new translator for this type. Do not add to any maps or anything else.
+ SGraphObjectTranslator *CreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ // Returns the canonical translator for a given instance or creates a new translator if none
+ // exist.
+ SGraphObjectTranslator *GetOrCreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ // Create a new aliased translator for this type.
+ SGraphObjectTranslator *GetOrCreateTranslator(qt3dsdm::Qt3DSDMInstanceHandle inInstance,
+ qt3dsdm::Qt3DSDMInstanceHandle inAliasInstance);
+ THandleTranslatorPairList &
+ GetTranslatorsForInstance(qt3dsdm::Qt3DSDMInstanceHandle inInstance);
+ qt3dsdm::Qt3DSDMInstanceHandle GetAnchorPoint(SPathPick &inPick);
+ qt3dsdm::Qt3DSDMInstanceHandle GetAnchorPoint(QT3DSU32 inAnchorIndex);
+ };
+
+ struct SDisableUseClearColor
+ {
+ SGraphObjectTranslator *m_SceneTranslator;
+ bool m_PreviousUseClearColor;
+ bool m_DisableUseClearColor;
+
+ SDisableUseClearColor(STranslation &inTranslation, bool disableUseClearColor)
+ : m_SceneTranslator(nullptr)
+ , m_PreviousUseClearColor(false)
+ , m_DisableUseClearColor(disableUseClearColor)
+ {
+ if (m_DisableUseClearColor) {
+ TIdentifier theRoot = inTranslation.m_AssetGraph.GetRoot(0);
+ m_SceneTranslator = inTranslation.GetOrCreateTranslator(theRoot);
+ if (m_SceneTranslator) {
+ SScene &theScene = static_cast<SScene &>(m_SceneTranslator->GetGraphObject());
+ m_PreviousUseClearColor = theScene.m_UseClearColor;
+ SetUseClearColor(false);
+ }
+ }
+ }
+
+ ~SDisableUseClearColor()
+ {
+ if (m_DisableUseClearColor) {
+ SetUseClearColor(m_PreviousUseClearColor);
+ }
+ }
+
+ void SetUseClearColor(bool inUseClearColor)
+ {
+ if (m_SceneTranslator) {
+ SScene &theScene = static_cast<SScene &>(m_SceneTranslator->GetGraphObject());
+ theScene.m_UseClearColor = inUseClearColor;
+ }
+ }
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioRotationWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioRotationWidget.cpp
new file mode 100644
index 00000000..f3e06cd6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioRotationWidget.cpp
@@ -0,0 +1,436 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioWidgetImpl.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderVertexBuffer.h"
+#include "Qt3DSRenderNode.h"
+#include "foundation/Qt3DSContainers.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+#include "Qt3DSRenderCamera.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+
+using namespace qt3ds::widgets;
+
+namespace {
+
+struct SRotationWidget : public SStudioWidgetImpl<StudioWidgetTypes::Rotation>
+{
+ typedef SStudioWidgetImpl<StudioWidgetTypes::Rotation> TBase;
+ NVRenderInputAssembler *m_XAxis;
+ NVRenderInputAssembler *m_YAxis;
+ NVRenderInputAssembler *m_ZAxis;
+ NVRenderInputAssembler *m_CameraAxis;
+ // We use a rect to clear the Z buffer.
+ NVRenderInputAssembler *m_CameraRect;
+
+ NVRenderShaderProgram *m_ZClearShader;
+
+ volatile QT3DSI32 mRefCount;
+
+ SRotationWidget(NVAllocatorCallback &inAlloc)
+ : TBase(inAlloc)
+ , m_XAxis(nullptr)
+ , m_YAxis(nullptr)
+ , m_ZAxis(nullptr)
+ , m_CameraAxis(nullptr)
+ , m_CameraRect(nullptr)
+ , m_ZClearShader(nullptr)
+ , mRefCount(0)
+ {
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator)
+
+ NVRenderInputAssembler *CreateRing(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, QT3DSVec3 inDirection,
+ QT3DSF32 inInnerRadius, QT3DSF32 inOuterRadius, QT3DSF32 inRingColor,
+ const char *inRingName)
+ {
+ QT3DS_ASSERT(inInnerRadius <= inOuterRadius);
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inRingName);
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval) {
+ return retval;
+ }
+
+ TResultVecType theVertexData(m_Allocator, "SRotationWidget::theVertexData");
+
+ QT3DSI32 numSubDivisions = 50;
+ QT3DSF32 arcRad = 360.0f / (QT3DSF32)numSubDivisions;
+ TORAD(arcRad);
+ QT3DSVec3 tempCross = inDirection.cross(QT3DSVec3(0, 1, 0));
+ if (tempCross.magnitudeSquared() < .05f)
+ tempCross = inDirection.cross(QT3DSVec3(1, 0, 0));
+
+ QT3DSVec3 upDir = inDirection.cross(tempCross);
+ QT3DSVec3 leftDir = upDir.cross(inDirection);
+ upDir.normalize();
+ leftDir.normalize();
+
+ QT3DSF32 ringWidth = inOuterRadius - inInnerRadius;
+ QT3DSF32 ringHalfWidth = ringWidth / 2.0f;
+ QT3DSF32 middleRadius = inInnerRadius + ringHalfWidth;
+
+ for (QT3DSI32 idx = 0, numLooper = numSubDivisions; idx < numLooper; ++idx) {
+ QT3DSF32 startDeg = idx * 360.0f / numSubDivisions;
+ QT3DSF32 endDeg = (idx + 1) * 360.0f / numSubDivisions;
+ QT3DSF32 startRad(startDeg);
+ QT3DSF32 endRad(endDeg);
+ TORAD(startRad);
+ TORAD(endRad);
+ QT3DSF32 startSin = NVSin(startRad);
+ QT3DSF32 endSin = NVSin(endRad);
+ QT3DSF32 startCos = NVCos(startRad);
+ QT3DSF32 endCos = NVCos(endRad);
+
+ QT3DSVec3 startDir = startSin * upDir + startCos * leftDir;
+ QT3DSVec3 endDir = endSin * upDir + endCos * leftDir;
+
+ QT3DSVec3 discStart = startDir * inInnerRadius;
+ QT3DSVec3 discEnd = endDir * inInnerRadius;
+ QT3DSVec3 ringStart = startDir * inOuterRadius;
+ QT3DSVec3 ringEnd = endDir * inOuterRadius;
+
+ QT3DSVec3 middleStart = startDir * (middleRadius);
+ QT3DSVec3 middleEnd = endDir * (middleRadius);
+ QT3DSVec3 middleTopLeft = middleStart + inDirection * ringHalfWidth;
+ QT3DSVec3 middleTopRight = middleStart - inDirection * ringHalfWidth;
+ QT3DSVec3 middleBottomLeft = middleEnd + inDirection * ringHalfWidth;
+ QT3DSVec3 middleBottomRight = middleEnd - inDirection * ringHalfWidth;
+
+ // Now two tris for the ring
+ theVertexData.push_back(QT3DSVec4(discStart, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringStart, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(discEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(discStart, inRingColor));
+ // Two tris for the ring that is perpendicular to the viewer
+ theVertexData.push_back(QT3DSVec4(middleTopLeft, inRingColor));
+ theVertexData.push_back(QT3DSVec4(middleTopRight, inRingColor));
+ theVertexData.push_back(QT3DSVec4(middleBottomRight, inRingColor));
+ theVertexData.push_back(QT3DSVec4(middleBottomRight, inRingColor));
+ theVertexData.push_back(QT3DSVec4(middleBottomLeft, inRingColor));
+ theVertexData.push_back(QT3DSVec4(middleTopLeft, inRingColor));
+ }
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ IStudioWidget::GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+ }
+
+ NVRenderInputAssembler *CreateRect(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, QT3DSVec3 inDirection,
+ QT3DSF32 inHalfWidth, const char *inItemName)
+ {
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inItemName);
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval) {
+ return retval;
+ }
+
+ QT3DSVec3 tempCross = inDirection.cross(QT3DSVec3(0, 1, 0));
+ if (tempCross.magnitudeSquared() < .05f)
+ tempCross = inDirection.cross(QT3DSVec3(1, 0, 0));
+
+ QT3DSVec3 upDir = inDirection.cross(tempCross);
+ QT3DSVec3 leftDir = upDir.cross(inDirection);
+
+ TResultVecType theVertexData(m_Allocator, "SRotationWidget::theVertexData");
+
+ theVertexData.push_back(QT3DSVec4(upDir * inHalfWidth, 0.0f));
+ theVertexData.push_back(QT3DSVec4(leftDir * inHalfWidth, 0.0f));
+ theVertexData.push_back(QT3DSVec4(upDir * -1.0f * inHalfWidth, 0.0f));
+
+ theVertexData.push_back(QT3DSVec4(upDir * -1.0f * inHalfWidth, 0.0f));
+ theVertexData.push_back(QT3DSVec4(leftDir * -1.0f * inHalfWidth, 0.0f));
+ theVertexData.push_back(QT3DSVec4(upDir * inHalfWidth, 0.0f));
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ IStudioWidget::GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+ return retval;
+ }
+
+ NVRenderShaderProgram *CreateZClearShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext,
+ const char *inItemName)
+ {
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inItemName);
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(theItemName);
+ if (retval) {
+ return retval;
+ }
+
+ qt3ds::render::IShaderProgramGenerator &theGenerator(inWidgetContext.GetProgramGenerator());
+ theGenerator.BeginProgram();
+ qt3ds::render::IShaderStageGenerator &theVertexGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Vertex));
+ qt3ds::render::IShaderStageGenerator &theFragmentGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Fragment));
+ theVertexGenerator.AddIncoming("attr_pos", "vec3");
+ theVertexGenerator.AddUniform("model_view_projection", "mat4");
+ theVertexGenerator.Append("void main() {");
+ theVertexGenerator.Append("\tgl_Position = model_view_projection * vec4(attr_pos, 1.0);");
+ theVertexGenerator.Append("}");
+ theFragmentGenerator.Append("void main() {");
+ theFragmentGenerator.Append("\tgl_FragColor.rgb = vec3(0.0, 0.0, 0.0);");
+ theFragmentGenerator.Append("\tgl_FragColor.a = 1.0;");
+ theFragmentGenerator.Append("}");
+ return inWidgetContext.CompileAndStoreShader(theItemName);
+ }
+
+ static inline QT3DSVec3 ToFixedCameraPos(const QT3DSVec3 &inCameraPos, const SCamera &inCamera)
+ {
+ if (inCamera.m_Flags.IsOrthographic()) {
+ return QT3DSVec3(inCameraPos.x, inCameraPos.y, -600.f);
+ }
+ QT3DSF32 multiplier = -600.f / inCameraPos.z;
+ return inCameraPos * multiplier;
+ }
+
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext) override
+ {
+ // Widgets have to clear the depth buffer; they shouldn't interact with other components
+ // but they should self-occlude.
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetBlendFunction(
+ qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::One,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ inRenderContext.SetBlendEquation(
+ qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+
+ float pixelRatio = float(StudioUtils::devicePixelRatio());
+ QT3DSF32 theRingRadius = 2 * CStudioPreferences::getSelectorLineLength() * pixelRatio;
+ QT3DSF32 theRingWidth = CStudioPreferences::getSelectorLineWidth() * pixelRatio;
+ QT3DSF32 theRingInner = theRingRadius;
+ QT3DSF32 theRingOuter = theRingRadius + theRingWidth;
+ if (m_XAxis == nullptr) {
+ TBase::SetupRender(inWidgetContext, inRenderContext);
+ m_PickShader = IStudioWidget::CreateWidgetPickShader(inWidgetContext, inRenderContext);
+ m_XAxis = CreateRing(inWidgetContext, inRenderContext, QT3DSVec3(-1, 0, 0),
+ theRingInner, theRingOuter, 0.0f, "RotationWidgetXAxis");
+ m_YAxis = CreateRing(inWidgetContext, inRenderContext, QT3DSVec3(0, -1, 0),
+ theRingInner, theRingOuter, 0.0f, "RotationWidgetYAxis");
+ m_ZAxis = CreateRing(inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ theRingInner, theRingOuter, 0.0f, "RotationWidgetZAxis");
+ m_CameraAxis =
+ CreateRing(inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ theRingInner + 5, theRingOuter + 5, 0.0f,
+ "RotationWidgetCameraAxis");
+ m_CameraRect = CreateRect(inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1), 200.0f,
+ "RotationWidgetZClear");
+ m_ZClearShader =
+ CreateZClearShader(inWidgetContext, inRenderContext, "RotationWidgetZClear");
+ }
+ QT3DSVec3 theXColor(GetXAxisColor());
+ QT3DSVec3 theYColor(GetYAxisColor());
+ QT3DSVec3 theZColor(GetZAxisColor());
+ QT3DSVec3 theRingColor(QT3DSVec3(.8f, .8f, .8f));
+
+ QT3DSMat44 theMVP = TBase::SetupMVP(inWidgetContext);
+ inRenderContext.SetCullingEnabled(false);
+ QT3DSMat44 theCameraMVP = m_WidgetInfo.m_LayerProjection * m_CameraTranslationScale;
+
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetColorWritesEnabled(false);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetActiveShader(m_ZClearShader);
+ inRenderContext.SetInputAssembler(m_CameraRect);
+ m_ZClearShader->SetPropertyValue("model_view_projection", theCameraMVP);
+ inRenderContext.Draw(NVRenderDrawMode::Triangles, m_CameraRect->GetVertexCount(), 0);
+
+ inRenderContext.SetColorWritesEnabled(true);
+ inRenderContext.SetActiveShader(m_Shader);
+ m_Shader->SetPropertyValue("model_view_projection", theCameraMVP);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetDepthWriteEnabled(false);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::CameraPlane, theRingColor,
+ inRenderContext, m_CameraAxis);
+
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetActiveShader(m_Shader);
+ m_Shader->SetPropertyValue("model_view_projection", theMVP);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::XAxis, theXColor, inRenderContext,
+ m_XAxis);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::YAxis, theYColor, inRenderContext,
+ m_YAxis);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::ZAxis, theZColor, inRenderContext,
+ m_ZAxis);
+
+ if (m_RotationWedge.hasValue()) {
+ BeginImmediateDrawing(inWidgetContext, inRenderContext);
+
+ QT3DSMat33 theMVPRotation(m_WidgetInfo.m_CameraGlobalInverse.column0.getXYZ(),
+ m_WidgetInfo.m_CameraGlobalInverse.column1.getXYZ(),
+ m_WidgetInfo.m_CameraGlobalInverse.column2.getXYZ());
+ theMVPRotation = theMVPRotation.getInverse().getTranspose();
+
+ QT3DSVec3 theRotationAxis = theMVPRotation.transform(m_RotationWedge->m_RotationAxis);
+ QT3DSVec3 theStartDirection = theMVPRotation.transform(m_RotationWedge->m_StartDirection);
+ theRotationAxis.normalize();
+ theStartDirection.normalize();
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetBlendingEnabled(true);
+ QT3DSVec4 lineColor(1.0f, 1.0f, 1.0f, .7f);
+ QT3DSVec4 fillColor(1.0f, 1.0f, 1.0f, .2f);
+ switch (m_Highlight) {
+ default:
+ break;
+ case StudioWidgetComponentIds::XAxis:
+ lineColor = QT3DSVec4(theXColor, .7f);
+ fillColor = QT3DSVec4(theXColor, .2f);
+ break;
+ case StudioWidgetComponentIds::YAxis:
+ lineColor = QT3DSVec4(theYColor, .7f);
+ fillColor = QT3DSVec4(theYColor, .2f);
+ break;
+ case StudioWidgetComponentIds::ZAxis:
+ lineColor = QT3DSVec4(theZColor, .7f);
+ fillColor = QT3DSVec4(theZColor, .2f);
+ break;
+ }
+ QT3DSVec3 theStartPos(m_WidgetInfo.m_Position);
+ QT3DSF32 theStartLineLen = theRingOuter * m_WidgetInfo.m_Scale;
+ if (m_Highlight == StudioWidgetComponentIds::CameraPlane)
+ theStartLineLen = (theRingOuter + 5) * m_WidgetInfo.m_Scale;
+ // Get the end line length in camera space.
+ QT3DSVec3 theGlobalStart = m_Node->GetGlobalPivot();
+ QT3DSQuat theGlobalRot(m_RotationWedge->m_Angle, m_RotationWedge->m_RotationAxis);
+ QT3DSVec3 theGlobalDir = theGlobalRot.rotate(m_RotationWedge->m_StartDirection);
+ QT3DSVec3 theGlobalEnd = theGlobalStart + theGlobalDir * m_RotationWedge->m_EndLineLen;
+ // Transform both start, end into camera space and get the length of the resulting
+ // vector
+ QT3DSVec3 theCameraEnd = m_WidgetInfo.m_CameraGlobalInverse.transform(theGlobalEnd);
+ // Draw lines in world space
+ SCamera &theCamera(*m_WidgetInfo.m_Camera);
+ QT3DSVec3 lineStart(ToFixedCameraPos(theStartPos, theCamera));
+ QT3DSVec3 startLineEnd(
+ ToFixedCameraPos(theStartPos + theStartDirection * theStartLineLen, theCamera));
+ QT3DSVec3 endLineEnd(ToFixedCameraPos(theCameraEnd, theCamera));
+ DrawImmediateLine(lineStart, startLineEnd, 1.0f, lineColor);
+ DrawImmediateLine(lineStart, endLineEnd, 1.0f, lineColor);
+ DrawFilledArc(theStartPos, theStartDirection, theStartLineLen, theRotationAxis,
+ m_RotationWedge->m_Angle, fillColor);
+ // Now setup the model-view-projection.
+ QT3DSMat44 theProjection = m_WidgetInfo.m_LayerProjection;
+ EndImmediateDrawing(inWidgetContext, inRenderContext, theProjection);
+
+ // Now we attempt to render some text. First we format it.
+
+ char textBuffer[25] = { 0 };
+ QT3DSF32 angleDeg(m_RotationWedge->m_Angle);
+ TODEG(angleDeg);
+ sprintf(textBuffer, " %.1f ", -angleDeg); // spaces added for margin
+ STextRenderInfo theInfo;
+ theInfo.m_Text = inRenderContext.GetStringTable().RegisterStr(textBuffer);
+ theInfo.m_HorizontalAlignment = TextHorizontalAlignment::Center;
+ theInfo.m_VerticalAlignment = TextVerticalAlignment::Bottom;
+ theInfo.m_FontSize = 24.0f * pixelRatio;
+ theInfo.m_Font = inRenderContext.GetStringTable().RegisterStr("TitilliumWeb-Regular");
+ QT3DSMat44 theTransMatrix(QT3DSMat44::createIdentity());
+ theTransMatrix.column3.x = endLineEnd.x;
+ theTransMatrix.column3.y = endLineEnd.y;
+ theTransMatrix.column3.z = endLineEnd.z;
+ // We want to scale the text *down* so that it looks better.
+ theTransMatrix.column0[0] = m_WidgetInfo.m_Scale * .8f;
+ theTransMatrix.column1[1] = m_WidgetInfo.m_Scale * .8f;
+ theTransMatrix.column2[2] = m_WidgetInfo.m_Scale * .8f;
+ QT3DSMat44 theTextMVP = theProjection * theTransMatrix;
+ inWidgetContext.RenderText(theInfo, QT3DSVec3(1.0f, 1.0f, 1.0f),
+ QT3DSVec3(.2f, .2f, .2f), theTextMVP);
+ }
+ m_Highlight = StudioWidgetComponentIds::NoId;
+ }
+
+ void RenderPick(const QT3DSMat44 &inProjPremult, NVRenderContext &inRenderContext,
+ QSize /*inWinDimensions*/) override
+ {
+ if (m_XAxis && m_PickShader) {
+ QT3DSMat44 theCameraMVP =
+ inProjPremult * m_WidgetInfo.m_PureProjection * m_CameraTranslationScale;
+ QT3DSMat44 theMVP = inProjPremult * m_WidgetInfo.m_PureProjection * m_TranslationScale;
+
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetDepthTestEnabled(false);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetBlendingEnabled(true);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_ZClearShader);
+ m_ZClearShader->SetPropertyValue("model_view_projection", theCameraMVP);
+ inRenderContext.SetInputAssembler(m_CameraRect);
+ inRenderContext.Draw(NVRenderDrawMode::Triangles, m_CameraRect->GetVertexCount(), 0);
+
+ inRenderContext.SetActiveShader(m_PickShader);
+ m_PickShader->SetPropertyValue("model_view_projection", theCameraMVP);
+ RenderPickBuffer(StudioWidgetComponentIds::CameraPlane, m_CameraAxis, inRenderContext);
+
+ m_PickShader->SetPropertyValue("model_view_projection", theMVP);
+
+ RenderPickBuffer(StudioWidgetComponentIds::XAxis, m_XAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::YAxis, m_YAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::ZAxis, m_ZAxis, inRenderContext);
+ }
+ }
+};
+}
+
+IStudioWidget &IStudioWidget::CreateRotationWidget(NVAllocatorCallback &inAlloc)
+{
+ return *QT3DS_NEW(inAlloc, SRotationWidget)(inAlloc);
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioScaleWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioScaleWidget.cpp
new file mode 100644
index 00000000..9b5eec1f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioScaleWidget.cpp
@@ -0,0 +1,266 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioWidgetImpl.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderVertexBuffer.h"
+#include "Qt3DSRenderNode.h"
+#include "foundation/Qt3DSContainers.h"
+#include "Qt3DSRenderShaderCodeGenerator.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+
+using namespace qt3ds::widgets;
+
+namespace {
+
+struct SScaleWidget : public SStudioWidgetImpl<StudioWidgetTypes::Scale>
+{
+ typedef SStudioWidgetImpl<StudioWidgetTypes::Scale> TBase;
+
+ NVRenderInputAssembler *m_XAxis;
+ NVRenderInputAssembler *m_YAxis;
+ NVRenderInputAssembler *m_ZAxis;
+
+ NVRenderInputAssembler *m_XPlane;
+ NVRenderInputAssembler *m_YPlane;
+ NVRenderInputAssembler *m_ZPlane;
+
+ volatile QT3DSI32 mRefCount;
+
+ SScaleWidget(NVAllocatorCallback &inAlloc)
+ : TBase(inAlloc)
+ , m_XAxis(nullptr)
+ , m_YAxis(nullptr)
+ , m_ZAxis(nullptr)
+ , m_XPlane(nullptr)
+ , m_YPlane(nullptr)
+ , m_ZPlane(nullptr)
+ , mRefCount(0)
+ {
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator)
+
+ NVRenderInputAssembler *CreateScaleAxis(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext,
+ const QT3DSVec3 &inDirection, QT3DSF32 inStartOffset,
+ QT3DSF32 inLength, QT3DSF32 inWidth, QT3DSF32 inBoxSideLength,
+ const char *inAxisName)
+ {
+
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inAxisName);
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval) {
+ return retval;
+ }
+
+ QT3DSVec3 tempCross = inDirection.cross(QT3DSVec3(0, 1, 0));
+ if (tempCross.magnitudeSquared() < .05f)
+ tempCross = inDirection.cross(QT3DSVec3(1, 0, 0));
+
+ QT3DSVec3 upDir = inDirection.cross(tempCross);
+ QT3DSVec3 leftDir = upDir.cross(inDirection);
+
+ TResultVecType theVertexData(m_Allocator, "SScaleWidget::theVertexData");
+
+ QT3DSVec3 rectStart = inDirection * inStartOffset;
+ // Rect end is also box start, obviously
+ QT3DSVec3 rectEnd = inDirection * (inStartOffset + inLength);
+ QT3DSVec3 boxEnd = inDirection * (inStartOffset + inLength + inBoxSideLength);
+
+ QT3DSF32 axisHalfWidth = inWidth / 2.0f;
+ // Create the axis
+ CreateRect(rectStart, rectEnd, upDir, axisHalfWidth, 0.0f, theVertexData);
+ CreateRect(rectStart, rectEnd, leftDir, axisHalfWidth, 0.0f, theVertexData);
+ // Create box at the top.
+ QT3DSF32 boxSideHalfLength = inBoxSideLength / 2;
+ // Get the four sides
+ QT3DSVec3 boxRectStart = rectEnd + (leftDir * boxSideHalfLength);
+ QT3DSVec3 boxRectEnd = boxEnd + (leftDir * boxSideHalfLength);
+ CreateRect(boxRectStart, boxRectEnd, upDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ boxRectStart = rectEnd - leftDir * boxSideHalfLength;
+ boxRectEnd = boxEnd - leftDir * boxSideHalfLength;
+ CreateRect(boxRectStart, boxRectEnd, upDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ boxRectStart = rectEnd + upDir * boxSideHalfLength;
+ boxRectEnd = boxEnd + upDir * boxSideHalfLength;
+ CreateRect(boxRectStart, boxRectEnd, leftDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ boxRectStart = rectEnd - upDir * boxSideHalfLength;
+ boxRectEnd = boxEnd - upDir * boxSideHalfLength;
+ CreateRect(boxRectStart, boxRectEnd, leftDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ // now create the top and bottom
+ // bottom
+ boxRectStart = rectEnd + upDir * boxSideHalfLength;
+ boxRectEnd = rectEnd - upDir * boxSideHalfLength;
+ CreateRect(boxRectStart, boxRectEnd, leftDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ // top
+ boxRectStart = boxEnd + upDir * boxSideHalfLength;
+ boxRectEnd = boxEnd - upDir * boxSideHalfLength;
+ CreateRect(boxRectStart, boxRectEnd, leftDir, boxSideHalfLength, 0.0f, theVertexData);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ IStudioWidget::GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+ }
+
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext) override
+ {
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ float pixelRatio = float(StudioUtils::devicePixelRatio());
+ QT3DSF32 axisWidth = pixelRatio;
+ QT3DSF32 triWidth = 3 * CStudioPreferences::getSelectorLineWidth() * pixelRatio;
+ QT3DSF32 axisStart = CStudioPreferences::getSelectorLineLength() / 3.0f * pixelRatio;
+ QT3DSF32 axisLength = CStudioPreferences::getSelectorLineLength() * pixelRatio;
+ QT3DSF32 axisTotalLength = triWidth + axisLength;
+ if (m_XAxis == nullptr) {
+ TBase::SetupRender(inWidgetContext, inRenderContext);
+
+ m_XAxis = CreateScaleAxis(inWidgetContext, inRenderContext, QT3DSVec3(1, 0, 0),
+ axisStart, axisLength, axisWidth, triWidth,
+ "ScaleWidgetXAxis");
+ m_YAxis = CreateScaleAxis(inWidgetContext, inRenderContext, QT3DSVec3(0, 1, 0),
+ axisStart, axisLength, axisWidth, triWidth,
+ "ScaleWidgetYAxis");
+ m_ZAxis = CreateScaleAxis(inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ axisStart, axisLength, axisWidth, triWidth,
+ "ScaleWidgetZAxis");
+
+ QT3DSF32 axisPos = GetDiscPos() * pixelRatio;
+ QT3DSF32 axisDiscRadius = GetDiscRadius() * pixelRatio;
+ QT3DSF32 axisRingRadius = GetDiscRingRadius() * pixelRatio;
+ m_XPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(1, 0, 0),
+ QT3DSVec3(0, axisPos, -axisPos), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "ScaleWidgetXPlane");
+ m_YPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 1, 0),
+ QT3DSVec3(axisPos, 0, -axisPos), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "ScaleWidgetYPlane");
+ m_ZPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ QT3DSVec3(axisPos, axisPos, 0), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "ScaleWidgetZPlane");
+
+ inRenderContext.SetActiveShader(m_Shader);
+ m_Shader->SetPropertyValue("attr_pos_add_start", axisStart + 1);
+ }
+
+ QT3DSMat44 theMVP = TBase::SetupMVP(inWidgetContext);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_Shader);
+ m_Shader->SetPropertyValue("model_view_projection", theMVP);
+ // temporary set color1 to white so we can hopefully see mistakes.
+ m_Shader->SetPropertyValue("color1", QT3DSVec3(1, 1, 1));
+
+ QT3DSVec3 theXColor(GetXAxisColor());
+ QT3DSVec3 theYColor(GetYAxisColor());
+ QT3DSVec3 theZColor(GetZAxisColor());
+ QT3DSVec3 theRingColor(QT3DSVec3(.8f, .8f, .8f));
+ QT3DSVec3 theEndOffset = QT3DSVec3(axisTotalLength);
+ QT3DSVec3 theScaledEnd = QT3DSVec3(theEndOffset.x * m_AxisScale.x,
+ theEndOffset.y * m_AxisScale.y,
+ theEndOffset.z * m_AxisScale.z);
+ QT3DSVec3 theEndAddition = theScaledEnd - theEndOffset;
+
+ m_Shader->SetPropertyValue("attr_pos_add_amount", QT3DSVec3(theEndAddition.x, 0, 0));
+ RenderSingleToneGeometry(StudioWidgetComponentIds::XAxis, theXColor, inRenderContext,
+ m_XAxis);
+
+ m_Shader->SetPropertyValue("attr_pos_add_amount", QT3DSVec3(0, theEndAddition.y, 0));
+ RenderSingleToneGeometry(StudioWidgetComponentIds::YAxis, theYColor, inRenderContext,
+ m_YAxis);
+
+ m_Shader->SetPropertyValue("attr_pos_add_amount", QT3DSVec3(0, 0, -1.0f * theEndAddition.z));
+ RenderSingleToneGeometry(StudioWidgetComponentIds::ZAxis, theZColor, inRenderContext,
+ m_ZAxis);
+
+ m_Shader->SetPropertyValue("attr_pos_add_amount", QT3DSVec3(0, 0, 0));
+ RenderTwoToneGeometry(StudioWidgetComponentIds::XPlane, theXColor, theRingColor,
+ inRenderContext, m_XPlane);
+ RenderTwoToneGeometry(StudioWidgetComponentIds::YPlane, theYColor, theRingColor,
+ inRenderContext, m_YPlane);
+ RenderTwoToneGeometry(StudioWidgetComponentIds::ZPlane, theZColor, theRingColor,
+ inRenderContext, m_ZPlane);
+
+ m_Highlight = StudioWidgetComponentIds::NoId;
+ }
+
+ void RenderPick(const QT3DSMat44 &inProjPremult, NVRenderContext &inRenderContext,
+ QSize /*inWinDimensions*/) override
+ {
+ if (m_XAxis && m_PickShader) {
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_PickShader);
+ // The projection premultiplication step moves the viewport around till
+ // it is centered over the mouse and scales everything *post* rendering (to keep
+ // appropriate aspect).
+ QT3DSMat44 theMVP = inProjPremult * m_PureProjection * m_TranslationScale;
+ m_PickShader->SetPropertyValue("model_view_projection", theMVP);
+
+ RenderPickBuffer(StudioWidgetComponentIds::XAxis, m_XAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::YAxis, m_YAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::ZAxis, m_ZAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::XPlane, m_XPlane, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::YPlane, m_YPlane, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::ZPlane, m_ZPlane, inRenderContext);
+ }
+ }
+};
+}
+
+IStudioWidget &IStudioWidget::CreateScaleWidget(NVAllocatorCallback &inAlloc)
+{
+ return *QT3DS_NEW(inAlloc, SScaleWidget)(inAlloc);
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.cpp b/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.cpp
new file mode 100644
index 00000000..b04233cd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.cpp
@@ -0,0 +1,377 @@
+/****************************************************************************
+**
+** 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 "StudioSubPresentationRenderer.h"
+#include "render/Qt3DSRenderContext.h"
+#include "q3dspresentation.h"
+#include "q3dsviewersettings.h"
+
+#include <QtCore/qfileinfo.h>
+#include <qxmlstream.h>
+
+using namespace qt3ds::render;
+
+class RendererThread : public QThread
+{
+public:
+ RendererThread(QThread *mainThread)
+ : m_running(false)
+ , m_updated(false)
+ , m_initialized(false)
+ , m_mainThread(mainThread)
+ , m_semaphore(0)
+ {
+ }
+
+ QSize readPresentationSize(const QString &url)
+ {
+ QFile file(url);
+ file.open(QFile::Text | QFile::ReadOnly);
+ if (!file.isOpen()) {
+ qWarning () << file.errorString();
+ return QSize();
+ }
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ if (reader.readNextStartElement() && reader.qualifiedName() == QLatin1String("UIP")) {
+ if (reader.readNextStartElement()
+ && reader.qualifiedName() == QLatin1String("Project")) {
+ if (reader.readNextStartElement()
+ && reader.qualifiedName() == QLatin1String("ProjectSettings")) {
+ const auto attrib = reader.attributes();
+ return QSize(attrib.value(QLatin1String("presentationWidth")).toInt(),
+ attrib.value(QLatin1String("presentationHeight")).toInt());
+ }
+ }
+ }
+ return QSize();
+ }
+
+ void initialize(const QString &id, const QString &presentation, const QString &path)
+ {
+ m_path = path;
+ m_presentation = presentation;
+
+ m_surfaceViewer.reset(new Q3DSSurfaceViewer);
+ m_surfaceViewer->setPresentationId(id);
+ m_context.reset(new QT_PREPEND_NAMESPACE(QOpenGLContext));
+ m_surface.reset(new QOffscreenSurface);
+
+ QSurfaceFormat format = QSurfaceFormat::defaultFormat();
+ if (QT_PREPEND_NAMESPACE(QOpenGLContext)::currentContext())
+ format = QT_PREPEND_NAMESPACE(QOpenGLContext)::currentContext()->format();
+ m_context->setShareContext(QT_PREPEND_NAMESPACE(QOpenGLContext)::currentContext());
+ m_context->setFormat(format);
+ m_surface->setFormat(format);
+ m_context->create();
+ m_surface->create();
+ m_context->moveToThread(this);
+ m_surface->moveToThread(this);
+ m_surfaceViewer->moveToThread(this);
+
+ QObject::connect(m_surfaceViewer.data(), &Q3DSSurfaceViewer::frameUpdate,
+ this, &RendererThread::frameRendered);
+ m_initialized = true;
+ }
+
+ void run() override
+ {
+ QFileInfo info(m_path + "/" + m_presentation);
+ m_size = readPresentationSize(m_path + "/" + m_presentation);
+
+ m_context->makeCurrent(m_surface.data());
+
+ m_fbo.reset(new QOpenGLFramebufferObject(m_size,
+ QOpenGLFramebufferObject::CombinedDepthStencil));
+
+ m_surfaceViewer->setSize(m_size);
+ m_surfaceViewer->setAutoSize(false);
+ m_surfaceViewer->setUpdateInterval(-1);
+ m_surfaceViewer->presentation()->setSource(QUrl::fromLocalFile(info.absoluteFilePath()));
+ m_surfaceViewer->settings()->setMatteColor(Qt::transparent);
+ m_surfaceViewer->create(m_surface.data(), m_context.data(), m_fbo->handle());
+ m_running = true;
+ m_semaphore.release();
+
+ m_context->doneCurrent();
+
+ while (true) {
+ QMutexLocker lock(&m_mutex);
+ if (!m_running)
+ break;
+ m_context->makeCurrent(m_surface.data());
+ if (!m_surfaceViewer->isRunning()) {
+ qWarning () << "Subpresentation stopped running. Stopping updating.";
+ break;
+ }
+
+ m_surfaceViewer->update();
+ m_context->functions()->glFlush();
+ m_updated = true;
+ m_context->doneCurrent();
+ lock.unlock();
+
+ // Update approx 30 fps
+ QThread::usleep(33000);
+ }
+ m_fbo.reset();
+ m_context->doneCurrent();
+ m_context.reset();
+ m_surfaceViewer->destroy();
+ m_surfaceViewer.reset();
+ m_surface->moveToThread(m_mainThread);
+ m_semaphore.release();
+ }
+
+ void frameRendered()
+ {
+ m_updated = true;
+ }
+
+ QScopedPointer<Q3DSSurfaceViewer> m_surfaceViewer;
+ QScopedPointer<QT_PREPEND_NAMESPACE(QOpenGLContext)> m_context;
+ QScopedPointer<QOffscreenSurface> m_surface;
+ QScopedPointer<QOpenGLFramebufferObject> m_fbo;
+ QString m_path;
+ QString m_presentation;
+ QMutex m_mutex;
+ bool m_running, m_updated, m_initialized;
+ QThread *m_mainThread;
+ QSemaphore m_semaphore;
+ QSize m_size;
+};
+
+StudioSubpresentationRenderer::StudioSubpresentationRenderer(
+ IQt3DSRenderContext &inRenderContext, const QString &id,
+ const QString &presentation, const QString &path)
+ : m_renderContext(inRenderContext), m_id(id), m_presentation(presentation), m_path(path)
+ , m_program(nullptr)
+ , m_vao(nullptr)
+ , m_vertices(nullptr)
+ , m_callback(nullptr)
+ , m_rendererType(inRenderContext.GetStringTable().RegisterStr("StudioPresentationRenderer"))
+ , mRefCount(0)
+{
+ m_thread.reset(new RendererThread(QThread::currentThread()));
+}
+
+StudioSubpresentationRenderer::~StudioSubpresentationRenderer()
+{
+ QMutexLocker lock(&m_thread->m_mutex);
+ if (m_thread->m_running) {
+ m_thread->m_running = false;
+ lock.unlock();
+ m_thread->m_semaphore.acquire();
+ }
+}
+
+CRegisteredString StudioSubpresentationRenderer::GetOffscreenRendererType()
+{
+ return m_rendererType;
+}
+
+SOffscreenRendererEnvironment
+StudioSubpresentationRenderer::GetDesiredEnvironment(QT3DSVec2 inPresentationScaleFactor)
+{
+ // If we aren't using a clear color, then we are expected to blend with the background
+ if (!m_thread->m_initialized) {
+ initialize();
+ }
+ bool hasTransparency = true;
+ NVRenderTextureFormats::Enum format =
+ hasTransparency ? NVRenderTextureFormats::RGBA8 : NVRenderTextureFormats::RGB8;
+ return SOffscreenRendererEnvironment(
+ QT3DSU32(m_thread->m_size.width() * inPresentationScaleFactor.x),
+ QT3DSU32(m_thread->m_size.height() * inPresentationScaleFactor.y),
+ format, OffscreenRendererDepthValues::Depth24, false,
+ AAModeValues::NoAA);
+}
+
+SOffscreenRenderFlags StudioSubpresentationRenderer::NeedsRender(
+ const SOffscreenRendererEnvironment &inEnvironment,
+ QT3DSVec2 inPresentationScaleFactor,
+ const SRenderInstanceId instanceId)
+{
+ Q_UNUSED(inEnvironment)
+ Q_UNUSED(inPresentationScaleFactor)
+ Q_UNUSED(instanceId)
+ return SOffscreenRenderFlags(true, true);
+}
+
+void StudioSubpresentationRenderer::initialize()
+{
+ m_thread->initialize(m_id, m_presentation, m_path);
+ m_thread->start();
+ m_thread->m_semaphore.acquire();
+ if (m_callback)
+ m_callback->onOffscreenRendererInitialized(m_id);
+}
+
+void StudioSubpresentationRenderer::Render(const SOffscreenRendererEnvironment &inEnvironment,
+ NVRenderContext &inRenderContext, QT3DSVec2 inPresentationScaleFactor,
+ SScene::RenderClearCommand inColorBufferNeedsClear,
+ const SRenderInstanceId instanceId)
+{
+ Q_UNUSED(inEnvironment)
+ Q_UNUSED(inPresentationScaleFactor)
+ Q_UNUSED(instanceId)
+ inRenderContext.PushPropertySet();
+ if (!m_thread->m_initialized) {
+ initialize();
+ }
+ QMutexLocker lock(&m_thread->m_mutex);
+ if (m_thread->m_initialized && m_thread->m_updated) {
+ QT_PREPEND_NAMESPACE(QOpenGLContext) *context
+ = QT_PREPEND_NAMESPACE(QOpenGLContext)::currentContext();
+ QOpenGLFunctions *func = context->functions();
+ GLuint texture = m_thread->m_fbo->texture();
+ func->glDisable(GL_DEPTH_TEST);
+ if (inColorBufferNeedsClear == SScene::RenderClearCommand::AlwaysClear)
+ func->glDisable(GL_BLEND);
+ else
+ func->glEnable(GL_BLEND);
+ func->glBlendEquation(GL_FUNC_ADD);
+ func->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (!m_program)
+ initializeFboCopy();
+
+ m_program->bind();
+
+ if (!m_vao) {
+ m_vao = new QOpenGLVertexArrayObject;
+ m_vao->create();
+ m_vao->bind();
+ m_vertices->bind();
+
+ m_program->enableAttributeArray(0);
+ m_program->enableAttributeArray(1);
+ func->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), nullptr);
+ func->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float),
+ reinterpret_cast<const void *>(4 * sizeof(GLfloat)));
+ m_vertices->release();
+ } else {
+ m_vao->bind();
+ }
+
+ func->glActiveTexture(GL_TEXTURE0);
+ func->glBindTexture(GL_TEXTURE_2D, texture);
+ func->glDrawArrays(GL_TRIANGLES, 0, 6);
+ func->glEnable(GL_DEPTH_TEST);
+ func->glBindTexture(GL_TEXTURE_2D, 0);
+
+ m_program->release();
+ m_vao->release();
+
+ if (m_callback)
+ m_callback->onOffscreenRendererFrame(m_id);
+ }
+ inRenderContext.PopPropertySet(true);
+}
+
+void StudioSubpresentationRenderer::RenderWithClear(
+ const SOffscreenRendererEnvironment &inEnvironment,
+ NVRenderContext &inRenderContext, QT3DSVec2 inPresentationScaleFactor,
+ SScene::RenderClearCommand inColorBufferNeedsClear,
+ QT3DSVec4 inclearColor,
+ const SRenderInstanceId instanceId)
+{
+ inRenderContext.SetClearColor(inclearColor);
+ Render(inEnvironment, inRenderContext, inPresentationScaleFactor,
+ inColorBufferNeedsClear, instanceId);
+}
+
+void StudioSubpresentationRenderer::initializeFboCopy()
+{
+ m_program = new QOpenGLShaderProgram;
+ if (m_thread->m_context->format().renderableType() == QSurfaceFormat::OpenGLES) {
+ static const char *vsSource =
+ "attribute highp vec4 pos;\n"
+ "attribute highp vec2 tc;\n"
+ "varying lowp vec2 texcoord;\n"
+ "void main() {\n"
+ " texcoord = tc;\n"
+ " gl_Position = pos;\n"
+ "}\n";
+ static const char *fsSource =
+ "varying highp vec2 texcoord;\n"
+ "uniform sampler2D sampler;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(sampler, texcoord);\n"
+ "}\n";
+ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsSource);
+ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsSource);
+ } else {
+ static const char *vsSource =
+ "#version 150 core\n"
+ "in vec4 pos;\n"
+ "in vec2 tc;\n"
+ "out vec2 texcoord;\n"
+ "void main() {\n"
+ " texcoord = tc;\n"
+ " gl_Position = pos;\n"
+ "}\n";
+ static const char *fsSource =
+ "#version 150 core\n"
+ "in vec2 texcoord;\n"
+ "out vec4 fragColor;\n"
+ "uniform sampler2D sampler;\n"
+ "void main() {\n"
+ " fragColor = texture(sampler, texcoord);\n"
+ "}\n";
+ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsSource);
+ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsSource);
+ }
+
+ m_program->bindAttributeLocation("pos", 0);
+ m_program->bindAttributeLocation("tc", 1);
+
+ if (!m_program->link()) {
+ QByteArray logData(m_program->log().toLocal8Bit());
+ const char *log = logData.data();
+ qFatal("Failed to create shader program: %s", log);
+ }
+
+ m_vertices = new QOpenGLBuffer;
+ m_vertices->create();
+ m_vertices->bind();
+
+ static const float vertices[] =
+ {
+ -1, -1, 0, 1, 0, 0,
+ 1, -1, 0, 1, 1, 0,
+ 1, 1, 0, 1, 1, 1,
+ -1, -1, 0, 1, 0, 0,
+ 1, 1, 0, 1, 1, 1,
+ -1, 1, 0, 1, 0, 1,
+ };
+
+ m_vertices->allocate(vertices, sizeof(vertices));
+ m_vertices->release();
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.h b/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.h
new file mode 100644
index 00000000..3ff434e6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioSubPresentationRenderer.h
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** 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 QT3DS_STUDIO_SUBPRESENTATION_RENDERER_H
+#define QT3DS_STUDIO_SUBPRESENTATION_RENDERER_H
+
+#include "Qt3DSOffscreenRenderManager.h"
+#include "Qt3DSRenderContextCore.h"
+#include "q3dssurfaceviewer.h"
+
+#include <QtGui/qopenglshaderprogram.h>
+#include <QtGui/qopenglvertexarrayobject.h>
+#include <QtGui/qopenglbuffer.h>
+#include <QtGui/qoffscreensurface.h>
+#include <QtGui/qopenglcontext.h>
+#include <QtCore/qscopedpointer.h>
+
+class RendererThread;
+
+class StudioSubpresentationRenderer : public qt3ds::render::IOffscreenRenderer
+{
+public:
+ StudioSubpresentationRenderer(qt3ds::render::IQt3DSRenderContext &inRenderContext,
+ const QString &id, const QString &presentation,
+ const QString &path);
+ ~StudioSubpresentationRenderer();
+
+ void addRef() override
+ {
+ qt3ds::render::atomicIncrement(&mRefCount);
+ }
+ void release() override
+ {
+ qt3ds::render::QT3DSI32 value = qt3ds::render::atomicDecrement(&mRefCount);
+ if (value <= 0)
+ qt3ds::render::NVDelete(m_renderContext.GetAllocator(), this);
+ }
+
+ qt3ds::render::CRegisteredString GetOffscreenRendererType() override;
+ qt3ds::render::SOffscreenRendererEnvironment
+ GetDesiredEnvironment(qt3ds::render::QT3DSVec2 inPresentationScaleFactor) override;
+
+ qt3ds::render::SOffscreenRenderFlags NeedsRender(
+ const qt3ds::render::SOffscreenRendererEnvironment &inEnvironment,
+ qt3ds::render::QT3DSVec2 inPresentationScaleFactor,
+ const qt3ds::render::SRenderInstanceId instanceId) override;
+
+ void Render(const qt3ds::render::SOffscreenRendererEnvironment &inEnvironment,
+ qt3ds::render::NVRenderContext &inRenderContext,
+ qt3ds::render::QT3DSVec2 inPresentationScaleFactor,
+ qt3ds::render::SScene::RenderClearCommand inColorBufferNeedsClear,
+ const qt3ds::render::SRenderInstanceId instanceId) override;
+ void RenderWithClear(const qt3ds::render::SOffscreenRendererEnvironment &inEnvironment,
+ qt3ds::render::NVRenderContext &inRenderContext,
+ qt3ds::render::QT3DSVec2 inPresentationScaleFactor,
+ qt3ds::render::SScene::RenderClearCommand inColorBufferNeedsClear,
+ qt3ds::render::QT3DSVec4 inclearColor,
+ const qt3ds::render::SRenderInstanceId instanceId) override;
+
+ qt3ds::render::IGraphObjectPickQuery *GetGraphObjectPickQuery(
+ const qt3ds::render::SRenderInstanceId instanceId) override
+ {
+ Q_UNUSED(instanceId);
+ return nullptr;
+ }
+
+ bool Pick(const qt3ds::render::QT3DSVec2 &inMouseCoords,
+ const qt3ds::render::QT3DSVec2 &inViewportDimensions,
+ const qt3ds::render::SRenderInstanceId instanceId) override
+ {
+ Q_UNUSED(inMouseCoords);
+ Q_UNUSED(inViewportDimensions);
+ Q_UNUSED(instanceId);
+ return false;
+ }
+
+ void addCallback(IOffscreenRendererCallback *cb) override
+ {
+ m_callback = cb;
+ }
+
+private:
+ void initializeFboCopy();
+ void initialize();
+
+ qt3ds::render::IQt3DSRenderContext &m_renderContext;
+ QString m_id;
+ QString m_presentation;
+ QString m_path;
+
+ QScopedPointer<RendererThread> m_thread;
+ QOpenGLShaderProgram *m_program;
+ QOpenGLVertexArrayObject *m_vao;
+ QOpenGLBuffer *m_vertices;
+ IOffscreenRendererCallback *m_callback;
+ qt3ds::render::CRegisteredString m_rendererType;
+ volatile qt3ds::render::QT3DSI32 mRefCount;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioTranslationWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioTranslationWidget.cpp
new file mode 100644
index 00000000..0897a866
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioTranslationWidget.cpp
@@ -0,0 +1,173 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioWidgetImpl.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "render/Qt3DSRenderContext.h"
+#include "Qt3DSRenderShaderCodeGenerator.h"
+#include "Qt3DSRenderNode.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+
+using namespace qt3ds::widgets;
+
+namespace {
+
+struct STranslationWidget : public SStudioWidgetImpl<StudioWidgetTypes::Translation>
+{
+ typedef SStudioWidgetImpl<StudioWidgetTypes::Translation> TBase;
+ NVRenderInputAssembler *m_XAxis;
+ NVRenderInputAssembler *m_YAxis;
+ NVRenderInputAssembler *m_ZAxis;
+
+ NVRenderInputAssembler *m_XPlane;
+ NVRenderInputAssembler *m_YPlane;
+ NVRenderInputAssembler *m_ZPlane;
+
+ volatile QT3DSI32 mRefCount;
+
+ STranslationWidget(NVAllocatorCallback &inAlloc)
+ : TBase(inAlloc)
+ , m_XAxis(nullptr)
+ , m_YAxis(nullptr)
+ , m_ZAxis(nullptr)
+ , m_XPlane(nullptr)
+ , m_YPlane(nullptr)
+ , m_ZPlane(nullptr)
+ , mRefCount(0)
+ {
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator);
+
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext) override
+ {
+ // Widgets have to clear the depth buffer; they shouldn't interact with other components
+ // but they should self-occlude.
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetDepthTestEnabled(true);
+ if (m_XAxis == nullptr) {
+ TBase::SetupRender(inWidgetContext, inRenderContext);
+ float pixelRatio = float(StudioUtils::devicePixelRatio());
+ QT3DSF32 axisWidth = pixelRatio;
+ QT3DSF32 triWidth = 4 * CStudioPreferences::getSelectorLineWidth() * pixelRatio;
+ QT3DSF32 axisStart = CStudioPreferences::getSelectorLineLength() / 3.0f * pixelRatio;
+ QT3DSF32 axisLength = CStudioPreferences::getSelectorLineLength() * pixelRatio;
+ QT3DSF32 triLength = axisStart;
+ m_XAxis = CreateAxis(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(1, 0, 0),
+ axisStart, axisLength, triLength, axisWidth, triWidth,
+ "TranslationWidgetXAxis");
+ m_YAxis = CreateAxis(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 1, 0),
+ axisStart, axisLength, triLength, axisWidth, triWidth,
+ "TranslationWidgetYAxis");
+ m_ZAxis = CreateAxis(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ axisStart, axisLength, triLength, axisWidth, triWidth,
+ "TranslationWidgetZAxis");
+
+ QT3DSF32 axisPos = GetDiscPos() * pixelRatio;
+ QT3DSF32 axisDiscRadius = GetDiscRadius() * pixelRatio;
+ QT3DSF32 axisRingRadius = GetDiscRingRadius() * pixelRatio;
+ m_XPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(1, 0, 0),
+ QT3DSVec3(0, axisPos, -axisPos), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "TranslationWidgetXPlane");
+ m_YPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 1, 0),
+ QT3DSVec3(axisPos, 0, -axisPos), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "TranslationWidgetYPlane");
+ m_ZPlane =
+ CreateRingedDisc(m_Allocator, inWidgetContext, inRenderContext, QT3DSVec3(0, 0, -1),
+ QT3DSVec3(axisPos, axisPos, 0), axisDiscRadius, axisRingRadius,
+ 0.0f, 1.0f, "TranslationWidgetZPlane");
+ }
+ QT3DSMat44 theMVP = TBase::SetupMVP(inWidgetContext);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_Shader);
+ m_Shader->SetPropertyValue("model_view_projection", theMVP);
+ // temporary set color1 to white so we can hopefully see mistakes.
+ m_Shader->SetPropertyValue("color1", QT3DSVec3(1, 1, 1));
+
+ QT3DSVec3 theXColor(GetXAxisColor());
+ QT3DSVec3 theYColor(GetYAxisColor());
+ QT3DSVec3 theZColor(GetZAxisColor());
+ QT3DSVec3 theRingColor(QT3DSVec3(.8f, .8f, .8f));
+
+ RenderSingleToneGeometry(StudioWidgetComponentIds::XAxis, theXColor, inRenderContext,
+ m_XAxis);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::YAxis, theYColor, inRenderContext,
+ m_YAxis);
+ RenderSingleToneGeometry(StudioWidgetComponentIds::ZAxis, theZColor, inRenderContext,
+ m_ZAxis);
+ RenderTwoToneGeometry(StudioWidgetComponentIds::XPlane, theXColor, theRingColor,
+ inRenderContext, m_XPlane);
+ RenderTwoToneGeometry(StudioWidgetComponentIds::YPlane, theYColor, theRingColor,
+ inRenderContext, m_YPlane);
+ RenderTwoToneGeometry(StudioWidgetComponentIds::ZPlane, theZColor, theRingColor,
+ inRenderContext, m_ZPlane);
+ m_Highlight = StudioWidgetComponentIds::NoId;
+ }
+
+ void RenderPick(const QT3DSMat44 &inProjPremult, NVRenderContext &inRenderContext,
+ QSize /*inWinDimensions*/) override
+ {
+ if (m_XAxis && m_PickShader) {
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.Clear(qt3ds::render::NVRenderClearValues::Depth);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(true);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetActiveShader(m_PickShader);
+ // The projection premultiplication step moves the viewport around till
+ // it is centered over the mouse and scales everything *post* rendering (to keep
+ // appropriate aspect).
+ QT3DSMat44 theMVP = inProjPremult * m_PureProjection * m_TranslationScale;
+ m_PickShader->SetPropertyValue("model_view_projection", theMVP);
+
+ RenderPickBuffer(StudioWidgetComponentIds::XAxis, m_XAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::YAxis, m_YAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::ZAxis, m_ZAxis, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::XPlane, m_XPlane, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::YPlane, m_YPlane, inRenderContext);
+ RenderPickBuffer(StudioWidgetComponentIds::ZPlane, m_ZPlane, inRenderContext);
+ }
+ }
+};
+}
+
+IStudioWidget &IStudioWidget::CreateTranslationWidget(NVAllocatorCallback &inAlloc)
+{
+ return *QT3DS_NEW(inAlloc, STranslationWidget)(inAlloc);
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.cpp
new file mode 100644
index 00000000..faadd7e6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.cpp
@@ -0,0 +1,730 @@
+/****************************************************************************
+**
+** 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 "StudioWidgetImpl.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderVertexBuffer.h"
+#include "Qt3DSRenderNode.h"
+#include "foundation/Qt3DSContainers.h"
+#include "Qt3DSRenderShaderCodeGenerator.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "StudioUtils.h"
+#include "StudioPreferences.h"
+#include "StudioVisualAidWidget.h"
+#include "Qt3DSRenderCamera.h"
+#include "Qt3DSRenderLight.h"
+#include "OptimizedArithmetic.h"
+
+namespace qt3ds {
+namespace widgets {
+
+static const float fepsilon = 1e-6f;
+static const float pointLightOuterRadius = 40.0f;
+static const float directionalLightRadius = 40.0f;
+static const float directionalLightLength = 100.0f;
+
+SVisualAidWidget::SVisualAidWidget(NVAllocatorCallback &inAlloc)
+ : m_node(nullptr)
+ , m_billboard(nullptr)
+ , m_cameraBox(nullptr)
+ , m_directionalLight(nullptr)
+ , m_pointLight(nullptr)
+ , m_areaLight(nullptr)
+ , m_renderCameraShader(nullptr)
+ , m_renderShader(nullptr)
+ , m_billboardShader(nullptr)
+ , m_billboardCameraTexture(nullptr)
+ , m_billboardLightTexture(nullptr)
+ , m_selected(false)
+ , m_allocator(inAlloc)
+ , mRefCount(0)
+{
+
+}
+
+NVConstDataRef<NVRenderVertexBufferEntry>
+SVisualAidWidget::getCameraBoxAttributesAndStride(QT3DSU32 &stride)
+{
+ static NVRenderVertexBufferEntry theEntries[] = {
+ NVRenderVertexBufferEntry("attr_pos", NVRenderComponentTypes::QT3DSF32, 4)
+ };
+
+ stride = 4 * sizeof(QT3DSF32);
+
+ return toConstDataRef(theEntries, 1);
+}
+
+NVConstDataRef<NVRenderVertexBufferEntry>
+SVisualAidWidget::getLightAttributesAndStride(QT3DSU32 &stride)
+{
+ static NVRenderVertexBufferEntry theEntries[] = {
+ NVRenderVertexBufferEntry("attr_pos", NVRenderComponentTypes::QT3DSF32, 3)
+ };
+
+ stride = 3 * sizeof(QT3DSF32);
+
+ return toConstDataRef(theEntries, 1);
+}
+
+NVConstDataRef<NVRenderVertexBufferEntry>
+SVisualAidWidget::getBillboardAttributesAndStride(QT3DSU32 &stride)
+{
+ static NVRenderVertexBufferEntry theEntries[] = {
+ NVRenderVertexBufferEntry("attr_pos", NVRenderComponentTypes::QT3DSF32, 3),
+ NVRenderVertexBufferEntry("attr_tc", NVRenderComponentTypes::QT3DSF32, 2,
+ 3 * sizeof(QT3DSF32))
+ };
+
+ stride = 6 * sizeof(QT3DSF32);
+
+ return toConstDataRef(theEntries, 2);
+}
+
+
+NVRenderInputAssembler *SVisualAidWidget::createCameraBox(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr("CameraBox");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec4> theVertexData(m_allocator, "SVisualAidWidget::theVertexData");
+ nvvector<QT3DSI8> theIndexData(m_allocator, "SVisualAidWidget::theIndexData");
+
+ // 9 vertices, origin and near and far rectangles in ndc
+ theVertexData.push_back(QT3DSVec4(0.0, 0.0, 0.0, 0.0));
+ theVertexData.push_back(QT3DSVec4(-1.0, -1.0, -1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(-1.0, 1.0, -1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(1.0, 1.0, -1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(1.0, -1.0, -1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(-1.0, -1.0, 1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(-1.0, 1.0, 1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(1.0, 1.0, 1.0, 1.0));
+ theVertexData.push_back(QT3DSVec4(1.0, -1.0, 1.0, 1.0));
+
+ // origin to near
+ theIndexData.push_back(0); theIndexData.push_back(1);
+ theIndexData.push_back(0); theIndexData.push_back(2);
+ theIndexData.push_back(0); theIndexData.push_back(3);
+ theIndexData.push_back(0); theIndexData.push_back(4);
+ // near rect
+ theIndexData.push_back(1); theIndexData.push_back(2);
+ theIndexData.push_back(2); theIndexData.push_back(3);
+ theIndexData.push_back(3); theIndexData.push_back(4);
+ theIndexData.push_back(4); theIndexData.push_back(1);
+ // near to far
+ theIndexData.push_back(1); theIndexData.push_back(5);
+ theIndexData.push_back(2); theIndexData.push_back(6);
+ theIndexData.push_back(3); theIndexData.push_back(7);
+ theIndexData.push_back(4); theIndexData.push_back(8);
+ // far rect
+ theIndexData.push_back(5); theIndexData.push_back(6);
+ theIndexData.push_back(6); theIndexData.push_back(7);
+ theIndexData.push_back(7); theIndexData.push_back(8);
+ theIndexData.push_back(8); theIndexData.push_back(5);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ getCameraBoxAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU8, theIndexData.size(),
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderInputAssembler *SVisualAidWidget::createBillboard(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr("Billboard");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec3> theVertexData(m_allocator, "SVisualAidWidget::theVertexData");
+ nvvector<QT3DSI8> theIndexData(m_allocator, "SVisualAidWidget::theIndexData");
+
+ theVertexData.push_back(QT3DSVec3(-1.0, -1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(0.0, 0.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(-1.0, 1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(0.0, 1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(1.0, 1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(1.0, 1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(1.0, -1.0, 0.0));
+ theVertexData.push_back(QT3DSVec3(1.0, 0.0, 0.0));
+
+ theIndexData.push_back(0); theIndexData.push_back(1); theIndexData.push_back(2);
+ theIndexData.push_back(0); theIndexData.push_back(3); theIndexData.push_back(2);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ getBillboardAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU8, theIndexData.size(),
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderInputAssembler *SVisualAidWidget::createDirectionalLight(
+ IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext)
+{
+ CRegisteredString theItemName
+ = inRenderContext.GetStringTable().RegisterStr("DirectionalLight");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec3> theVertexData(m_allocator, "SVisualAidWidget::theVertexData");
+ nvvector<QT3DSI8> theIndexData(m_allocator, "SVisualAidWidget::theIndexData");
+
+ const int ringVertices = 96;
+ int skip = 1;
+ int v = 0;
+
+ // disc with parallel lines
+ for (int i = 0; i < ringVertices; ++i) {
+ float x = directionalLightRadius * cos(2.0 * M_PI * i / ringVertices);
+ float y = directionalLightRadius * sin(2.0 * M_PI * i / ringVertices);
+ theVertexData.push_back(QT3DSVec3(x, y, 0.0));
+ if (i > 0) {
+ theIndexData.push_back(v - skip);
+ theIndexData.push_back(v);
+ skip = 1;
+ }
+ ++v;
+ if ((i%8) == 1) {
+ theVertexData.push_back(QT3DSVec3(x, y, -directionalLightLength));
+ theIndexData.push_back(v - 1);
+ theIndexData.push_back(v);
+ skip = 2;
+ ++v;
+ }
+ }
+ theIndexData.push_back(v - skip);
+ theIndexData.push_back(0);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ getLightAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU8, theIndexData.size(),
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderInputAssembler *SVisualAidWidget::createPointLight(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr("PointLight");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec3> theVertexData(m_allocator, "SVisualAidWidget::theVertexData");
+ nvvector<QT3DSI16> theIndexData(m_allocator, "SVisualAidWidget::theIndexData");
+
+ const int latSlices = 7;
+ const float innerRadius = pointLightOuterRadius / 3.0f;
+ const float outerRadius = pointLightOuterRadius;
+
+#define lngVar(n) ((n&1) ? -3.0 : 3.0)
+#define latVar(n) ((n&1) ? -2.0 : 2.0)
+
+ int v = 0;
+
+ for (int i = 0; i <= latSlices; ++i) {
+ float lat = (175 / latSlices) * i + 5 + latVar(i);
+ int longSlices = latSlices * 2 * sin(lat / 180 * M_PI);
+ lat -= 90.0;
+
+ for (int j = 0; j < longSlices; ++j) {
+ float lng = (360 / longSlices) * j + ((j & 1) ? -180.0 : 0.0);
+
+ float q = cos((lngVar(j) + lat) / 180 * M_PI);
+ float x = cos(2.0 * lng / 180 * M_PI) * q;
+ float y = sin((lngVar(j) + lat) / 180 * M_PI);
+ float z = sin(2.0 * lng / 180 * M_PI) * q;
+
+ theVertexData.push_back(QT3DSVec3(x, y, z) * innerRadius);
+ theVertexData.push_back(QT3DSVec3(x, y, z) * outerRadius);
+ theIndexData.push_back(v);
+ theIndexData.push_back(v + 1);
+ v += 2;
+ }
+ }
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ getLightAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU16, theIndexData.size() * 2,
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderInputAssembler *SVisualAidWidget::createAreaLight(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr("AreaLight");
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval)
+ return retval;
+
+ nvvector<QT3DSVec3> theVertexData(m_allocator, "SVisualAidWidget::theVertexData");
+ nvvector<QT3DSI8> theIndexData(m_allocator, "SVisualAidWidget::theIndexData");
+
+ theVertexData.push_back(QT3DSVec3(-1, -1, 0));
+ theVertexData.push_back(QT3DSVec3(1,- 1, 0));
+ theVertexData.push_back(QT3DSVec3(1, 1, 0));
+ theVertexData.push_back(QT3DSVec3(-1, 1, 0));
+ theIndexData.push_back(0); theIndexData.push_back(1);
+ theIndexData.push_back(1); theIndexData.push_back(2);
+ theIndexData.push_back(2); theIndexData.push_back(3);
+ theIndexData.push_back(3); theIndexData.push_back(0);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ getLightAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+ NVRenderIndexBuffer *theIndexBuffer = &inWidgetContext.GetOrCreateIndexBuffer(
+ theItemName, NVRenderComponentTypes::QT3DSU8, theIndexData.size(),
+ toU8DataRef(theIndexData.begin(), theIndexData.size()));
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+NVRenderShaderProgram *SVisualAidWidget::createRenderShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString itemName = inRenderContext.GetStringTable().RegisterStr("LineShader");
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(itemName);
+ if (retval)
+ return retval;
+
+ IShaderProgramGenerator &generator(inWidgetContext.GetProgramGenerator());
+ generator.BeginProgram();
+ IShaderStageGenerator &vertGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &fragGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Fragment));
+ vertGenerator.AddIncoming("attr_pos", "vec3");
+ vertGenerator.AddUniform("mvpMatrix", "mat4");
+
+ vertGenerator.Append("void main() {");
+ vertGenerator.Append("\tgl_Position = mvpMatrix * vec4(attr_pos, 1.0);");
+ vertGenerator.Append("}");
+
+ fragGenerator.AddUniform("color", "vec3");
+ fragGenerator.AddUniform("opacity", "float");
+ fragGenerator.Append("void main() {");
+ fragGenerator.Append("\tgl_FragColor.rgb = color;");
+ fragGenerator.Append("\tgl_FragColor.a = opacity;");
+ fragGenerator.Append("}");
+
+ return inWidgetContext.CompileAndStoreShader(itemName);
+}
+
+NVRenderShaderProgram *SVisualAidWidget::createRenderCameraShader(
+ IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext)
+{
+ CRegisteredString itemName = inRenderContext.GetStringTable().RegisterStr("CameraRenderShader");
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(itemName);
+ if (retval)
+ return retval;
+
+ IShaderProgramGenerator &generator(inWidgetContext.GetProgramGenerator());
+ generator.BeginProgram();
+ IShaderStageGenerator &vertGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &fragGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Fragment));
+ vertGenerator.AddIncoming("attr_pos", "vec4");
+ vertGenerator.AddUniform("invProjMatrix", "mat4");
+ vertGenerator.AddUniform("mvpMatrix", "mat4");
+ vertGenerator.AddUniform("orthographic", "int");
+
+ vertGenerator.Append("void main() {");
+ vertGenerator.Append("\tvec4 pos = vec4(0.0, 0.0, 0.0, 1.0);");
+ vertGenerator.Append("\tif (attr_pos.w != 0.0) {");
+ vertGenerator.Append("\t\tpos = invProjMatrix * attr_pos;");
+ vertGenerator.Append("\t\tpos = pos / pos.w;");
+ vertGenerator.Append("\t}");
+ vertGenerator.Append("\tgl_Position = mvpMatrix * vec4(pos.xyz, 1.0);");
+ vertGenerator.Append("}");
+
+ fragGenerator.AddUniform("color", "vec3");
+ fragGenerator.AddUniform("opacity", "float");
+ fragGenerator.Append("void main() {");
+ fragGenerator.Append("\tgl_FragColor.rgb = color;");
+ fragGenerator.Append("\tgl_FragColor.a = opacity;");
+ fragGenerator.Append("}");
+
+ return inWidgetContext.CompileAndStoreShader(itemName);
+}
+
+NVRenderShaderProgram *SVisualAidWidget::createBillboardShader(
+ IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext)
+{
+ CRegisteredString itemName = inRenderContext.GetStringTable().RegisterStr("BillboardShader");
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(itemName);
+ if (retval)
+ return retval;
+
+ IShaderProgramGenerator &generator(inWidgetContext.GetProgramGenerator());
+ generator.BeginProgram();
+ IShaderStageGenerator &vertGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &fragGenerator(
+ *generator.GetStage(ShaderGeneratorStages::Fragment));
+
+ vertGenerator.AddIncoming("attr_pos", "vec3");
+ vertGenerator.AddIncoming("attr_tc", "vec2");
+ vertGenerator.AddUniform("billboardMatrix", "mat4");
+ vertGenerator.AddUniform("anchor", "vec2");
+ vertGenerator.AddOutgoing("texcoord", "vec2");
+
+ vertGenerator.Append("void main() {");
+ vertGenerator.Append("texcoord = attr_tc;");
+ vertGenerator.Append("\tgl_Position = billboardMatrix * vec4(attr_pos.x + anchor.x, " \
+ "\tattr_pos.y + anchor.y, attr_pos.z, 1.0);");
+ vertGenerator.Append("}");
+
+ fragGenerator.AddIncoming("texcoord", "vec2");
+ fragGenerator.AddUniform("color", "vec3");
+ fragGenerator.AddUniform("opacity", "float");
+ fragGenerator.AddUniform("billboardTexture", "sampler2D");
+ fragGenerator.Append("void main() {");
+ fragGenerator.Append("\tgl_FragColor.rgb = texture2D(billboardTexture, texcoord).rgb;");
+ fragGenerator.Append("\tgl_FragColor.a = texture2D(billboardTexture, texcoord).a * opacity;");
+ //fragGenerator.Append("\tif (gl_FragColor.a <= 0.005) discard; ");
+ fragGenerator.Append("}");
+
+ return inWidgetContext.CompileAndStoreShader(itemName);
+}
+
+void SVisualAidWidget::renderCamera(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ SCamera *camera = static_cast<SCamera *>(node);
+ Q_ASSERT(camera);
+ QT3DSMat44 projection = camera->m_Projection;
+ QT3DSMat44 ip = projection.getInverse();
+
+ if (!m_cameraBox)
+ m_cameraBox = createCameraBox(inWidgetContext, inRenderContext);
+ if (!m_renderCameraShader)
+ m_renderCameraShader = createRenderCameraShader(inWidgetContext, inRenderContext);
+
+ inRenderContext.SetInputAssembler(m_cameraBox);
+ inRenderContext.SetActiveShader(m_renderCameraShader);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetCullingEnabled(false);
+
+ SWidgetRenderSetupResult theSetup(inWidgetContext, *node, RenderWidgetModes::Local);
+
+ m_renderCameraShader->SetPropertyValue("orthographic",
+ camera->m_Flags.IsOrthographic() ? 1 : 0);
+ m_renderCameraShader->SetPropertyValue("invProjMatrix", ip);
+ m_renderCameraShader->SetPropertyValue("mvpMatrix", theSetup.m_PureProjection
+ * theSetup.m_WidgetInfo.m_NodeParentToCamera
+ * node->m_GlobalTransform);
+
+ ::CColor color = CStudioPreferences::GetSingleBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+
+ m_renderCameraShader->SetPropertyValue("color", m_selected ? colorVec : QT3DSVec3(1, 1, 1));
+ inRenderContext.SetBlendFunction(
+ qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::One,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ inRenderContext.SetBlendEquation(
+ qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+ inRenderContext.SetBlendingEnabled(true);
+ m_renderCameraShader->SetPropertyValue("opacity", 0.5f);
+
+ inRenderContext.Draw(NVRenderDrawMode::Lines, m_cameraBox->GetIndexCount(), 0);
+}
+
+void SVisualAidWidget::renderBillboard(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ if (!m_billboard)
+ m_billboard = createBillboard(inWidgetContext, inRenderContext);
+ if (!m_billboardShader)
+ m_billboardShader = createBillboardShader(inWidgetContext, inRenderContext);
+
+ if (!m_billboardCameraTexture) {
+ QImage img(":/images/Asset-Camera-Pick.png");
+ img = img.mirrored();
+ img = img.rgbSwapped();
+ m_billboardCameraTexture = inRenderContext.CreateTexture2D();
+ NVDataRef<QT3DSU8> data(img.bits(), img.byteCount());
+ m_billboardCameraTexture->SetTextureData(data, 0, img.width(), img.height(),
+ NVRenderTextureFormats::RGBA8);
+
+ img = QImage(":/images/Asset-Light-Pick.png");
+ img = img.mirrored();
+ img = img.rgbSwapped();
+ m_billboardLightTexture = inRenderContext.CreateTexture2D();
+ data = NVDataRef<QT3DSU8>(img.bits(), img.byteCount());
+ m_billboardLightTexture->SetTextureData(data, 0, img.width(), img.height(),
+ NVRenderTextureFormats::RGBA8);
+ }
+
+ inRenderContext.SetInputAssembler(m_billboard);
+ inRenderContext.SetActiveShader(m_billboardShader);
+ inRenderContext.SetBlendingEnabled(true);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetCullingEnabled(false);
+ inRenderContext.SetBlendFunction(
+ qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::Zero,
+ qt3ds::render::NVRenderDstBlendFunc::One));
+ inRenderContext.SetBlendEquation(
+ qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+
+ SWidgetRenderSetupResult theSetup(inWidgetContext, *node, RenderWidgetModes::Local);
+ QT3DSMat44 billboardMatrix = QT3DSMat44::createIdentity();
+ billboardMatrix.setPosition((theSetup.m_WidgetInfo.m_NodeParentToCamera
+ * node->m_GlobalTransform).getPosition());
+ billboardMatrix.scale(QT3DSVec4(8, 8, 1, 1));
+ billboardMatrix = theSetup.m_PureProjection * billboardMatrix;
+
+ m_billboardShader->SetPropertyValue("billboardMatrix", billboardMatrix);
+ m_billboardShader->SetPropertyValue("billboardTexture",
+ node->m_Type == GraphObjectTypes::Camera
+ ? m_billboardCameraTexture.mPtr
+ : m_billboardLightTexture.mPtr);
+ m_billboardShader->SetPropertyValue("opacity", m_selected ? 0.9f : 0.7f);
+ m_billboardShader->SetPropertyValue("anchor", QT3DSVec2(-1, -1));
+
+ inRenderContext.Draw(NVRenderDrawMode::Triangles, m_billboard->GetIndexCount(), 0);
+}
+
+void SVisualAidWidget::renderLight(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ SLight *light = static_cast<SLight *>(node);
+ Q_ASSERT(light);
+
+ NVRenderInputAssembler *input = nullptr;
+ QT3DSMat44 areaScaleMatrix = QT3DSMat44::createIdentity();
+
+ switch (light->m_LightType) {
+ case RenderLightTypes::Directional: {
+ if (!m_directionalLight)
+ m_directionalLight = createDirectionalLight(inWidgetContext, inRenderContext);
+ input = m_directionalLight;
+ } break;
+
+ case RenderLightTypes::Point: {
+ if (!m_pointLight)
+ m_pointLight = createPointLight(inWidgetContext, inRenderContext);
+ input = m_pointLight;
+ } break;
+
+ case RenderLightTypes::Area: {
+ const float w = light->m_AreaWidth * 0.5f;
+ const float h = light->m_AreaHeight * 0.5f;
+ areaScaleMatrix.scale(QT3DSVec4(w, h, 0.0, 1.0));
+ if (!m_areaLight)
+ m_areaLight = createAreaLight(inWidgetContext, inRenderContext);
+ input = m_areaLight;
+ } break;
+
+ default:
+ break;
+ }
+ if (input) {
+ if (!m_renderShader)
+ m_renderShader = createRenderShader(inWidgetContext, inRenderContext);
+ inRenderContext.SetInputAssembler(input);
+ inRenderContext.SetActiveShader(m_renderShader);
+ inRenderContext.SetBlendingEnabled(false);
+ inRenderContext.SetDepthTestEnabled(true);
+ inRenderContext.SetDepthWriteEnabled(false);
+ inRenderContext.SetCullingEnabled(false);
+
+ SWidgetRenderSetupResult theSetup(inWidgetContext, *node, RenderWidgetModes::Local);
+
+ m_renderShader->SetPropertyValue("mvpMatrix", theSetup.m_PureProjection
+ * theSetup.m_WidgetInfo.m_NodeParentToCamera
+ * node->m_GlobalTransform
+ * areaScaleMatrix);
+
+ ::CColor color = CStudioPreferences::GetSingleBoundingBoxColor();
+ QT3DSVec3 colorVec(color.GetRed() / 255.f,
+ color.GetGreen() / 255.f,
+ color.GetBlue() / 255.f);
+
+ m_renderShader->SetPropertyValue("color", m_selected ? colorVec : QT3DSVec3(1, 1, 1));
+
+ inRenderContext.SetBlendFunction(
+ qt3ds::render::NVRenderBlendFunctionArgument(
+ qt3ds::render::NVRenderSrcBlendFunc::SrcAlpha,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ qt3ds::render::NVRenderSrcBlendFunc::One,
+ qt3ds::render::NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ inRenderContext.SetBlendEquation(
+ qt3ds::render::NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+ inRenderContext.SetBlendingEnabled(true);
+ m_renderShader->SetPropertyValue("opacity", 0.5f);
+ inRenderContext.Draw(NVRenderDrawMode::Lines, input->GetIndexCount(), 0);
+ }
+}
+
+void SVisualAidWidget::SetNode(SNode *inNode)
+{
+ if (inNode == m_node)
+ return;
+ m_node = inNode;
+}
+
+void SVisualAidWidget::Render(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ switch (m_node->m_Type) {
+ case GraphObjectTypes::Camera:
+ renderCamera(m_node, inWidgetContext, inRenderContext);
+ break;
+
+ case GraphObjectTypes::Light:
+ renderLight(m_node, inWidgetContext, inRenderContext);
+ break;
+
+ default:
+ break;
+ }
+ renderBillboard(m_node, inWidgetContext, inRenderContext);
+}
+
+bool SVisualAidWidget::pick(IRenderWidgetContext &inWidgetContext, float &dist, QT3DSVec2 viewport,
+ QT3DSVec2 pos)
+{
+ SWidgetRenderSetupResult theSetup(inWidgetContext, *m_node, RenderWidgetModes::Local);
+ SCamera *pickCamera = theSetup.m_WidgetInfo.m_Camera;
+ QT3DSMat44 pip = pickCamera->m_Projection.getInverse();
+ float x = pos.x / viewport.x;
+ float y = 1.0f - pos.y / viewport.y;
+ x = 2.0 * x - 1.0;
+ y = 2.0 * y - 1.0;
+
+ QT3DSVec2 anchor(-1, -1);
+
+ QT3DSVec4 n(x, y, -1, 1), f(x, y, 1, 1);
+ QT3DSVec4 np = pip.transform(n);
+ QT3DSVec4 fp = pip.transform(f);
+
+ np = np * (1.0 / np.w);
+ fp = fp * (1.0 / fp.w);
+
+ QT3DSMat44 billboardMatrix = QT3DSMat44::createIdentity();
+ billboardMatrix.setPosition((theSetup.m_WidgetInfo.m_NodeParentToCamera
+ *m_node->m_GlobalTransform).getPosition());
+ billboardMatrix.scale(QT3DSVec4(8, 8, 1, 1));
+
+ QT3DSMat44 toBillboard = billboardMatrix.getInverse();
+
+ np = toBillboard.transform(np);
+ fp = toBillboard.transform(fp);
+
+ QT3DSVec3 c = np.getXYZ();
+ QT3DSVec3 dir = (fp - np).getXYZ();
+ dir.normalize();
+
+ QT3DSVec2 min(anchor.x - 1, anchor.y - 1);
+ QT3DSVec2 max(anchor.x + 1, anchor.y + 1);
+
+ if (qAbs(dir.z) > fepsilon) {
+ QT3DSVec2 xy = QT3DSVec2(dir.x / dir.z, dir.y / dir.z);
+ QT3DSVec2 p = xy * (-c.z) + QT3DSVec2(c.x, c.y);
+ if (p.x >= min.x && p.x <= max.x &&
+ p.y >= min.y && p.y <= max.y) {
+ QT3DSVec3 ip = QT3DSVec3(p.x, p.y, 0) - c;
+ dist = ip.magnitude();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+SVisualAidWidget &SVisualAidWidget::CreateVisualAidWidget(NVAllocatorCallback &inAlloc)
+{
+ return *QT3DS_NEW(inAlloc, SVisualAidWidget)(inAlloc);
+}
+
+}
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.h b/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.h
new file mode 100644
index 00000000..8a91fa9c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioVisualAidWidget.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** 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 QT3DS_STUDIO_VISUAL_AID_WIDGET_H
+#define QT3DS_STUDIO_VISUAL_AID_WIDGET_H
+
+#include "StudioWidget.h"
+#include "foundation/Qt3DSContainers.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+
+#include "Qt3DSRenderNode.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+
+
+namespace qt3ds {
+namespace widgets {
+
+struct SVisualAidWidget
+{
+ SNode *m_node;
+ NVScopedRefCounted<NVRenderInputAssembler> m_billboard;
+ NVScopedRefCounted<NVRenderInputAssembler> m_cameraBox;
+ NVScopedRefCounted<NVRenderInputAssembler> m_directionalLight;
+ NVScopedRefCounted<NVRenderInputAssembler> m_pointLight;
+ NVScopedRefCounted<NVRenderInputAssembler> m_areaLight;
+ NVScopedRefCounted<NVRenderShaderProgram> m_renderCameraShader;
+ NVScopedRefCounted<NVRenderShaderProgram> m_renderShader;
+ NVScopedRefCounted<NVRenderShaderProgram> m_billboardShader;
+ NVScopedRefCounted<NVRenderTexture2D> m_billboardCameraTexture;
+ NVScopedRefCounted<NVRenderTexture2D> m_billboardLightTexture;
+
+ bool m_selected;
+ NVAllocatorCallback &m_allocator;
+ volatile QT3DSI32 mRefCount;
+
+ SVisualAidWidget(NVAllocatorCallback &inAlloc);
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_allocator)
+
+ NVConstDataRef<NVRenderVertexBufferEntry> getCameraBoxAttributesAndStride(QT3DSU32 &stride);
+ NVConstDataRef<NVRenderVertexBufferEntry> getLightAttributesAndStride(QT3DSU32 &stride);
+ NVConstDataRef<NVRenderVertexBufferEntry> getBillboardAttributesAndStride(QT3DSU32 &stride);
+ void renderCamera(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ void renderLight(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ void renderBillboard(SNode *node, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ void SetNode(SNode *inNode);
+ NVRenderShaderProgram *createRenderShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderShaderProgram *createBillboardShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderShaderProgram *createRenderCameraShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderInputAssembler *createCameraBox(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderInputAssembler *createBillboard(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderInputAssembler *createDirectionalLight(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderInputAssembler *createPointLight(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ NVRenderInputAssembler *createAreaLight(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ void Render(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext);
+
+ bool pick(IRenderWidgetContext &inWidgetContext, float &dist,
+ QT3DSVec2 viewport, QT3DSVec2 pos);
+
+ void setSelected(bool selected)
+ {
+ m_selected = selected;
+ }
+
+ static SVisualAidWidget &CreateVisualAidWidget(NVAllocatorCallback &inAlloc);
+};
+
+}
+}
+
+
+#endif // QT3DS_STUDIO_VISUAL_AID_WIDGET_H
diff --git a/src/Authoring/Qt3DStudio/Render/StudioWidget.cpp b/src/Authoring/Qt3DStudio/Render/StudioWidget.cpp
new file mode 100644
index 00000000..cccce4d4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioWidget.cpp
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioWidget.h"
+#include "Qt3DSRenderWidgets.h"
+#include "Qt3DSRenderContextCore.h"
+#include "render/Qt3DSRenderContext.h"
+#include "foundation/Qt3DSContainers.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+
+using namespace qt3ds::widgets;
+
+void IStudioWidget::CreateRect(QT3DSVec3 rectStart, QT3DSVec3 rectEnd, QT3DSVec3 orth1, QT3DSF32 axisHalfWidth,
+ QT3DSF32 inColorIndex, TResultVecType &outResult)
+{
+ outResult.push_back(QT3DSVec4(rectStart + orth1 * axisHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(rectEnd + orth1 * axisHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(rectEnd - orth1 * axisHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(rectEnd - orth1 * axisHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(rectStart - orth1 * axisHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(rectStart + orth1 * axisHalfWidth, inColorIndex));
+}
+
+void IStudioWidget::CreateTriangle(QT3DSVec3 triStart, QT3DSVec3 triEnd, QT3DSVec3 orth1, QT3DSF32 triHalfWidth,
+ QT3DSF32 inColorIndex, TResultVecType &outResult)
+{
+ outResult.push_back(QT3DSVec4(triStart + orth1 * triHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(triStart - orth1 * triHalfWidth, inColorIndex));
+ outResult.push_back(QT3DSVec4(triEnd, inColorIndex));
+}
+
+NVRenderInputAssembler *IStudioWidget::CreateRingedDisc(
+ NVAllocatorCallback &inAllocator, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, QT3DSVec3 inDirection, QT3DSVec3 inCenterPt, QT3DSF32 inInnerRadius,
+ QT3DSF32 inOuterRadius, QT3DSF32 inDiscColor, QT3DSF32 inRingColor, const char *inItemName)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inItemName);
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval) {
+ return retval;
+ }
+ TResultVecType theVertexData(inAllocator, "STranslationWidget::theVertexData");
+ QT3DS_ASSERT(inInnerRadius < inOuterRadius);
+ QT3DSI32 numSubDivisions = 50;
+ QT3DSF32 arcRad = 360.0f / (QT3DSF32)numSubDivisions;
+ TORAD(arcRad);
+ QT3DSVec3 tempCross = inDirection.cross(QT3DSVec3(0, 1, 0));
+ if (tempCross.magnitudeSquared() < .05f)
+ tempCross = inDirection.cross(QT3DSVec3(1, 0, 0));
+
+ QT3DSVec3 upDir = inDirection.cross(tempCross);
+ QT3DSVec3 leftDir = upDir.cross(inDirection);
+ upDir.normalize();
+ leftDir.normalize();
+
+ for (QT3DSI32 idx = 0, numLooper = numSubDivisions; idx < numLooper; ++idx) {
+ QT3DSF32 startDeg = idx * 360.0f / numSubDivisions;
+ QT3DSF32 endDeg = (idx + 1) * 360.0f / numSubDivisions;
+ QT3DSF32 startRad(startDeg);
+ QT3DSF32 endRad(endDeg);
+ TORAD(startRad);
+ TORAD(endRad);
+ QT3DSF32 startSin = NVSin(startRad);
+ QT3DSF32 endSin = NVSin(endRad);
+ QT3DSF32 startCos = NVCos(startRad);
+ QT3DSF32 endCos = NVCos(endRad);
+
+ QT3DSVec3 startDir = startSin * upDir + startCos * leftDir;
+ QT3DSVec3 endDir = endSin * upDir + endCos * leftDir;
+
+ QT3DSVec3 discStart = inCenterPt + startDir * inInnerRadius;
+ QT3DSVec3 discEnd = inCenterPt + endDir * inInnerRadius;
+ QT3DSVec3 ringStart = inCenterPt + startDir * inOuterRadius;
+ QT3DSVec3 ringEnd = inCenterPt + endDir * inOuterRadius;
+
+ // Create the Triangles
+ // disc first
+ theVertexData.push_back(QT3DSVec4(inCenterPt, inDiscColor));
+ theVertexData.push_back(QT3DSVec4(discStart, inDiscColor));
+ theVertexData.push_back(QT3DSVec4(discEnd, inDiscColor));
+
+ // Now two tris for the ring
+ theVertexData.push_back(QT3DSVec4(discStart, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringStart, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(ringEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(discEnd, inRingColor));
+ theVertexData.push_back(QT3DSVec4(discStart, inRingColor));
+ }
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ IStudioWidget::GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+}
+
+// Create an axis with a triangle at the top. Really we create two axis that are orthogonal to each
+// other.
+NVRenderInputAssembler *
+IStudioWidget::CreateAxis(NVAllocatorCallback &inAllocator, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, const QT3DSVec3 &inAxisDirection,
+ QT3DSF32 inAxisStartOffset, QT3DSF32 inAxisLength, QT3DSF32 inTriLength,
+ QT3DSF32 inAxisWidth, QT3DSF32 inTriWidth, const char *inAxisName)
+{
+ CRegisteredString theItemName = inRenderContext.GetStringTable().RegisterStr(inAxisName);
+ NVRenderInputAssembler *retval = inWidgetContext.GetInputAssembler(theItemName);
+ if (retval) {
+ return retval;
+ }
+
+ TResultVecType theVertexData(inAllocator, "STranslationWidget::theVertexData");
+ QT3DSVec3 orth1 = inAxisDirection.cross(QT3DSVec3(0, 0, 1));
+ if (orth1.magnitudeSquared() < .05f)
+ orth1 = inAxisDirection.cross(QT3DSVec3(0, 1, 0));
+ QT3DSVec3 orth2 = inAxisDirection.cross(orth1);
+
+ // Draw a rect that starts at inAxisStartOffset
+ QT3DSVec3 rectStart = inAxisDirection * inAxisStartOffset;
+ // Rect end is also tri start, obviously
+ QT3DSVec3 rectEnd = inAxisDirection * (inAxisStartOffset + inAxisLength);
+ QT3DSVec3 triEnd = inAxisDirection * (inAxisStartOffset + inAxisLength + inTriLength);
+
+ QT3DSF32 axisHalfWidth = inAxisWidth / 2.0f;
+ QT3DSF32 triHalfWidth = inTriWidth / 2.0f;
+ CreateRect(rectStart, rectEnd, orth1, axisHalfWidth, 0.0f, theVertexData);
+ CreateTriangle(rectEnd, triEnd, orth1, triHalfWidth, 0.0f, theVertexData);
+ CreateRect(rectStart, rectEnd, orth2, axisHalfWidth, 0.0f, theVertexData);
+ CreateTriangle(rectEnd, triEnd, orth2, triHalfWidth, 0.0f, theVertexData);
+
+ QT3DSU32 stride;
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout = &inWidgetContext.CreateAttributeLayout(
+ IStudioWidget::GetVertexBufferAttributesAndStride(stride));
+ NVRenderVertexBuffer *theVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theItemName, stride, toU8DataRef(theVertexData.begin(), theVertexData.size()));
+
+ retval = &inWidgetContext.GetOrCreateInputAssembler(
+ theItemName, theAttribLayout, toConstDataRef(&theVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return retval;
+};
+
+SWidgetRenderSetupResult::SWidgetRenderSetupResult(IRenderWidgetContext &inWidgetContext,
+ SNode &inNode,
+ RenderWidgetModes::Enum inWidgetMode)
+{
+ m_WidgetInfo =
+ inWidgetContext.GetWidgetRenderInformation(inNode, QT3DSVec3(0, 0, 0), inWidgetMode);
+ QT3DSMat44 theTranslationScale(QT3DSMat44::createIdentity());
+ QT3DSMat33 theRotationMult(QT3DSMat33::createIdentity());
+ bool includeNodeRotation = inWidgetMode == RenderWidgetModes::Local ? true : false;
+ if (includeNodeRotation) {
+ QT3DSMat44 theNodeRotation;
+ inNode.CalculateRotationMatrix(theNodeRotation);
+ if (inNode.m_Flags.IsLeftHanded()) {
+ SNode::FlipCoordinateSystem(theNodeRotation);
+ }
+ theRotationMult =
+ QT3DSMat33(theNodeRotation.column0.getXYZ(), theNodeRotation.column1.getXYZ(),
+ theNodeRotation.column2.getXYZ());
+ }
+
+ QT3DSMat33 theRotationMatrix = m_WidgetInfo.m_NormalMatrix * theRotationMult;
+ QT3DSMat33 theScaleMatrix(QT3DSMat33::createIdentity());
+ theScaleMatrix.column0[0] = m_WidgetInfo.m_Scale;
+ theScaleMatrix.column1[1] = m_WidgetInfo.m_Scale;
+ theScaleMatrix.column2[2] = m_WidgetInfo.m_Scale;
+ QT3DSMat33 theCombined = theRotationMatrix * theScaleMatrix;
+ theTranslationScale.column0 = QT3DSVec4(theCombined.column0, 0.0f);
+ theTranslationScale.column1 = QT3DSVec4(theCombined.column1, 0.0f);
+ theTranslationScale.column2 = QT3DSVec4(theCombined.column2, 0.0f);
+ theTranslationScale.column3.x = m_WidgetInfo.m_Position.x;
+ theTranslationScale.column3.y = m_WidgetInfo.m_Position.y;
+ theTranslationScale.column3.z = m_WidgetInfo.m_Position.z;
+ m_TranslationScale = theTranslationScale;
+ m_PureProjection = m_WidgetInfo.m_PureProjection;
+
+ QT3DSMat44 theCameraTransScale(QT3DSMat44::createIdentity());
+ theCameraTransScale.column0 = QT3DSVec4(m_WidgetInfo.m_LookAtMatrix.column0, 0.0f);
+ theCameraTransScale.column1 = QT3DSVec4(m_WidgetInfo.m_LookAtMatrix.column1, 0.0f);
+ theCameraTransScale.column2 = QT3DSVec4(m_WidgetInfo.m_LookAtMatrix.column2, 0.0f);
+ theCameraTransScale.column3.x = m_WidgetInfo.m_Position.x;
+ theCameraTransScale.column3.y = m_WidgetInfo.m_Position.y;
+ theCameraTransScale.column3.z = m_WidgetInfo.m_Position.z;
+ theCameraTransScale.column0[0] = m_WidgetInfo.m_Scale;
+ theCameraTransScale.column1[1] = m_WidgetInfo.m_Scale;
+ theCameraTransScale.column2[2] = m_WidgetInfo.m_Scale;
+ m_CameraTranslationScale = theCameraTransScale;
+ m_SetupResult = m_WidgetInfo.m_LayerProjection * theTranslationScale;
+}
+
+CRegisteredString IStudioWidget::GetSharedShaderName(IStringTable &inStrTable)
+{
+ return inStrTable.RegisterStr("IStudioWidget Shader");
+}
+
+CRegisteredString IStudioWidget::GetSharedPickShaderName(IStringTable &inStrTable)
+{
+ return inStrTable.RegisterStr("IStudioWidget Pick Shader");
+}
+
+NVRenderShaderProgram *IStudioWidget::CreateWidgetShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theSharedName(GetSharedShaderName(inRenderContext.GetStringTable()));
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(theSharedName);
+ if (retval)
+ return retval;
+ qt3ds::render::IShaderProgramGenerator &theGenerator(inWidgetContext.GetProgramGenerator());
+ theGenerator.BeginProgram();
+ qt3ds::render::IShaderStageGenerator &theVertexGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Vertex));
+ qt3ds::render::IShaderStageGenerator &theFragmentGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Fragment));
+ theVertexGenerator.AddIncoming("attr_pos", "vec3");
+ theVertexGenerator.AddIncoming("attr_color_index", "float");
+ theVertexGenerator.AddOutgoing("output_color_index", "float");
+ theVertexGenerator.AddUniform("model_view_projection", "mat4");
+ // These are required in order to scale the scale widget the way we want to scale it.
+ theVertexGenerator.AddUniform("attr_pos_add_start", "float");
+ theVertexGenerator.AddUniform("attr_pos_add_amount", "vec3");
+ theVertexGenerator.Append("void main() {");
+ theVertexGenerator
+ << "\tvec3 thePos = attr_pos;" << Endl
+ << "\tif ( length(thePos) > attr_pos_add_start ) thePos = thePos + attr_pos_add_amount;"
+ << Endl;
+ theVertexGenerator.Append("\tgl_Position = model_view_projection * vec4(thePos, 1.0);");
+ theVertexGenerator.Append("\toutput_color_index = attr_color_index;");
+ theVertexGenerator.Append("}");
+ theFragmentGenerator.AddUniform("color0", "vec3");
+ theFragmentGenerator.AddUniform("color1", "vec3");
+ theFragmentGenerator.Append("void main() {");
+ theFragmentGenerator.Append("\tgl_FragColor.rgb = output_color_index > 0.0 ? color1 : color0;");
+ theFragmentGenerator.Append("\tgl_FragColor.a = 1.0;");
+ theFragmentGenerator.Append("}");
+ return inWidgetContext.CompileAndStoreShader(theSharedName);
+}
+
+NVRenderShaderProgram *IStudioWidget::CreateWidgetPickShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext)
+{
+ CRegisteredString theSharedName(GetSharedPickShaderName(inRenderContext.GetStringTable()));
+ NVRenderShaderProgram *retval = inWidgetContext.GetShader(theSharedName);
+ if (retval)
+ return retval;
+ qt3ds::render::IShaderProgramGenerator &theGenerator(inWidgetContext.GetProgramGenerator());
+ theGenerator.BeginProgram();
+ qt3ds::render::IShaderStageGenerator &theVertexGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Vertex));
+ qt3ds::render::IShaderStageGenerator &theFragmentGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Fragment));
+ theVertexGenerator.AddIncoming("attr_pos", "vec3");
+ theVertexGenerator.AddUniform("model_view_projection", "mat4");
+ theVertexGenerator.Append("void main() {");
+ theVertexGenerator.Append("\tgl_Position = model_view_projection * vec4(attr_pos, 1.0);");
+ theVertexGenerator.Append("}");
+ theFragmentGenerator.AddUniform("object_id", "int");
+ theFragmentGenerator.Append("void main() {");
+ if (inRenderContext.GetRenderContextType() == NVRenderContextValues::GLES2) {
+ theFragmentGenerator.Append("int moddiv = object_id / 256;");
+ theFragmentGenerator.Append(
+ "\tgl_FragColor.r = float(object_id - moddiv * 256)/255.0;");
+ } else {
+ theFragmentGenerator.Append("\tgl_FragColor.r = float(object_id % 256)/255.0;");
+ }
+ theFragmentGenerator.Append("\tgl_FragColor.g = float(object_id / 256)/255.0;");
+ theFragmentGenerator.Append("\tgl_FragColor.b = 0.0;");
+ theFragmentGenerator.Append("\tgl_FragColor.a = 1.0;");
+ theFragmentGenerator.Append("}");
+ return inWidgetContext.CompileAndStoreShader(theSharedName);
+}
+
+NVConstDataRef<qt3ds::render::NVRenderVertexBufferEntry>
+IStudioWidget::GetVertexBufferAttributesAndStride(QT3DSU32 &stride)
+{
+ static qt3ds::render::NVRenderVertexBufferEntry theEntries[] = {
+ qt3ds::render::NVRenderVertexBufferEntry("attr_pos", qt3ds::render::NVRenderComponentTypes::QT3DSF32,
+ 3),
+ qt3ds::render::NVRenderVertexBufferEntry("attr_color_index",
+ qt3ds::render::NVRenderComponentTypes::QT3DSF32, 1, 12),
+ };
+
+ stride = 3 * sizeof(QT3DSF32) + 1 * sizeof(QT3DSF32);
+
+ return toConstDataRef(theEntries, 2);
+}
diff --git a/src/Authoring/Qt3DStudio/Render/StudioWidget.h b/src/Authoring/Qt3DStudio/Render/StudioWidget.h
new file mode 100644
index 00000000..0d40f195
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioWidget.h
@@ -0,0 +1,139 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_RENDERER_WIDGET_H
+#define QT3DS_STUDIO_RENDERER_WIDGET_H
+#pragma once
+#include "Qt3DSRenderWidgets.h"
+#include "foundation/Qt3DSRefCounted.h"
+#include "StudioPickValues.h"
+
+namespace qt3ds {
+namespace widgets {
+ using namespace qt3ds::render;
+
+ struct StudioWidgetTypes
+ {
+ enum Enum {
+ Unknown,
+ Translation,
+ Scale,
+ Rotation
+ };
+ };
+
+ // These are also the ids used as colors in the pick image.
+ struct StudioWidgetComponentIds
+ {
+ enum Enum {
+ NoId = 0,
+ XAxis,
+ YAxis,
+ ZAxis,
+ XPlane,
+ YPlane,
+ ZPlane,
+ CameraPlane,
+ LastId,
+ };
+ };
+
+ // Functionality shared between the path widget and the various manipulation gadgets
+ class IStudioWidgetBase : public IRenderWidget, public NVRefCounted
+ {
+ public:
+ virtual void SetNode(SNode &inNode) = 0;
+ virtual void RenderPick(const QT3DSMat44 &inProjPreMult, NVRenderContext &inRenderContext,
+ QSize inWinDimensions) = 0;
+ virtual qt3ds::studio::SStudioPickValue PickIndexToPickValue(QT3DSU32 inPickIndex) = 0;
+ };
+
+ typedef nvvector<QT3DSVec4> TResultVecType;
+
+ struct SWidgetRenderSetupResult
+ {
+ QT3DSMat44 m_TranslationScale;
+ QT3DSMat44 m_CameraTranslationScale;
+ QT3DSMat44 m_PureProjection;
+ SWidgetRenderInformation m_WidgetInfo;
+ QT3DSMat44 m_SetupResult;
+
+ SWidgetRenderSetupResult() {}
+ SWidgetRenderSetupResult(IRenderWidgetContext &inWidgetContext, SNode &inNode,
+ RenderWidgetModes::Enum inWidgetMode);
+ };
+
+ class IStudioWidget : public IStudioWidgetBase
+ {
+ public:
+ static CRegisteredString GetSharedShaderName(IStringTable &inStrTable);
+ static CRegisteredString GetSharedPickShaderName(IStringTable &inStrTable);
+ static NVRenderShaderProgram *CreateWidgetShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ static NVRenderShaderProgram *CreateWidgetPickShader(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext);
+ static NVConstDataRef<qt3ds::render::NVRenderVertexBufferEntry>
+ GetVertexBufferAttributesAndStride(QT3DSU32 &stride);
+ static NVRenderInputAssembler *
+ CreateRingedDisc(NVAllocatorCallback &inAllocator, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, QT3DSVec3 inDirection, QT3DSVec3 inCenterPt,
+ QT3DSF32 inInnerRadius, QT3DSF32 inOuterRadius, QT3DSF32 inDiscColor,
+ QT3DSF32 inRingColor, const char *inItemName);
+ static NVRenderInputAssembler *
+ CreateAxis(NVAllocatorCallback &inAllocator, IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, const QT3DSVec3 &inAxisDirection,
+ QT3DSF32 inAxisStartOffset, QT3DSF32 inAxisLength, QT3DSF32 inTriLength,
+ QT3DSF32 inAxisWidth, QT3DSF32 inTriWidth, const char *inAxisName);
+ static void CreateRect(QT3DSVec3 rectStart, QT3DSVec3 rectEnd, QT3DSVec3 orth1, QT3DSF32 axisHalfWidth,
+ QT3DSF32 inColorIndex, TResultVecType &outResult);
+ static void CreateTriangle(QT3DSVec3 triStart, QT3DSVec3 triEnd, QT3DSVec3 orth1, QT3DSF32 triHalfWidth,
+ QT3DSF32 inColorIndex, TResultVecType &outResult);
+
+ void SetNode(SNode &inNode) override = 0;
+ virtual StudioWidgetTypes::Enum GetWidgetType() const = 0;
+ virtual void SetSubComponentId(int inSubComponentId) = 0;
+ virtual void SetRenderWidgetMode(RenderWidgetModes::Enum inSpace) = 0;
+ virtual RenderWidgetModes::Enum GetRenderWidgetMode() const = 0;
+ // When we render the axis, we can scale the axis item itself
+ virtual void SetAxisScale(const QT3DSVec3 &inNewScale) = 0;
+ // Set the start/end positions of the rotation arc so the rotation gadget can show
+ // the angle and optionally display an angle readout. The start direction should
+ // be a normalized direction in world space, and the angle should be in radians
+ // inRotationAxis is expected to be a normalized direction in world space.
+ virtual void SetRotationEdges(const QT3DSVec3 &inStartDirection, const QT3DSVec3 &inRotationAxis,
+ QT3DSF32 inAngleRad, QT3DSF32 inEndLineLen) = 0;
+ virtual void ClearRotationEdges() = 0;
+
+ static IStudioWidget &CreateTranslationWidget(NVAllocatorCallback &inAlloc);
+ static IStudioWidget &CreateRotationWidget(NVAllocatorCallback &inAlloc);
+ static IStudioWidget &CreateScaleWidget(NVAllocatorCallback &inAlloc);
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/StudioWidgetImpl.h b/src/Authoring/Qt3DStudio/Render/StudioWidgetImpl.h
new file mode 100644
index 00000000..b78c1edf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/StudioWidgetImpl.h
@@ -0,0 +1,346 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 QT3DS_STUDIO_RENDERER_WIDGET_IMPL_H
+#define QT3DS_STUDIO_RENDERER_WIDGET_IMPL_H
+#pragma once
+
+#include "StudioWidget.h"
+#include "foundation/Qt3DSContainers.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+
+#include "Qt3DSRenderNode.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+#include "StudioPreferences.h"
+
+namespace qt3ds {
+namespace widgets {
+
+ typedef nvvector<QT3DSVec4> TResultVecType;
+
+ struct SRotationWedge
+ {
+ QT3DSVec3 m_StartDirection; // world space position
+ QT3DSVec3 m_RotationAxis;
+ QT3DSF32 m_Angle; // angle in radians.
+ QT3DSF32 m_EndLineLen;
+ SRotationWedge() {}
+ SRotationWedge(const QT3DSVec3 &inStartDirection, const QT3DSVec3 &inRotationAxis, QT3DSF32 inAngle,
+ QT3DSF32 inEndLineLen)
+ : m_StartDirection(inStartDirection)
+ , m_RotationAxis(inRotationAxis)
+ , m_Angle(inAngle)
+ , m_EndLineLen(inEndLineLen)
+ {
+ }
+ };
+
+ struct SImmediateVertex
+ {
+ QT3DSVec3 m_Position;
+ QT3DSVec4 m_Color;
+ SImmediateVertex(const QT3DSVec3 &inPosition, const QT3DSVec4 &inColor)
+ : m_Position(inPosition)
+ , m_Color(inColor)
+ {
+ }
+ SImmediateVertex() {}
+ };
+
+ template <StudioWidgetTypes::Enum TWidgetType>
+ struct SStudioWidgetImpl : public IStudioWidget
+ {
+ NVAllocatorCallback &m_Allocator;
+ NVRenderShaderProgram *m_Shader;
+ NVRenderShaderProgram *m_PickShader;
+ QT3DSMat44 m_TranslationScale;
+ QT3DSMat44 m_CameraTranslationScale;
+ QT3DSMat44 m_PureProjection;
+ SWidgetRenderInformation m_WidgetInfo;
+ StudioWidgetComponentIds::Enum m_Highlight;
+ RenderWidgetModes::Enum m_WidgetMode;
+
+ QT3DSVec3 m_AxisScale;
+ Option<SRotationWedge> m_RotationWedge;
+ nvvector<SImmediateVertex> m_ImmediateBuffer;
+ NVRenderVertexBuffer *m_ImmediateVertexBuffer;
+ NVRenderInputAssembler *m_ImmediateInputAssembler;
+ NVRenderShaderProgram *m_ImmediateShader;
+
+ SStudioWidgetImpl(NVAllocatorCallback &inAlloc)
+ : m_Allocator(inAlloc)
+ , m_Shader(NULL)
+ , m_PickShader(NULL)
+ , m_Highlight(StudioWidgetComponentIds::NoId)
+ , m_WidgetMode(RenderWidgetModes::Local)
+ , m_AxisScale(QT3DSVec3(1, 1, 1))
+ , m_ImmediateBuffer(m_Allocator, "STranslationWidget::theVertexData")
+ , m_ImmediateVertexBuffer(NULL)
+ , m_ImmediateInputAssembler(NULL)
+ , m_ImmediateShader(NULL)
+ {
+ }
+
+ void SetNode(SNode &inNode) override { m_Node = &inNode; }
+
+ void SetSubComponentId(int inId) override
+ {
+ if (inId > 0 && inId < (int)StudioWidgetComponentIds::LastId)
+ m_Highlight = static_cast<StudioWidgetComponentIds::Enum>(inId);
+ else
+ m_Highlight = StudioWidgetComponentIds::NoId;
+ }
+
+ StudioWidgetTypes::Enum GetWidgetType() const override { return TWidgetType; }
+ qt3ds::studio::SStudioPickValue PickIndexToPickValue(QT3DSU32 inPickIndex) override
+ {
+ return qt3ds::studio::SWidgetPick((QT3DSI32)inPickIndex);
+ }
+
+ void SetupRender(IRenderWidgetContext &inWidgetContext, NVRenderContext &inRenderContext)
+ {
+ m_Shader = IStudioWidget::CreateWidgetShader(inWidgetContext, inRenderContext);
+ m_PickShader = IStudioWidget::CreateWidgetPickShader(inWidgetContext, inRenderContext);
+ }
+
+ QT3DSMat44 SetupMVP(IRenderWidgetContext &inWidgetContext)
+ {
+ SWidgetRenderSetupResult theSetup(inWidgetContext, *m_Node, m_WidgetMode);
+ m_TranslationScale = theSetup.m_TranslationScale;
+ m_CameraTranslationScale = theSetup.m_CameraTranslationScale;
+ m_PureProjection = theSetup.m_PureProjection;
+ m_WidgetInfo = theSetup.m_WidgetInfo;
+ return theSetup.m_SetupResult;
+ }
+
+ void RenderSingleToneGeometry(StudioWidgetComponentIds::Enum inId,
+ const QT3DSVec3 &inOriginalColor,
+ NVRenderContext &inRenderContext,
+ NVRenderInputAssembler *inGeometryAssembly)
+ {
+ bool isHighlighted = inId == m_Highlight;
+ QT3DSVec3 theColor = isHighlighted ? QT3DSVec3(1, 1, 0) : inOriginalColor;
+
+ m_Shader->SetPropertyValue("color0", theColor);
+ inRenderContext.SetInputAssembler(inGeometryAssembly);
+ inRenderContext.Draw(qt3ds::render::NVRenderDrawMode::Triangles,
+ inGeometryAssembly->GetVertexCount(), 0);
+ };
+
+ void RenderTwoToneGeometry(StudioWidgetComponentIds::Enum inId, QT3DSVec3 inColor0,
+ QT3DSVec3 inColor1, NVRenderContext &inRenderContext,
+ NVRenderInputAssembler *inGeometryAssembly)
+ {
+ bool isHighlighted = inId == m_Highlight;
+ if (isHighlighted) {
+ inColor0 = inColor1 = QT3DSVec3(1, 1, 0);
+ }
+
+ m_Shader->SetPropertyValue("color0", inColor0);
+ m_Shader->SetPropertyValue("color1", inColor1);
+ inRenderContext.SetInputAssembler(inGeometryAssembly);
+ inRenderContext.Draw(qt3ds::render::NVRenderDrawMode::Triangles,
+ inGeometryAssembly->GetVertexCount(), 0);
+ };
+
+ void SetRenderWidgetMode(RenderWidgetModes::Enum inSpace) override { m_WidgetMode = inSpace; }
+
+ RenderWidgetModes::Enum GetRenderWidgetMode() const override { return m_WidgetMode; }
+
+ void RenderPickBuffer(StudioWidgetComponentIds::Enum inId,
+ NVRenderInputAssembler *inGeometryAssembly,
+ NVRenderContext &inRenderContext)
+ {
+ QT3DSI32 theObjectId = inId;
+ m_PickShader->SetPropertyValue("object_id", theObjectId);
+ inRenderContext.SetInputAssembler(inGeometryAssembly);
+ inRenderContext.Draw(qt3ds::render::NVRenderDrawMode::Triangles,
+ inGeometryAssembly->GetVertexCount(), 0);
+ }
+
+ void BeginImmediateDrawing(IRenderWidgetContext &, NVRenderContext &)
+ {
+ m_ImmediateBuffer.clear();
+ }
+
+ void DrawImmediateRect(const QT3DSVec3 &rectStart, const QT3DSVec3 &rectEnd, const QT3DSVec3 &orth1,
+ QT3DSF32 axisHalfWidth, const QT3DSVec4 &inColor)
+ {
+ StaticAssert<sizeof(SImmediateVertex) == 7 * sizeof(QT3DSF32)>::valid_expression();
+ m_ImmediateBuffer.push_back(
+ SImmediateVertex(rectStart + orth1 * axisHalfWidth, inColor));
+ m_ImmediateBuffer.push_back(SImmediateVertex(rectEnd + orth1 * axisHalfWidth, inColor));
+ m_ImmediateBuffer.push_back(SImmediateVertex(rectEnd - orth1 * axisHalfWidth, inColor));
+ m_ImmediateBuffer.push_back(SImmediateVertex(rectEnd - orth1 * axisHalfWidth, inColor));
+ m_ImmediateBuffer.push_back(
+ SImmediateVertex(rectStart - orth1 * axisHalfWidth, inColor));
+ m_ImmediateBuffer.push_back(
+ SImmediateVertex(rectStart + orth1 * axisHalfWidth, inColor));
+ }
+
+ void DrawImmediateLine(const QT3DSVec3 &inStart, const QT3DSVec3 &inEnd, QT3DSF32 inWidth,
+ const QT3DSVec4 &inColor)
+ {
+ QT3DSVec3 theDir = inEnd - inStart;
+ theDir.normalize();
+ QT3DSVec3 theTemp = theDir.cross(QT3DSVec3(0, 0, 1));
+ QT3DSF32 theTempLen = theTemp.normalize();
+ if (theTempLen < .01f) {
+ theTemp = theDir.cross(QT3DSVec3(0, 1, 0));
+ theTemp.normalize();
+ }
+ QT3DSVec3 rectStart(inStart);
+ QT3DSVec3 rectEnd(inEnd);
+ QT3DSVec3 orth1 = theDir.cross(theTemp);
+ QT3DSVec3 orth2 = orth1.cross(theDir);
+ orth1.normalize();
+ orth2.normalize();
+ QT3DSF32 axisHalfWidth = inWidth / 2.0f;
+ DrawImmediateRect(rectStart, rectEnd, orth1, axisHalfWidth, inColor);
+ DrawImmediateRect(rectStart, rectEnd, orth2, axisHalfWidth, inColor);
+ }
+
+ void DrawFilledArc(const QT3DSVec3 &inStartPos, const QT3DSVec3 &inStartDirection, QT3DSF32 inArcLen,
+ const QT3DSVec3 &inRotationAxis, QT3DSF32 inAngle, const QT3DSVec4 &inFillColor)
+ {
+ // 25 small triangles per 180 degrees
+ QT3DSF32 arcLen = (QT3DSF32)(M_PI / 25.0f);
+ QT3DSU32 increments = qMax((QT3DSU32)1, (QT3DSU32)((fabs(inArcLen) / arcLen) + .5f));
+ QT3DSF32 angleMultiplier = inAngle / (QT3DSF32)increments;
+ for (QT3DSU32 idx = 0; idx < increments; ++idx) {
+ QT3DSF32 localAngle = angleMultiplier * idx;
+ QT3DSF32 nextAngle = angleMultiplier * (idx + 1);
+ QT3DSQuat theQuat(localAngle, inRotationAxis);
+ QT3DSQuat nextQuat(nextAngle, inRotationAxis);
+ QT3DSVec3 startDir = theQuat.rotate(inStartDirection);
+ QT3DSVec3 endDir = nextQuat.rotate(inStartDirection);
+ QT3DSVec3 arcStart = inStartPos + (startDir * inArcLen);
+ QT3DSVec3 arcEnd = inStartPos + (endDir * inArcLen);
+ m_ImmediateBuffer.push_back(SImmediateVertex(inStartPos, inFillColor));
+ m_ImmediateBuffer.push_back(SImmediateVertex(arcStart, inFillColor));
+ m_ImmediateBuffer.push_back(SImmediateVertex(arcEnd, inFillColor));
+ }
+ }
+
+ void EndImmediateDrawing(IRenderWidgetContext &inWidgetContext,
+ NVRenderContext &inRenderContext, const QT3DSMat44 &inProjection)
+ {
+ static qt3ds::render::NVRenderVertexBufferEntry theEntries[] = {
+ qt3ds::render::NVRenderVertexBufferEntry("attr_pos",
+ qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3),
+ qt3ds::render::NVRenderVertexBufferEntry(
+ "attr_color", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 4, 12),
+ };
+
+ if (m_ImmediateBuffer.empty())
+ return;
+
+ CRegisteredString theShaderName(
+ inRenderContext.GetStringTable().RegisterStr("StudioWidgetImmedateShader"));
+ m_ImmediateShader = inWidgetContext.GetShader(theShaderName);
+
+ if (m_ImmediateShader == nullptr) {
+ qt3ds::render::IShaderProgramGenerator &theGenerator(
+ inWidgetContext.GetProgramGenerator());
+ theGenerator.BeginProgram();
+ qt3ds::render::IShaderStageGenerator &theVertexGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Vertex));
+ qt3ds::render::IShaderStageGenerator &theFragmentGenerator(
+ *theGenerator.GetStage(qt3ds::render::ShaderGeneratorStages::Fragment));
+ theVertexGenerator.AddIncoming("attr_pos", "vec3");
+ theVertexGenerator.AddIncoming("attr_color", "vec4");
+ theVertexGenerator.AddUniform("model_view_projection", "mat4");
+ theVertexGenerator.AddOutgoing("vertex_color", "vec4");
+ theVertexGenerator.Append("void main() {");
+ theVertexGenerator.Append(
+ "\tgl_Position = model_view_projection * vec4(attr_pos, 1.0);");
+ theVertexGenerator.Append("\tvertex_color = attr_color;");
+ theVertexGenerator.Append("}");
+ theFragmentGenerator.Append("void main() {");
+ theFragmentGenerator.Append("\tgl_FragColor = vertex_color;");
+ theFragmentGenerator.Append("}");
+
+ m_ImmediateShader = inWidgetContext.CompileAndStoreShader(theShaderName);
+ }
+
+ CRegisteredString theBufferName =
+ inRenderContext.GetStringTable().RegisterStr("StudioWidgetImmediateBuffer");
+ m_ImmediateVertexBuffer = &inWidgetContext.GetOrCreateVertexBuffer(
+ theBufferName, 3 * sizeof(QT3DSF32) + 4 * sizeof(QT3DSF32),
+ toU8DataRef(m_ImmediateBuffer.begin(), m_ImmediateBuffer.size()));
+
+ if (!m_ImmediateInputAssembler) {
+ QT3DSU32 stride = m_ImmediateVertexBuffer->GetStride();
+ QT3DSU32 offset = 0;
+ NVRenderAttribLayout *theAttribLayout =
+ &inWidgetContext.CreateAttributeLayout(toConstDataRef(theEntries, 2));
+
+ CRegisteredString theString =
+ inRenderContext.GetStringTable().RegisterStr("StudioWidgetImmediateBuffer");
+ m_ImmediateInputAssembler = &inWidgetContext.GetOrCreateInputAssembler(
+ theString, theAttribLayout, toConstDataRef(&m_ImmediateVertexBuffer, 1), nullptr,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+ }
+
+ if (m_ImmediateShader && m_ImmediateInputAssembler) {
+ inRenderContext.SetActiveShader(m_ImmediateShader);
+ m_ImmediateShader->SetPropertyValue("model_view_projection", inProjection);
+ inRenderContext.SetInputAssembler(m_ImmediateInputAssembler);
+ inRenderContext.Draw(NVRenderDrawMode::Triangles,
+ m_ImmediateInputAssembler->GetVertexCount(), 0);
+ }
+ }
+
+ void SetAxisScale(const QT3DSVec3 &inAxisScale) override { m_AxisScale = inAxisScale; }
+
+ void SetRotationEdges(const QT3DSVec3 &inStartDirection, const QT3DSVec3 &inRotationAxis,
+ QT3DSF32 inAngleRad, QT3DSF32 inEndLineLen) override
+ {
+ m_RotationWedge =
+ SRotationWedge(inStartDirection, inRotationAxis, inAngleRad, inEndLineLen);
+ }
+
+ void ClearRotationEdges() override { m_RotationWedge = Empty(); }
+
+ static inline QT3DSVec3 ToGLSLColor(const QColor &c)
+ {
+ return QT3DSVec3(c.redF(), c.greenF(), c.blueF());
+ }
+
+ static QT3DSVec3 GetXAxisColor() { return ToGLSLColor(CStudioPreferences::GetXAxisColor()); }
+ static QT3DSVec3 GetYAxisColor() { return ToGLSLColor(CStudioPreferences::GetYAxisColor()); }
+ static QT3DSVec3 GetZAxisColor() { return ToGLSLColor(CStudioPreferences::GetZAxisColor()); }
+
+ static inline QT3DSF32 GetDiscPos() { return 65.0f; }
+ static inline QT3DSF32 GetDiscRadius() { return 7.0f; }
+ static inline QT3DSF32 GetDiscRingRadius() { return GetDiscRadius() + 2.0f; }
+ };
+}
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Render/WGLRenderContext.cpp b/src/Authoring/Qt3DStudio/Render/WGLRenderContext.cpp
new file mode 100644
index 00000000..57428631
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/WGLRenderContext.cpp
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2001 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 "WGLRenderContext.h"
+#include "foundation/TrackingAllocator.h"
+#include "foundation/Qt3DSFoundation.h"
+#include "render/Qt3DSRenderContext.h"
+#include "EASTL/string.h"
+#include "foundation/Qt3DSLogging.h"
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QSurfaceFormat>
+#include <QtWidgets/qopenglwidget.h>
+
+//QT3DS_DEFINE_THISFILE;
+
+//=============================================================================
+/**
+ * Constructor: Creates the object
+ */
+CWGLRenderContext::CWGLRenderContext(Qt3DSWindow inWindow)
+ : m_qtContext(0)
+ , m_Foundation(Q3DStudio::Foundation::SStudioFoundation::Create())
+{
+ Open(inWindow);
+}
+
+//=============================================================================
+/**
+ * Destructor: Destroys the object.
+ */
+CWGLRenderContext::~CWGLRenderContext()
+{
+ Close();
+}
+
+//=============================================================================
+/**
+ * Open the render context.
+ * @param inRenderWindow window handle
+ * @param inWindowSize window size
+ */
+void CWGLRenderContext::Open(Qt3DSWindow inRenderWindow)
+{
+ // needed because NVidia cards will fail all the system functions below if there is no window.
+ // note: the only time inRenderWindow is nullptr is when CThumbnailGenerator is used. Bug3075.
+ if (!inRenderWindow)
+ return;
+
+ QObject* qObject = static_cast<QObject*>(inRenderWindow);
+ QOpenGLWidget* qRenderWidget = qobject_cast<QOpenGLWidget*>(qObject);
+ Q_ASSERT(qRenderWidget);
+
+ OpenNormalContext(qRenderWidget);
+}
+
+static bool compareContextVersion(QSurfaceFormat a, QSurfaceFormat b)
+{
+ if (a.renderableType() != b.renderableType())
+ return false;
+ if (a.majorVersion() != b.majorVersion())
+ return false;
+ if (a.minorVersion() > b.minorVersion())
+ return false;
+ return true;
+}
+
+QSurfaceFormat CWGLRenderContext::selectSurfaceFormat(QOpenGLWidget *window)
+{
+#if defined(Q_OS_MACOS)
+ Q_UNUSED(window)
+ QSurfaceFormat openGL33Format;
+ openGL33Format.setRenderableType(QSurfaceFormat::OpenGL);
+ openGL33Format.setProfile(QSurfaceFormat::CoreProfile);
+ openGL33Format.setMajorVersion(3);
+ openGL33Format.setMinorVersion(3);
+ openGL33Format.setStencilBufferSize(8);
+ QSurfaceFormat::setDefaultFormat(openGL33Format);
+ return openGL33Format;
+#else
+ struct ContextVersion {
+ int major;
+ int minor;
+ qt3ds::render::NVRenderContextType contextType;
+ };
+
+ ContextVersion versions[] = {
+ {4, 5, qt3ds::render::NVRenderContextValues::GL4},
+ {4, 4, qt3ds::render::NVRenderContextValues::GL4},
+ {4, 3, qt3ds::render::NVRenderContextValues::GL4},
+ {4, 2, qt3ds::render::NVRenderContextValues::GL4},
+ {4, 1, qt3ds::render::NVRenderContextValues::GL4},
+ {3, 3, qt3ds::render::NVRenderContextValues::GL3},
+ {2, 1, qt3ds::render::NVRenderContextValues::GL2},
+ {2, 0, qt3ds::render::NVRenderContextValues::GLES2},
+ {0, 0, qt3ds::render::NVRenderContextValues::NullContext}
+ };
+
+ QSurfaceFormat result = window->format();
+ bool valid = false;
+
+ for (const auto& ver : versions) {
+ if (ver.contextType == qt3ds::render::NVRenderContextValues::NullContext)
+ break;
+
+ // make an offscreen surface + context to query version
+ QScopedPointer<QOffscreenSurface> offscreenSurface(new QOffscreenSurface);
+
+ QSurfaceFormat format = window->format();
+ if (ver.contextType == qt3ds::render::NVRenderContextValues::GLES2) {
+ format.setRenderableType(QSurfaceFormat::OpenGLES);
+ } else {
+ format.setRenderableType(QSurfaceFormat::OpenGL);
+ if (ver.major >= 2)
+ format.setProfile(QSurfaceFormat::CoreProfile);
+ }
+ format.setMajorVersion(ver.major);
+ format.setMinorVersion(ver.minor);
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+
+ offscreenSurface->setFormat(format);
+ offscreenSurface->create();
+ Q_ASSERT(offscreenSurface->isValid());
+
+ QScopedPointer<QOpenGLContext> queryContext(new QOpenGLContext);
+ queryContext->setFormat(format);
+ if (queryContext->create() && compareContextVersion(format, queryContext->format())) {
+ valid = true;
+ result = format;
+ break;
+ }
+ } // of version test iteration
+
+ if (!valid) {
+ qFatal("Unable to select suitable OpenGL context");
+ }
+
+ qDebug() << Q_FUNC_INFO << "selected surface format:" << result;
+ QSurfaceFormat::setDefaultFormat(result);
+ return result;
+#endif
+}
+
+//=============================================================================
+/**
+ * Open a non-multisample render context.
+ * @param inRenderWindow window handle
+ * @param inWindowSize window size
+ * @param inPixelDesc the pixel descriptor struct
+ */
+void CWGLRenderContext::OpenNormalContext(QOpenGLWidget* inRenderWindow)
+{
+ // Close before trying to open
+ Close();
+
+ // Save off the window
+ m_Window = inRenderWindow;
+ m_qtContext = m_Window->context();
+ Q_ASSERT(m_qtContext);
+
+ qt3ds::foundation::NVScopedRefCounted<qt3ds::foundation::IStringTable> theStringTable =
+ qt3ds::foundation::IStringTable::CreateStringTable(*m_Foundation.m_AllocatorCallback);
+ QSurfaceFormat contextFormat = m_qtContext->format();
+ m_RenderContext = NVScopedRefCounted<NVRenderContext>(
+ NVRenderContext::CreateGL(*m_Foundation.m_Foundation, *theStringTable,
+ contextFormat));
+ if (m_RenderContext) {
+ m_RenderContext->SetDefaultDepthBufferBitCount(contextFormat.depthBufferSize());
+ m_RenderContext->SetDefaultRenderTarget(inRenderWindow->defaultFramebufferObject());
+ }
+}
+
+//=============================================================================
+/**
+ * Close the render context.
+ */
+void CWGLRenderContext::Close()
+{
+ m_qtContext = 0;
+}
+
+//=============================================================================
+/**
+ * Prepare the render context to begin rendering.
+ */
+void CWGLRenderContext::BeginRender()
+{
+ m_Window->makeCurrent();
+ if (m_lastWidgetFBO != m_Window->defaultFramebufferObject()) {
+ m_lastWidgetFBO = m_Window->defaultFramebufferObject();
+ m_RenderContext->SetDefaultRenderTarget(m_lastWidgetFBO);
+ }
+}
+
+//=============================================================================
+/**
+ * Finalize the rendering of this frame, and present the result.
+ */
+void CWGLRenderContext::EndRender()
+{
+ m_Window->doneCurrent();
+}
+
+void CWGLRenderContext::resized()
+{
+ if (m_RenderContext) {
+ m_RenderContext->SetDefaultRenderTarget(m_Window->defaultFramebufferObject());
+ }
+}
+
+void CWGLRenderContext::requestRender()
+{
+ m_Window->update();
+}
diff --git a/src/Authoring/Qt3DStudio/Render/WGLRenderContext.h b/src/Authoring/Qt3DStudio/Render/WGLRenderContext.h
new file mode 100644
index 00000000..100c459a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Render/WGLRenderContext.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** 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 __WGLRENDERCONTEXT_H_
+#define __WGLRENDERCONTEXT_H_
+#include "PlatformTypes.h"
+#include "Q3DStudioNVFoundation.h"
+#include "render/Qt3DSRenderBaseTypes.h"
+
+#include <QSurfaceFormat>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLContext)
+QT_FORWARD_DECLARE_CLASS(QOpenGLWidget)
+
+namespace qt3ds {
+class NVFoundation;
+}
+
+namespace qt3ds {
+namespace render {
+ class NVRenderContext;
+ class CAllocator;
+}
+}
+
+using qt3ds::NVFoundation;
+using qt3ds::render::NVRenderContext;
+using qt3ds::render::CAllocator;
+using qt3ds::foundation::NVScopedRefCounted;
+
+class GLogErrorString;
+
+//==============================================================================
+/**
+ * @class CWGLRenderContext: The OpenGL subclass of the CRenderContext class.
+ */
+class CWGLRenderContext
+{
+ // Field Members
+protected:
+ QOpenGLContext *m_qtContext;
+
+ Q3DStudio::Foundation::SStudioFoundation m_Foundation;
+ NVScopedRefCounted<NVRenderContext> m_RenderContext;
+ QOpenGLWidget* m_Window;
+ qt3ds::render::NVRenderContextType m_ContextType;
+
+ quint32 m_lastWidgetFBO = 0;
+
+ // Construction
+public:
+ CWGLRenderContext(Qt3DSWindow inRenderWindow);
+ ~CWGLRenderContext();
+
+ // Access
+public:
+ void BeginRender();
+ void EndRender();
+
+ // Only available after open.
+ NVRenderContext &GetRenderContext() { return *m_RenderContext; }
+
+ static QSurfaceFormat selectSurfaceFormat(QOpenGLWidget* window);
+
+ void resized();
+
+ void requestRender();
+
+ // Implementation
+protected:
+ void Open(Qt3DSWindow inRenderWindow);
+
+ void OpenNormalContext(QOpenGLWidget* inRenderWindow);
+ void Close();
+};
+
+#endif // __WGLRENDERCONTEXT_H_
diff --git a/src/Authoring/Qt3DStudio/UI/EditCameraBar.cpp b/src/Authoring/Qt3DStudio/UI/EditCameraBar.cpp
new file mode 100644
index 00000000..5675f978
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/EditCameraBar.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "EditCameraBar.h"
+#include "MainFrm.h"
+#include "SceneView.h"
+#include "StudioPreferences.h"
+#include "StudioApp.h"
+#include "IStudioRenderer.h"
+
+#include <QtWidgets/qcombobox.h>
+#include <QtWidgets/qlistview.h>
+
+CEditCameraBar::CEditCameraBar(QWidget *parent) : QToolBar(parent)
+{
+ initialize();
+
+ connect(m_CameraSelector, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &CEditCameraBar::handleCameraChanged);
+}
+
+CEditCameraBar::~CEditCameraBar()
+{
+ delete m_CameraSelector;
+}
+
+/**
+ * Setup the list of edit cameras into the camera combo box
+ * @param inCameras the container that holds the edit cameras
+ */
+void CEditCameraBar::setupCameras()
+{
+ m_CameraSelector->clear();
+ Q3DStudio::IStudioRenderer &theRenderer = g_StudioApp.getRenderer();
+ QStringList theCameraNames;
+ theRenderer.GetEditCameraList(theCameraNames);
+ m_CameraSelector->addItems(theCameraNames);
+ m_CameraSelector->addItem(tr("Scene Camera View"));
+
+ m_CameraSelector->insertSeparator(m_CameraSelector->count() - 1);
+
+ // adding a 1px spacing, else the separator will disappear sometimes (QComboBox bug)
+ qobject_cast<QListView *>(m_CameraSelector->view())->setSpacing(1);
+
+ // set initial view
+ int viewIndex = CStudioPreferences::GetPreferredStartupView();
+ // if not set or invalid index, use the scene camera view (last index)
+ if (viewIndex == -1 || viewIndex > m_CameraSelector->count() - 3)
+ viewIndex = m_CameraSelector->count() - 1;
+
+ m_CameraSelector->setCurrentIndex(viewIndex);
+ handleCameraChanged(viewIndex);
+
+ QString ctrlKey(QStringLiteral("Ctrl+"));
+#ifdef Q_OS_MACOS
+ ctrlKey = "⌘";
+#endif
+
+ m_CameraSelector->setToolTip(tr("Change Camera View (%1<1..9>)").arg(ctrlKey));
+}
+
+void CEditCameraBar::setCameraIndex(int inIndex)
+{
+ m_CameraSelector->setCurrentIndex(inIndex);
+ handleCameraChanged(inIndex);
+}
+
+/**
+ * Handle the switching of the current edit camera
+ * @param inIndex the index of the to-be-activated camera in the combo box
+ */
+void CEditCameraBar::handleCameraChanged(int inIndex)
+{
+ Q3DStudio::IStudioRenderer &theRenderer = g_StudioApp.getRenderer();
+
+ // last index is scene camera view, renderer requires index -1 for it
+ theRenderer.SetEditCamera(inIndex == m_CameraSelector->count() - 1 ? -1 : inIndex);
+
+ if (m_SceneView)
+ m_SceneView->onEditCameraChanged();
+
+ // if the current tool is camera rotate and has been switch to 2d camera
+ // set the tool to camera pan
+ long theToolMode = g_StudioApp.GetToolMode();
+ if (!theRenderer.DoesEditCameraSupportRotation(theRenderer.GetEditCamera())
+ && theToolMode == STUDIO_TOOLMODE_CAMERA_ROTATE) {
+ g_StudioApp.SetToolMode(STUDIO_TOOLMODE_CAMERA_PAN);
+ m_SceneView->setViewCursor(); // Just set cursor, we don't want to update previous tool
+ }
+
+ // Trigger for tool changed. Changing between deployment/edit camera can change the tool
+ g_StudioApp.m_pMainWnd->OnUpdateToolChange();
+}
+
+/**
+ * Set the current scene view. This scene view is notified when there is a camera
+ * changed.
+ * @param inSceneView the scene view object
+ */
+void CEditCameraBar::setSceneView(CSceneView *inSceneView)
+{
+ m_SceneView = inSceneView;
+}
+
+void CEditCameraBar::initialize()
+{
+ // Create the combo box
+ addWidget(m_CameraSelector = new QComboBox);
+#if (defined Q_OS_MACOS)
+ // There is a "selected" icon in the popup, and automatic scaling does not work for some reason
+ m_CameraSelector->setMinimumContentsLength(tr("Scene Camera View").length() + 1);
+#else
+ m_CameraSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+#endif
+ // We need to specify accessibleName and objectName for the combobox, as it's in the toolbar,
+ // and we want to use a different style for it.
+ const QString objName(QStringLiteral("cameraSelector"));
+ m_CameraSelector->setAccessibleName(objName);
+ m_CameraSelector->setObjectName(objName);
+}
diff --git a/src/Authoring/Qt3DStudio/UI/EditCameraBar.h b/src/Authoring/Qt3DStudio/UI/EditCameraBar.h
new file mode 100644
index 00000000..fae523c3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/EditCameraBar.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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_EDIT_CAMERA_BAR
+#define INCLUDED_EDIT_CAMERA_BAR
+
+#include <QtWidgets/qtoolbar.h>
+
+QT_BEGIN_NAMESPACE
+class QComboBox;
+QT_END_NAMESPACE
+
+class CSceneView;
+
+class CEditCameraBar : public QToolBar
+{
+ Q_OBJECT
+
+public:
+ CEditCameraBar(QWidget *parent = nullptr);
+ virtual ~CEditCameraBar() override;
+
+ void setupCameras();
+ void setSceneView(CSceneView *inSceneView);
+ void setCameraIndex(int inIndex);
+
+private:
+ void initialize();
+ void handleCameraChanged(int inIndex);
+
+ CSceneView *m_SceneView = nullptr;
+ QComboBox *m_CameraSelector = nullptr;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/UI/EditorPane.cpp b/src/Authoring/Qt3DStudio/UI/EditorPane.cpp
new file mode 100644
index 00000000..4b87300d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/EditorPane.cpp
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "EditorPane.h"
+
+//==============================================================================
+/**
+ * Constructor: Creates a CEditorPane
+ */
+CEditorPane::CEditorPane()
+ : m_EditObject(nullptr)
+{
+}
+
+//==============================================================================
+/**
+ * Destructor: Releases the CEditorPane
+ */
+CEditorPane::~CEditorPane()
+{
+}
+
+//==============================================================================
+/**
+ * Set the object that this window will edit.
+ * Recieve and keep the CAsset to be edited. You can override this method
+ * for each specific editor view class.
+ * @param inEditObject The CAsset to be represented and edited in this view.
+ */
+void CEditorPane::SetEditObject(CAsset *inEditObject)
+{
+ // Retain this object
+ m_EditObject = inEditObject;
+}
+
+//==============================================================================
+/**
+ * Get the object that this window is editing.
+ * return The CAsset that is associated with this window
+ */
+CAsset *CEditorPane::GetEditObject()
+{
+ // Return the asset
+ return m_EditObject;
+}
+
+//==============================================================================
+/**
+ * CloseEditor: Close the editor.
+ * Override to close each individual editor.
+ * @return false
+ */
+bool CEditorPane::CloseEditor()
+{
+ return false;
+}
diff --git a/src/Authoring/Qt3DStudio/UI/EditorPane.h b/src/Authoring/Qt3DStudio/UI/EditorPane.h
new file mode 100644
index 00000000..9bc91694
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/EditorPane.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$
+**
+****************************************************************************/
+
+//==============================================================================
+// Prefix
+//==============================================================================
+#ifndef INCLUDED_EDITOR_PANE
+#define INCLUDED_EDITOR_PANE 1
+
+#pragma once
+
+//==============================================================================
+// Forwards
+//==============================================================================
+class CAsset;
+
+//==============================================================================
+/**
+ * @class CEditorPane
+ * @brief Abstract base class for various editor views.
+ *
+ * A base class for universally accessing information from editor views.
+ */
+class CEditorPane
+{
+ // Fields
+protected:
+ CAsset *m_EditObject; ///< object being edited by this window.
+
+ // Methods
+public:
+ CEditorPane();
+ virtual ~CEditorPane();
+ virtual void SetEditObject(CAsset *inEditObject);
+ CAsset *GetEditObject();
+ virtual bool CloseEditor();
+};
+
+#endif // INCLUDED_EDITOR_PANE \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/UI/GLVersionDlg.cpp b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.cpp
new file mode 100644
index 00000000..6ecce324
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.cpp
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "GLVersionDlg.h"
+#include "ui_GLVersionDlg.h"
+
+#include <QtWidgets/qstyle.h>
+
+CGLVersionDlg::CGLVersionDlg(QWidget *pParent)
+ : QDialog(pParent)
+ , m_ui(new Ui::GLVersionDlg)
+{
+ m_ui->setupUi(this);
+ setFixedSize(size());
+}
+
+CGLVersionDlg::~CGLVersionDlg()
+{
+}
+
+void CGLVersionDlg::Initialize(const QString &inTitle, const QString &inMessage, bool inErrorIcon)
+{
+ // Set title and message
+ setWindowTitle(inTitle);
+ m_ui->m_Message->setText(inMessage);
+
+ // Set which icon to load
+ const int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
+ m_ui->m_WarningIcon->setPixmap(style()->standardIcon(inErrorIcon
+ ? QStyle::SP_MessageBoxCritical
+ : QStyle::SP_MessageBoxWarning).pixmap(
+ size, size));
+}
+
+bool CGLVersionDlg::GetDontShowAgain()
+{
+ return m_ui->m_DontShowAgain->isChecked();
+}
diff --git a/src/Authoring/Qt3DStudio/UI/GLVersionDlg.h b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.h
new file mode 100644
index 00000000..588d62aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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_GL_VERSION_DLG
+#define INCLUDED_GL_VERSION_DLG 1
+
+#pragma once
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "Qt3DSString.h"
+
+#include <QtWidgets/qdialog.h>
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+ class GLVersionDlg;
+}
+QT_END_NAMESPACE
+
+//==============================================================================
+/**
+ * CGLVersionDlg: Dialog class for showing Open GL Version Warning
+ */
+//==============================================================================
+class CGLVersionDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ CGLVersionDlg(QWidget *pParent = nullptr); // standard constructor
+ ~CGLVersionDlg();
+
+ void Initialize(const QString &inTitle, const QString &inMessage, bool inErrorIcon);
+ bool GetDontShowAgain();
+
+private:
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::GLVersionDlg)> m_ui;
+};
+
+#endif // INCLUDED_GL_VERSION_DLG
diff --git a/src/Authoring/Qt3DStudio/UI/GLVersionDlg.ui b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.ui
new file mode 100644
index 00000000..3b17f560
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/GLVersionDlg.ui
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GLVersionDlg</class>
+ <widget class="QDialog" name="GLVersionDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>557</width>
+ <height>146</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="m_WarningIcon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_Message">
+ <property name="text">
+ <string>OpenGL version X.X.X detected.
+A video card with an updated driver capable of OpenGL X.X.X is recommended or there may be rendering errors.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="m_DontShowAgain">
+ <property name="text">
+ <string>Don't show this dialog again</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>GLVersionDlg</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>GLVersionDlg</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/UI/InterpolationDlg.cpp b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.cpp
new file mode 100644
index 00000000..9c4c5c38
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.cpp
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "InterpolationDlg.h"
+#include "ui_InterpolationDlg.h"
+
+/**
+ * Constructor: Creates a CInterpolationDlg.
+ *
+ * @param parent Pointer to the parent of this dialog (defaults to NULL)
+ */
+//==============================================================================
+CInterpolationDlg::CInterpolationDlg(QWidget *parent)
+ : QDialog(parent)
+ , m_ui(new Ui::InterpolationDlg)
+{
+ m_ui->setupUi(this);
+ window()->setFixedSize(size());
+}
+
+CInterpolationDlg::~CInterpolationDlg()
+{
+ delete m_ui;
+ m_ui = nullptr;
+}
+
+void CInterpolationDlg::setEaseIn(uint value)
+{
+ m_ui->easeInSlider->setValue(value);
+}
+
+void CInterpolationDlg::setEaseOut(uint value)
+{
+ m_ui->easeOutSlider->setValue(value);
+}
+
+int CInterpolationDlg::easeIn() const
+{
+ return m_ui->easeInSlider->value();
+}
+
+int CInterpolationDlg::easeOut() const
+{
+ return m_ui->easeOutSlider->value();
+}
+
+
diff --git a/src/Authoring/Qt3DStudio/UI/InterpolationDlg.h b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.h
new file mode 100644
index 00000000..0b324bd3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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_INTERPOLATION_DLG
+#define INCLUDED_INTERPOLATION_DLG 1
+
+#pragma once
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class InterpolationDlg;
+}
+QT_END_NAMESPACE
+
+//==============================================================================
+/**
+ * CInterpolationDlg: Dialog class for editing the ease-in/ease-out values of keyframes.
+ */
+//==============================================================================
+class CInterpolationDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit CInterpolationDlg(QWidget *parent = nullptr); // standard constructor
+ ~CInterpolationDlg();
+
+ void setEaseIn(uint value);
+ void setEaseOut(uint value);
+ int easeIn() const;
+ int easeOut() const;
+
+protected:
+ QT_PREPEND_NAMESPACE(Ui::InterpolationDlg) *m_ui = nullptr;
+};
+
+#endif // INCLUDED_INTERPOLATION_DLG
diff --git a/src/Authoring/Qt3DStudio/UI/InterpolationDlg.ui b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.ui
new file mode 100644
index 00000000..0d27d777
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/InterpolationDlg.ui
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InterpolationDlg</class>
+ <widget class="QDialog" name="InterpolationDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>425</width>
+ <height>195</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Set Keyframe Interpolation</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="1" colspan="2">
+ <widget class="QSlider" name="easeOutSlider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBelow</enum>
+ </property>
+ <property name="tickInterval">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Ease In:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Smooth</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QSlider" name="easeInSlider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBelow</enum>
+ </property>
+ <property name="tickInterval">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Linear</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Linear</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QLabel" name="label_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Smooth</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Ease Out:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QSpinBox" name="easeInSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QSpinBox" name="easeOutSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </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>
+ <tabstops>
+ <tabstop>easeInSlider</tabstop>
+ <tabstop>easeInSpinBox</tabstop>
+ <tabstop>easeOutSlider</tabstop>
+ <tabstop>easeOutSpinBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>InterpolationDlg</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>224</x>
+ <y>272</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>InterpolationDlg</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>
+ <connection>
+ <sender>easeInSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>easeInSpinBox</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>331</x>
+ <y>21</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>542</x>
+ <y>35</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>easeOutSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>easeOutSpinBox</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>210</x>
+ <y>93</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>545</x>
+ <y>93</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>easeInSpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>easeInSlider</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>533</x>
+ <y>24</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>408</x>
+ <y>18</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>easeOutSpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>easeOutSlider</receiver>
+ <slot>setValue(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>546</x>
+ <y>98</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>338</x>
+ <y>84</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.cpp b/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.cpp
new file mode 100644
index 00000000..e077cf9e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.cpp
@@ -0,0 +1,313 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "SceneView.h"
+#include "Doc.h"
+#include "StudioProjectSettings.h"
+#include "Dispatch.h"
+#include "HotKeys.h"
+#include "MasterP.h"
+#include "StudioApp.h"
+#include "IStudioRenderer.h"
+#include "PlatformStrings.h"
+#include "PlayerContainerWnd.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "Core.h"
+#include "MainFrm.h"
+#include "StudioUtils.h"
+
+#include <QtWidgets/qscrollbar.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qscreen.h>
+
+CPlayerContainerWnd::CPlayerContainerWnd(CSceneView *inSceneView)
+ : QScrollArea(inSceneView)
+ , m_SceneView(inSceneView)
+ , m_PlayerWnd(NULL)
+ , m_ViewMode(VIEW_SCENE)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+CPlayerContainerWnd::~CPlayerContainerWnd()
+{
+}
+
+bool CPlayerContainerWnd::ShouldHideScrollBars()
+{
+ return m_ViewMode == VIEW_EDIT || g_StudioApp.IsAuthorZoom();
+}
+
+//==============================================================================
+/**
+ * SetPlayerWndPosition: Sets the position of the child player window
+ *
+ * Called when the view is scrolled to position the child player window
+ * @param outLeftPresentationEdge the left edge of the presentation, with the scroll position
+ * taken into consideration
+ * @param outTopPresentionEdge the top edge of the presentation, with the scroll
+ * position taken into consideration
+ *
+ */
+//==============================================================================
+void CPlayerContainerWnd::SetPlayerWndPosition(long &outLeftPresentationEdge,
+ long &outTopPresentionEdge)
+{
+ long theHScrollPosition, theVScrollPosition;
+ // Negate to adjust actual client positions
+ theHScrollPosition = -horizontalScrollBar()->value();
+ theVScrollPosition = -verticalScrollBar()->value();
+
+ QRect theClientRect = rect();
+
+ // Horizontal scrollbar does not exist
+ if (m_ClientRect.width() < theClientRect.width()) {
+ theHScrollPosition =
+ theClientRect.left() + (theClientRect.width() / 2) - (m_ClientRect.width() / 2);
+ outLeftPresentationEdge = theHScrollPosition;
+ } else {
+ // This stays a negated value
+ outLeftPresentationEdge = theHScrollPosition;
+ }
+
+ // Vertical scrollbar does not exist
+ if (m_ClientRect.height() < theClientRect.height()) {
+ theVScrollPosition =
+ theClientRect.top() + (theClientRect.height() / 2) - (m_ClientRect.height() / 2);
+ outTopPresentionEdge = theVScrollPosition;
+ } else {
+ // This stays a negated value
+ outTopPresentionEdge = theVScrollPosition;
+ }
+
+ // Move the child player window
+ m_PlayerWnd->setGeometry(QRect(QPoint(theHScrollPosition, theVScrollPosition),
+ m_ClientRect.size()));
+}
+
+//==============================================================================
+/**
+ * SetScrollRanges: Sets the scroll ranges when the view is being resized
+ */
+//==============================================================================
+void CPlayerContainerWnd::SetScrollRanges()
+{
+
+ long theScrollWidth = 0;
+ long theScrollHeight = 0;
+
+ if (ShouldHideScrollBars()) {
+ horizontalScrollBar()->setRange(0, 0);
+ verticalScrollBar()->setRange(0, 0);
+ horizontalScrollBar()->setValue(0);
+ verticalScrollBar()->setValue(0);
+ } else {
+ QSize theSize = GetEffectivePresentationSize();
+
+ theScrollWidth = theSize.width();
+ theScrollHeight = theSize.height();
+
+ // Set scrollbar ranges
+ horizontalScrollBar()->setRange(0, theScrollWidth - width());
+ verticalScrollBar()->setRange(0, theScrollHeight - height());
+ horizontalScrollBar()->setPageStep(width());
+ verticalScrollBar()->setPageStep(height());
+ horizontalScrollBar()->setVisible(true);
+ verticalScrollBar()->setVisible(true);
+ }
+}
+
+//==============================================================================
+/**
+ * OnRulerGuideToggled:
+ * Handle scrollbar position when ruler, guide has been toggled
+ */
+//==============================================================================
+void CPlayerContainerWnd::OnRulerGuideToggled()
+{
+ int scrollAmount = g_StudioApp.getRenderer().AreGuidesEnabled() ? 16 : -16;
+ bool hasHorz = horizontalScrollBar()->isVisible();
+ bool hasVert = verticalScrollBar()->isVisible();
+ int hscrollPos = 0, vscrollPos = 0;
+ if (hasHorz)
+ hscrollPos = qMax(horizontalScrollBar()->value() + scrollAmount, 0);
+ if (hasVert)
+ vscrollPos = qMax(verticalScrollBar()->value() + scrollAmount, 0);
+ horizontalScrollBar()->setValue(hscrollPos);
+ verticalScrollBar()->setValue(vscrollPos);
+ m_PlayerWnd->update();
+}
+
+qreal CPlayerContainerWnd::fixedDevicePixelRatio() const
+{
+ // Fix a problem on X11: https://bugreports.qt.io/browse/QTBUG-65570
+ qreal ratio = devicePixelRatio();
+ if (QWindow *w = window()->windowHandle()) {
+ if (QScreen *s = w->screen())
+ ratio = s->devicePixelRatio();
+ }
+ return ratio;
+}
+
+//==============================================================================
+/**
+ * RecenterClient: Recenters the Client rect in the View's client area.
+ */
+//==============================================================================
+void CPlayerContainerWnd::RecenterClient()
+{
+ QRect theViewClientRect = rect();
+ QSize theClientSize;
+ m_ClientRect = theViewClientRect;
+
+ if (!ShouldHideScrollBars()) {
+ theClientSize = GetEffectivePresentationSize();
+
+ // Only center if we need to scroll
+ if (theClientSize.width() > theViewClientRect.width()) {
+ // compute the size of the client rectangle to display
+ m_ClientRect.setLeft(
+ (theViewClientRect.width() / 2) - (theClientSize.width() / 2)
+ - (theClientSize.width() % 2));
+ m_ClientRect.setRight((theViewClientRect.width() / 2) + (theClientSize.width() / 2));
+ }
+
+ if (theClientSize.height() > theViewClientRect.height()) {
+ m_ClientRect.setTop(
+ (theViewClientRect.height() / 2) - (theClientSize.height() / 2)
+ - (theClientSize.height() % 2));
+ m_ClientRect.setBottom((theViewClientRect.height() / 2) + (theClientSize.height() / 2));
+ }
+ }
+
+ QRect glRect = m_ClientRect;
+ glRect.setWidth(int(fixedDevicePixelRatio() * m_ClientRect.width()));
+ glRect.setHeight(int(fixedDevicePixelRatio() * m_ClientRect.height()));
+ g_StudioApp.getRenderer().SetViewRect(glRect);
+}
+
+void CPlayerContainerWnd::wheelEvent(QWheelEvent* event)
+{
+ const bool theCtrlKeyIsDown = event->modifiers() & Qt::ControlModifier;
+
+ if (!theCtrlKeyIsDown && !IsDeploymentView()) {
+ // Zoom when in edit camera view
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseWheel(
+ SceneDragSenderType::Matte, event->delta(), STUDIO_TOOLMODE_CAMERA_ZOOM);
+ } else {
+ // Otherwise, scroll the view
+ QScrollArea::wheelEvent(event);
+ }
+}
+
+void CPlayerContainerWnd::scrollContentsBy(int, int)
+{
+ long x, y;
+ SetPlayerWndPosition(x, y);
+}
+
+//==============================================================================
+/**
+ * Set the view mode of the current scene view, whether we are in editing mode
+ * or deployment mode. For editing mode, we want to use the full scene area without
+ * any matte area.
+ * @param inViewMode the view mode of this scene
+ */
+void CPlayerContainerWnd::SetViewMode(EViewMode inViewMode)
+{
+ m_ViewMode = inViewMode;
+ m_SceneView->recheckSizingMode();
+
+ // When viewmode changes, update scene camera view mode status
+ // into renderer context
+ g_StudioApp.getRenderer().setIsSceneCameraView(IsDeploymentView());
+}
+
+//==============================================================================
+/**
+ * return the view mode of the current scene view, whether we are in editing mode
+ * or deployment mode. For editing mode, we want to use the full scene area without
+ * any matte area.
+ * @return the current view mode
+ */
+CPlayerContainerWnd::EViewMode CPlayerContainerWnd::GetViewMode()
+{
+ return m_ViewMode;
+}
+
+//==============================================================================
+/**
+ * Checks whether we are in deployment view mode.
+ * @return true if is in deployment view mode, else false
+ */
+bool CPlayerContainerWnd::IsDeploymentView()
+{
+ return m_ViewMode == VIEW_SCENE ? true : false;
+}
+
+void CPlayerContainerWnd::setToolMode(long inMode)
+{
+ if (m_PlayerWnd)
+ m_PlayerWnd->setToolMode(inMode);
+}
+
+QSize CPlayerContainerWnd::GetEffectivePresentationSize() const
+{
+ QSize theSize = g_StudioApp.GetCore()->GetStudioProjectSettings()->getPresentationSize();
+
+ // If we have guides, resize the window with enough space for the guides as well as the
+ // presentation
+ // This is a very dirty hack because we are of course hardcoding the size of the guides.
+ // If the size of the guides never changes, the bet paid off.
+ if (g_StudioApp.getRenderer().AreGuidesEnabled())
+ theSize += QSize(32, 32);
+
+ return theSize / fixedDevicePixelRatio();
+}
+
+//==============================================================================
+
+void CPlayerContainerWnd::SetPlayerWnd(CPlayerWnd *inPlayerWnd)
+{
+ m_PlayerWnd = inPlayerWnd;
+ viewport()->setBackgroundRole(QPalette::Dark);
+ m_PlayerWnd->setContainerWnd(this);
+ m_PlayerWnd->setParent(viewport());
+ m_PlayerWnd->setVisible(true);
+ m_PlayerWnd->resize(m_PlayerWnd->sizeHint());
+}
+
+void CPlayerContainerWnd::resizeEvent(QResizeEvent* event)
+{
+ QAbstractScrollArea::resizeEvent(event);
+
+ SetScrollRanges();
+}
diff --git a/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.h b/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.h
new file mode 100644
index 00000000..4ef40b90
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/PlayerContainerWnd.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** 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_PLAYER_CONTAINER_WND_H
+#define INCLUDED_PLAYER_CONTAINER_WND_H 1
+
+#pragma once
+
+#include <QtWidgets/qscrollarea.h>
+
+class CSceneView;
+class CStudioApp;
+class CPlayerWnd;
+class CWGLRenderContext;
+
+class CPlayerContainerWnd : public QScrollArea
+{
+ Q_OBJECT
+public:
+ typedef enum {
+ VIEW_EDIT = 0, ///< Edit View
+ VIEW_SCENE, ///< Scene View
+ } EViewMode;
+
+public:
+ CPlayerContainerWnd(CSceneView *inSceneView);
+ virtual ~CPlayerContainerWnd();
+
+ void SetPlayerWnd(CPlayerWnd *inPlayerWnd);
+ void SetPlayerWndPosition(long &outLeftPresentationEdge, long &outTopPresentionEdge);
+ void SetScrollRanges();
+ void RecenterClient();
+ void OnRulerGuideToggled();
+
+ void SetViewMode(EViewMode inViewMode);
+ EViewMode GetViewMode();
+ bool IsDeploymentView();
+ void setToolMode(long inMode);
+
+ QRect GetDisplayedClientRect() const { return m_ClientRect; }
+
+ QSize GetEffectivePresentationSize() const;
+
+ qreal fixedDevicePixelRatio() const;
+
+Q_SIGNALS:
+ void toolChanged();
+
+protected:
+ void resizeEvent(QResizeEvent *) override;
+ void wheelEvent(QWheelEvent *) override;
+ void scrollContentsBy(int, int) override;
+
+private:
+ CPlayerContainerWnd() {}
+ bool ShouldHideScrollBars();
+
+protected:
+ CSceneView *m_SceneView; ///< Pointer to the SceneView for rulers manipulation
+ CPlayerWnd *m_PlayerWnd; ///< Pointer to the window control that contains client
+ QRect m_ClientRect; ///< The rect where the client is drawn
+ EViewMode m_ViewMode;
+};
+
+#endif // INCLUDED_PLAYER_CONTAINER_WND_H
diff --git a/src/Authoring/Qt3DStudio/UI/PlayerWnd.cpp b/src/Authoring/Qt3DStudio/UI/PlayerWnd.cpp
new file mode 100644
index 00000000..a528cea1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/PlayerWnd.cpp
@@ -0,0 +1,266 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "PlayerWnd.h"
+#include "MainFrm.h"
+#include "SceneView.h"
+#include "Dispatch.h"
+#include "MasterP.h"
+#include "HotKeys.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "Dispatch.h"
+#include "HotKeys.h"
+#include "MouseCursor.h"
+#include "ResourceCache.h"
+#include "SceneDropTarget.h"
+#include "Core.h"
+#include "IDragable.h"
+#include "WGLRenderContext.h"
+#include "IStudioRenderer.h"
+
+#include <QtWidgets/qmessagebox.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qscreen.h>
+
+CPlayerWnd::CPlayerWnd(QWidget *parent)
+ : QOpenGLWidget(parent)
+ , m_containerWnd(nullptr)
+ , m_mouseDown(false)
+{
+ setAcceptDrops(true);
+ RegisterForDnd(this);
+ AddMainFlavor(QT3DS_FLAVOR_FILE);
+ AddMainFlavor(QT3DS_FLAVOR_ASSET_UICFILE);
+ AddMainFlavor(QT3DS_FLAVOR_ASSET_LIB);
+ AddMainFlavor(QT3DS_FLAVOR_BASIC_OBJECTS);
+
+ setFormat(CWGLRenderContext::selectSurfaceFormat(this));
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ m_previousToolMode = g_StudioApp.GetToolMode();
+}
+
+CPlayerWnd::~CPlayerWnd()
+{
+}
+
+void CPlayerWnd::resizeEvent(QResizeEvent *event)
+{
+ QOpenGLWidget::resizeEvent(event);
+ update();
+}
+
+void CPlayerWnd::mouseMoveEvent(QMouseEvent *event)
+{
+ if (m_mouseDown) {
+ long theModifierKeys = 0;
+ if (event->buttons() & Qt::LeftButton
+ || (!g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance().Valid())
+ && !m_containerWnd->IsDeploymentView()) {
+ // When in edit camera view and nothing is selected, all buttons are mapped
+ // as left button. That is how camera control tools work, they are all
+ // assuming left button.
+ theModifierKeys = CHotKeys::MOUSE_LBUTTON | CHotKeys::GetCurrentKeyModifiers();
+ } else if (event->buttons() & Qt::RightButton) {
+ theModifierKeys = CHotKeys::MOUSE_RBUTTON | CHotKeys::GetCurrentKeyModifiers();
+ } else if (event->buttons() & Qt::MiddleButton) {
+ theModifierKeys = CHotKeys::MOUSE_MBUTTON | CHotKeys::GetCurrentKeyModifiers();
+ }
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseDrag(
+ SceneDragSenderType::Matte, event->pos(), g_StudioApp.GetToolMode(),
+ theModifierKeys);
+ } else {
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseMove(
+ SceneDragSenderType::SceneWindow, event->pos());
+ }
+}
+
+void CPlayerWnd::mousePressEvent(QMouseEvent *event)
+{
+ g_StudioApp.setLastActiveView(this);
+
+ long toolMode = g_StudioApp.GetToolMode();
+ const Qt::MouseButton btn = event->button();
+ bool toolChanged = false;
+
+ if (!m_containerWnd->IsDeploymentView() && (event->modifiers() & Qt::AltModifier)) {
+ // We are in edit camera view, so we are in Alt-click camera tool
+ // controlling mode
+ m_mouseDown = true;
+ if (btn == Qt::MiddleButton) {
+ // Alt + Wheel Click
+ toolMode = STUDIO_TOOLMODE_CAMERA_PAN;
+ toolChanged = true;
+ } else if (btn == Qt::LeftButton) {
+ // Alt + Left Click
+ if (g_StudioApp.getRenderer().DoesEditCameraSupportRotation(
+ g_StudioApp.getRenderer().GetEditCamera())) {
+ toolMode = STUDIO_TOOLMODE_CAMERA_ROTATE;
+ toolChanged = true;
+ }
+ } else if (btn == Qt::RightButton) {
+ // Alt + Right Click
+ toolMode = STUDIO_TOOLMODE_CAMERA_ZOOM;
+ toolChanged = true;
+ }
+
+ if (toolChanged) {
+ g_StudioApp.SetToolMode(toolMode);
+ Q_EMIT m_containerWnd->toolChanged();
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseDown(SceneDragSenderType::Matte,
+ event->pos(), toolMode);
+ }
+ } else {
+ if (btn == Qt::LeftButton || btn == Qt::RightButton) {
+ // Pause playback for the duration of the mouse click
+ if (g_StudioApp.IsPlaying()) {
+ g_StudioApp.PlaybackStopNoRestore();
+ m_resumePlayOnMouseRelease = true;
+ } else {
+ m_resumePlayOnMouseRelease = false;
+ }
+
+ toolMode = g_StudioApp.GetToolMode();
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseDown(
+ SceneDragSenderType::SceneWindow, event->pos(), toolMode);
+ m_mouseDown = true;
+ } else if (btn == Qt::MiddleButton) {
+ event->ignore();
+ }
+ }
+}
+
+void CPlayerWnd::mouseReleaseEvent(QMouseEvent *event)
+{
+ const Qt::MouseButton btn = event->button();
+
+ if (!m_containerWnd->IsDeploymentView()) {
+ // We are in edit camera view
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseUp(SceneDragSenderType::Matte);
+ g_StudioApp.GetCore()->CommitCurrentCommand();
+ m_mouseDown = false;
+ // Restore normal tool mode
+ g_StudioApp.SetToolMode(m_previousToolMode);
+ Q_EMIT m_containerWnd->toolChanged();
+ } else {
+ if (btn == Qt::LeftButton || btn == Qt::RightButton) {
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseUp(
+ SceneDragSenderType::SceneWindow);
+ g_StudioApp.GetCore()->CommitCurrentCommand();
+ m_mouseDown = false;
+ if (m_resumePlayOnMouseRelease) {
+ m_resumePlayOnMouseRelease = false;
+ g_StudioApp.PlaybackPlay();
+ }
+ } else if (btn == Qt::MiddleButton) {
+ event->ignore();
+ }
+ }
+}
+
+void CPlayerWnd::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ g_StudioApp.GetCore()->GetDispatch()->FireSceneMouseDblClick(
+ SceneDragSenderType::SceneWindow, event->pos());
+}
+
+bool CPlayerWnd::OnDragWithin(CDropSource &inSource)
+{
+ CSceneViewDropTarget theTarget;
+ return theTarget.Accept(inSource);
+}
+
+bool CPlayerWnd::OnDragReceive(CDropSource &inSource)
+{
+ CSceneViewDropTarget theTarget;
+ Q_EMIT dropReceived();
+ return theTarget.Drop(inSource);
+}
+
+void CPlayerWnd::setContainerWnd(CPlayerContainerWnd *inContainerWnd)
+{
+ m_containerWnd = inContainerWnd;
+ updateGeometry();
+}
+
+QSize CPlayerWnd::sizeHint() const
+{
+ if (m_containerWnd)
+ return m_containerWnd->GetEffectivePresentationSize();
+ else
+ return QOpenGLWidget::sizeHint();
+}
+
+void CPlayerWnd::initializeGL()
+{
+ Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer());
+ if (theRenderer.IsInitialized() == false) {
+ try {
+ theRenderer.Initialize(this);
+ } catch (...) {
+ QMessageBox::critical(this, tr("Fatal Error"),
+ tr("Unable to initialize OpenGL.\nThis may be because your "
+ "graphic device is not sufficient, or simply because your "
+ "driver is too old.\n\nPlease try upgrading your graphics "
+ "driver and try again."));
+ exit(1);
+ }
+ }
+}
+
+void CPlayerWnd::paintGL()
+{
+ Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer());
+ // Don't use request render here, this has to be synchronous inside paintGL
+ theRenderer.RenderNow();
+
+ Q_EMIT newFrame();
+}
+
+qreal CPlayerWnd::fixedDevicePixelRatio() const
+{
+ // Fix a problem on X11: https://bugreports.qt.io/browse/QTBUG-65570
+ qreal ratio = devicePixelRatio();
+ if (QWindow *w = window()->windowHandle()) {
+ if (QScreen *s = w->screen())
+ ratio = s->devicePixelRatio();
+ }
+ return ratio;
+}
+
+void CPlayerWnd::resizeGL(int width, int height)
+{
+ // This also passes the new FBO to the OpenGLContext
+ Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer());
+ theRenderer.SetViewRect(QRect(0, 0, width * fixedDevicePixelRatio(),
+ height * fixedDevicePixelRatio()));
+}
diff --git a/src/Authoring/Qt3DStudio/UI/PlayerWnd.h b/src/Authoring/Qt3DStudio/UI/PlayerWnd.h
new file mode 100644
index 00000000..de005220
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/PlayerWnd.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** 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_PLAYERWND_H
+#define INCLUDED_PLAYERWND_H 1
+
+#pragma once
+
+#include "DropContainer.h"
+#include "PlayerContainerWnd.h"
+
+#include <QtWidgets/qopenglwidget.h>
+
+class CPlayerContainerWnd;
+class CStudioApp;
+class CMouseCursor;
+class CHotkeys;
+
+class CPlayerWnd : public QOpenGLWidget, public CWinDropContainer
+{
+ Q_OBJECT
+public:
+ explicit CPlayerWnd(QWidget *parent = nullptr);
+ ~CPlayerWnd();
+
+ void setContainerWnd(CPlayerContainerWnd *inSceneView);
+ CPlayerContainerWnd *containerWnd() const { return m_containerWnd; }
+
+ QSize sizeHint() const override;
+
+ bool OnDragWithin(CDropSource &inSource) override;
+ bool OnDragReceive(CDropSource &inSource) override;
+ void OnDragLeave() override {}
+ void OnReflectMouse(CPt &, Qt::KeyboardModifiers) override {}
+
+ qreal fixedDevicePixelRatio() const;
+ void setToolMode(long toolMode) { m_previousToolMode = toolMode; }
+
+protected:
+
+ CPlayerContainerWnd *m_containerWnd;
+ bool m_mouseDown;
+ bool m_resumePlayOnMouseRelease = false;
+ long m_previousToolMode;
+
+Q_SIGNALS:
+ void dropReceived();
+ void newFrame();
+
+protected:
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+ void initializeGL() override;
+ void paintGL() override;
+ void resizeGL(int width, int height) override;
+};
+
+#endif // INCLUDED_PLAYERWND_H
diff --git a/src/Authoring/Qt3DStudio/UI/RecentItems.cpp b/src/Authoring/Qt3DStudio/UI/RecentItems.cpp
new file mode 100644
index 00000000..681ae421
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/RecentItems.cpp
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "RecentItems.h"
+#include "StudioPreferences.h"
+
+#include <QtWidgets/qmenu.h>
+#include <QtCore/qfileinfo.h>
+
+const int CRecentItems::MAX_ITEMS = 10; // maximum allowed number of recent items
+
+CRecentItems::CRecentItems(QMenu *inMenuID)
+{
+ m_Menu = inMenuID;
+
+ ReconstructList();
+}
+
+CRecentItems::~CRecentItems()
+{
+}
+
+void CRecentItems::AddRecentItem(const QString &inItem)
+{
+ RemoveRecentItem(inItem, false);
+
+ m_RecentItems.insert(m_RecentItems.begin(), inItem);
+
+ while (m_RecentItems.size() > MAX_ITEMS)
+ m_RecentItems.pop_back();
+
+ RebuildList();
+}
+
+void CRecentItems::RemoveRecentItem(const QString &inItem, bool rebuild)
+{
+ auto thePos = m_RecentItems.begin();
+ for (; thePos != m_RecentItems.end(); ++thePos) {
+ if (*thePos == inItem) {
+ m_RecentItems.erase(thePos);
+ break;
+ }
+ }
+
+ if (rebuild)
+ RebuildList();
+}
+
+// load the recent items from the preferences file to m_RecentItems and add them to menu
+void CRecentItems::ReconstructList()
+{
+ m_Menu->clear();
+ m_RecentItems.clear();
+
+ int numRecentItems = CStudioPreferences::getNumRecentItems();
+ if (numRecentItems > MAX_ITEMS)
+ numRecentItems = MAX_ITEMS;
+
+ for (int i = 0; i < numRecentItems; ++i) {
+ QString theFile = CStudioPreferences::getRecentItem(i);
+ if (!theFile.isEmpty() && QFileInfo(theFile).exists()) {
+ m_RecentItems.push_back(theFile);
+ QAction *act = m_Menu->addAction(theFile, this, &CRecentItems::onTriggerRecent);
+ act->setData(i);
+ }
+ }
+}
+
+// save the recent items from m_RecentItems to the preferences file. Also recreate the menu.
+void CRecentItems::RebuildList()
+{
+ m_Menu->clear();
+
+ CStudioPreferences::setNumRecentItems(GetItemCount());
+
+ for (int i = 0; i < m_RecentItems.size(); ++i) {
+ const QString &item_i = m_RecentItems.at(i);
+ if (QFileInfo(item_i).exists()) {
+ QAction *act = m_Menu->addAction(item_i, this, &CRecentItems::onTriggerRecent);
+ act->setData(i);
+ CStudioPreferences::setRecentItem(i, item_i);
+ }
+ }
+}
+
+QString CRecentItems::GetItem(long inIndex) const
+{
+ return m_RecentItems.at(inIndex);
+}
+
+void CRecentItems::onTriggerRecent()
+{
+ const int index = qobject_cast<QAction *>(sender())->data().toInt();
+ Q_EMIT openRecent(index);
+}
+
diff --git a/src/Authoring/Qt3DStudio/UI/RecentItems.h b/src/Authoring/Qt3DStudio/UI/RecentItems.h
new file mode 100644
index 00000000..9557353f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/RecentItems.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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_RECENT_ITEMS_H
+#define INCLUDED_RECENT_ITEMS_H 1
+
+#pragma once
+
+#include <QObject>
+#include <vector>
+
+QT_FORWARD_DECLARE_CLASS(QMenu)
+
+class CRecentItems : public QObject
+{
+ Q_OBJECT
+
+public:
+ static const int MAX_ITEMS;
+
+Q_SIGNALS:
+ void openRecent(int index);
+public:
+ CRecentItems(QMenu *inMenu);
+ virtual ~CRecentItems();
+
+ void AddRecentItem(const QString &inItem);
+ void RemoveRecentItem(const QString &inItem, bool rebuild = true);
+
+ QString GetItem(long inIndex) const;
+ long GetItemCount() const { return (long)m_RecentItems.size(); }
+
+protected:
+ void ReconstructList();
+ void RebuildList();
+
+ std::vector<QString> m_RecentItems;
+
+ QMenu *m_Menu;
+
+private Q_SLOTS:
+ void onTriggerRecent();
+};
+#endif // INCLUDED_RECENT_ITEMS_H
diff --git a/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.cpp b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.cpp
new file mode 100644
index 00000000..74345832
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.cpp
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "Qt3DSCommonPrecompile.h"
+#include "ResetKeyframeValuesDlg.h"
+#include "ui_ResetKeyframeValuesDlg.h"
+
+#include <QtWidgets/qpushbutton.h>
+#include <QtWidgets/qstyle.h>
+
+CResetKeyframeValuesDlg::CResetKeyframeValuesDlg(QWidget *pParent)
+ : QDialog(pParent)
+ , m_ui(new Ui::ResetKeyframeValuesDlg)
+{
+ m_ui->setupUi(this);
+ m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Reset Key"));
+ const int i = style()->pixelMetric(QStyle::PM_LargeIconSize);
+ const QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
+ m_ui->m_WarningIcon->setPixmap(icon.pixmap(i, i));
+ setFixedSize(size());
+}
+
+CResetKeyframeValuesDlg::~CResetKeyframeValuesDlg()
+{
+}
diff --git a/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.h b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.h
new file mode 100644
index 00000000..ca5f17f6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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
+//==============================================================================
+#pragma once
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+ class ResetKeyframeValuesDlg;
+}
+QT_END_NAMESPACE
+
+class CResetKeyframeValuesDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ CResetKeyframeValuesDlg(QWidget *pParent = nullptr); // standard constructor
+ ~CResetKeyframeValuesDlg();
+
+private:
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::ResetKeyframeValuesDlg)> m_ui;
+};
diff --git a/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.ui b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.ui
new file mode 100644
index 00000000..1d7aa670
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/ResetKeyframeValuesDlg.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ResetKeyframeValuesDlg</class>
+ <widget class="QDialog" name="ResetKeyframeValuesDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>534</width>
+ <height>146</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Reset Keyframe Curves</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="m_WarningIcon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Warning: You have selected keyframes that were created in an external application. Proceeding with this action will reset all curve values to the defaults.
+
+Are you sure you would like to proceed?</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ResetKeyframeValuesDlg</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>ResetKeyframeValuesDlg</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/UI/SceneView.cpp b/src/Authoring/Qt3DStudio/UI/SceneView.cpp
new file mode 100644
index 00000000..5e617d92
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/SceneView.cpp
@@ -0,0 +1,362 @@
+/****************************************************************************
+**
+** 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 "SceneView.h"
+#include "StudioPreferences.h"
+#include "HotKeys.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "Dispatch.h"
+#include "MouseCursor.h"
+#include "ResourceCache.h"
+#include "Core.h"
+#include "IStudioRenderer.h"
+
+#include <QtGui/qevent.h>
+
+CSceneView::CSceneView(QWidget *parent)
+ : QWidget(parent)
+ , m_playerContainerWnd(new CPlayerContainerWnd(this))
+ , m_playerWnd(new CPlayerWnd(m_playerContainerWnd.data()))
+{
+ m_previousSelectMode = g_StudioApp.GetSelectMode();
+
+ connect(m_playerContainerWnd.data(), &CPlayerContainerWnd::toolChanged,
+ this, [=](){ setViewCursor(); Q_EMIT toolChanged(); });
+
+ connect(m_playerWnd.data(), &CPlayerWnd::dropReceived, this, &CSceneView::onDropReceived);
+
+ m_playerContainerWnd->SetPlayerWnd(m_playerWnd.data());
+
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+CSceneView::CSceneView()
+{
+ m_previousSelectMode = g_StudioApp.GetSelectMode();
+}
+
+void CSceneView::onDropReceived()
+{
+ setFocus();
+}
+
+CPlayerWnd *CSceneView::getPlayerWnd() const
+{
+ return m_playerWnd.data();
+}
+
+CSceneView::~CSceneView()
+{
+ CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch();
+ // Stop listening for selection change events
+ theDispatch->RemoveEditCameraChangeListener(this);
+}
+
+QSize CSceneView::sizeHint() const
+{
+ return CStudioPreferences::GetDefaultClientSize();
+}
+
+//==============================================================================
+/**
+ * Called by the framework after the view is first attached
+ * to the document, but before the view is initially displayed.
+ * Notifies the Main Frame that the palettes need to be shown, and destroys the
+ * splash screen.
+ */
+void CSceneView::showEvent(QShowEvent *event)
+{
+ QWidget::showEvent(event);
+
+ m_playerContainerWnd->RecenterClient();
+
+ // Set the scroll information.
+ m_playerContainerWnd->SetScrollRanges();
+
+ // Create the cursors
+ m_arrowCursor = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_ARROW);
+ m_cursorGroupMove = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_GROUP_MOVE);
+ m_cursorGroupRotate =
+ CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_GROUP_ROTATE);
+ m_cursorGroupScale = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_GROUP_SCALE);
+ m_cursorItemMove = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_ITEM_MOVE);
+ m_cursorItemRotate = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_ITEM_ROTATE);
+ m_cursorItemScale = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_ITEM_SCALE);
+ m_cursorEditCameraPan =
+ CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_PAN);
+ m_cursorEditCameraRotate =
+ CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_ROTATE);
+ m_cursorEditCameraZoom =
+ CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_ZOOM);
+
+ g_StudioApp.GetCore()->GetDispatch()->AddEditCameraChangeListener(this);
+
+ // Set the default cursor
+ setViewCursor();
+}
+
+//==============================================================================
+/**
+ * OnToolGroupSelection: Called when the Group Selection button is pressed.
+ * Sets the current tool mode and changes the cursor.
+ */
+void CSceneView::onToolGroupSelection()
+{
+ m_previousSelectMode = g_StudioApp.GetSelectMode();
+ g_StudioApp.SetSelectMode(STUDIO_SELECTMODE_GROUP);
+ setViewCursor();
+ Q_EMIT toolChanged();
+}
+
+//==============================================================================
+/**
+ * OnToolItemSelection: Called when the Item Selection button is pressed.
+ * Sets the current tool mode and changes the cursor.
+ */
+void CSceneView::onToolItemSelection()
+{
+ m_previousSelectMode = g_StudioApp.GetSelectMode();
+ g_StudioApp.SetSelectMode(STUDIO_SELECTMODE_ENTITY);
+ setViewCursor();
+ Q_EMIT toolChanged();
+}
+
+//==============================================================================
+/**
+ * SetViewCursor: Sets the cursor for the view according to the current Client Tool.
+ *
+ * Changes the cursor depending on the current tool mode. Each time the Tool mode
+ * changes, you should call this function. If you add extra tool modes, you
+ * will need to adjust this function.
+ */
+void CSceneView::setViewCursor()
+{
+ long theCurrentToolSettings = g_StudioApp.GetToolMode();
+ long theCurrentSelectSettings = g_StudioApp.GetSelectMode();
+
+ // See what tool mode we are in
+ switch (theCurrentToolSettings) {
+ case STUDIO_TOOLMODE_MOVE:
+ switch (theCurrentSelectSettings) {
+ case STUDIO_SELECTMODE_ENTITY:
+ m_playerWnd->setCursor(m_cursorItemMove);
+ break;
+ case STUDIO_SELECTMODE_GROUP:
+ m_playerWnd->setCursor(m_cursorGroupMove);
+ break;
+ // Default - shouldn't happen
+ default:
+ m_playerWnd->setCursor(m_cursorItemMove);
+ break;
+ }
+ break;
+
+ case STUDIO_TOOLMODE_ROTATE:
+ switch (theCurrentSelectSettings) {
+ case STUDIO_SELECTMODE_ENTITY:
+ m_playerWnd->setCursor(m_cursorItemRotate);
+ break;
+ case STUDIO_SELECTMODE_GROUP:
+ m_playerWnd->setCursor(m_cursorGroupRotate);
+ break;
+ // Default - shouldn't happen
+ default:
+ m_playerWnd->setCursor(m_cursorItemRotate);
+ break;
+ }
+ break;
+
+ case STUDIO_TOOLMODE_SCALE:
+ switch (theCurrentSelectSettings) {
+ case STUDIO_SELECTMODE_ENTITY:
+ m_playerWnd->setCursor(m_cursorItemScale);
+ break;
+ case STUDIO_SELECTMODE_GROUP:
+ m_playerWnd->setCursor(m_cursorGroupScale);
+ break;
+ // Default - shouldn't happen
+ default:
+ m_playerWnd->setCursor(m_cursorItemScale);
+ break;
+ }
+ break;
+
+ case STUDIO_TOOLMODE_CAMERA_PAN:
+ m_playerWnd->setCursor(m_cursorEditCameraPan);
+ break;
+
+ case STUDIO_TOOLMODE_CAMERA_ZOOM:
+ m_playerWnd->setCursor(m_cursorEditCameraZoom);
+ break;
+
+ case STUDIO_TOOLMODE_CAMERA_ROTATE:
+ m_playerWnd->setCursor(m_cursorEditCameraRotate);
+ break;
+
+ // Default - shouldn't happen
+ default:
+ m_playerWnd->setCursor(m_cursorItemMove);
+ break;
+ }
+}
+
+//==============================================================================
+/**
+ * RecalcMatte: Recalculates the matte around the Client based on the settings.
+ *
+ * This will recalculate the matting around the Client based on the Client's
+ * current size. If the Client is a "Fit To Window" mode, then the matte region
+ * is cleared.
+ */
+//==============================================================================
+void CSceneView::recalcMatte()
+{
+ long theXOffset = 0;
+ long theYOffset = 0;
+ QRect theClientRect = rect();
+
+ // Adjust the client area based if rulers are visible
+ if (m_playerContainerWnd) {
+ m_playerContainerWnd->setGeometry(theXOffset, theYOffset,
+ theClientRect.width() - theXOffset,
+ theClientRect.height() - theYOffset);
+
+ // Recenter the Client rect
+ m_playerContainerWnd->RecenterClient();
+ }
+}
+
+void CSceneView::resizeEvent(QResizeEvent* event)
+{
+ QWidget::resizeEvent(event);
+ if (m_playerContainerWnd) {
+ recalcMatte();
+ setPlayerWndPosition();
+ g_StudioApp.GetCore()->GetDoc( )->GetSceneGraph()->RequestRender();
+ }
+}
+
+//==============================================================================
+/**
+ * SetPlayerWndPosition: Sets the position of the child player window
+ *
+ * Called when the view is scrolled to position the child player window
+ *
+ */
+//==============================================================================
+void CSceneView::setPlayerWndPosition()
+{
+ // Move the child player window to coincide with the scrollbars
+ if (m_playerContainerWnd) {
+ long theLeft, theTop;
+ // Retrieve the left and top edge of the presentation currently in view
+ m_playerContainerWnd->SetPlayerWndPosition(theLeft, theTop);
+ m_playerContainerWnd->update();
+ }
+}
+
+//==============================================================================
+/**
+ * Resets its scroll ranges and recenters client in the window. This is called when
+ * an outside source needs to tell the scene view that size ranges have changed such
+ * as the preferences telling the sceneview that the size changed.
+ */
+void CSceneView::recheckSizingMode()
+{
+ if (m_playerContainerWnd)
+ m_playerContainerWnd->SetScrollRanges();
+}
+
+//==============================================================================
+/**
+ * Redirect to PlayerContainerWnd to check whether we are in deployment view mode.
+ * @return true if is in deployment view mode, else false
+ */
+bool CSceneView::isDeploymentView()
+{
+ // Default mode is SCENE_VIEW so if playercontainerwnd does not exist (should only happen on
+ // startup), it is deployment view
+ bool theStatus = true;
+ if (m_playerContainerWnd)
+ theStatus = m_playerContainerWnd->IsDeploymentView();
+
+ return theStatus;
+}
+
+//==============================================================================
+/**
+ * Redirect to PlayerContainerWnd to set the view mode of the current scene view,
+ * whether we are in editing mode or deployment mode. For editing mode, we want to
+ * use the full scene area without any matte area.
+ * @param inViewMode the view mode of this scene
+ */
+void CSceneView::setViewMode(CPlayerContainerWnd::EViewMode inViewMode)
+{
+ if (m_playerContainerWnd)
+ m_playerContainerWnd->SetViewMode(inViewMode);
+}
+
+void CSceneView::setToolMode(long inMode)
+{
+ if (m_playerContainerWnd)
+ m_playerContainerWnd->setToolMode(inMode);
+ setViewCursor();
+}
+
+//==============================================================================
+/**
+ * When the active camera is changed, the display string needs to be changed. Hence
+ * find which entry is the one which is modified and update with the new string
+ * @param inCamera the camera that has been modified
+ */
+void CSceneView::onEditCameraChanged()
+{
+ // Reset any scrolling and recalculate the window position.
+ if (m_playerContainerWnd) {
+ m_playerContainerWnd->SetScrollRanges();
+ recalcMatte();
+ setPlayerWndPosition();
+ }
+
+ // Update the view mode accordingly
+ setViewMode(g_StudioApp.getRenderer().GetEditCamera() >= 0 ? CPlayerContainerWnd::VIEW_EDIT
+ : CPlayerContainerWnd::VIEW_SCENE);
+ m_playerWnd->update();
+}
+
+void CSceneView::onAuthorZoomChanged()
+{
+ onEditCameraChanged();
+}
+
+void CSceneView::onRulerGuideToggled()
+{
+ m_playerContainerWnd->OnRulerGuideToggled();
+}
diff --git a/src/Authoring/Qt3DStudio/UI/SceneView.h b/src/Authoring/Qt3DStudio/UI/SceneView.h
new file mode 100644
index 00000000..c4948e39
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/SceneView.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** 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_SCENE_VIEW_H
+#define INCLUDED_SCENE_VIEW_H 1
+
+#pragma once
+
+#include "PlayerWnd.h"
+#include "PlayerContainerWnd.h"
+#include "DispatchListeners.h"
+
+#include <QtWidgets/qwidget.h>
+#include <QtGui/qcursor.h>
+
+class CHotKeys;
+
+class CSceneView : public QWidget,
+ public CEditCameraChangeListener
+{
+ Q_OBJECT
+protected:
+ QScopedPointer<CPlayerContainerWnd> m_playerContainerWnd; // first-level child
+ QScopedPointer<CPlayerWnd> m_playerWnd; // second-level child (grandchild)
+ QCursor m_arrowCursor; // A pointer to the current cursor (changes according to mode)
+ QCursor m_cursorGroupMove; // The move group cursor
+ QCursor m_cursorGroupRotate; // The rotate group cursor
+ QCursor m_cursorGroupScale; // The scale group cursor
+ QCursor m_cursorItemMove; // The move item cursor
+ QCursor m_cursorItemRotate; // The rotate item cursor
+ QCursor m_cursorItemScale; // The scale item cursor
+ QCursor m_cursorEditCameraPan; // The edit camera pan cursor
+ QCursor m_cursorEditCameraRotate; // The edit camera rotate cursor
+ QCursor m_cursorEditCameraZoom; // The edit camera zoom cursor
+
+ long m_previousSelectMode; // The previous select mode
+
+public:
+ CSceneView(QWidget *parent = nullptr);
+ CSceneView(); // used for serialization only!
+ virtual ~CSceneView();
+
+ void setViewCursor();
+ void recheckSizingMode();
+
+ void setPlayerWndPosition();
+
+ // redirect to/from PlayerContainerWnd
+ bool isDeploymentView();
+ void setViewMode(CPlayerContainerWnd::EViewMode inViewMode);
+ void setToolMode(long inMode);
+
+ void onRulerGuideToggled();
+
+ // CEditCameraChangeListener
+ void onEditCameraChanged() override;
+ void onEditCamerasTransformed() override {}
+ void onAuthorZoomChanged() override;
+
+ QSize sizeHint() const override;
+
+ void onToolGroupSelection();
+ void onToolItemSelection();
+
+ CPlayerWnd *getPlayerWnd() const;
+
+Q_SIGNALS:
+ void toolChanged();
+
+protected:
+ void showEvent(QShowEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void recalcMatte();
+ void onDropReceived();
+};
+
+#endif // INCLUDED_SCENE_VIEW_H
diff --git a/src/Authoring/Qt3DStudio/UI/StartupDlg.cpp b/src/Authoring/Qt3DStudio/UI/StartupDlg.cpp
new file mode 100644
index 00000000..c5cc4c47
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StartupDlg.cpp
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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 "StudioDefs.h"
+#include "StartupDlg.h"
+#include "StudioPreferences.h"
+#include "ui_StartupDlg.h"
+
+#include <QtCore/qfileinfo.h>
+#include <QtGui/qpalette.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdir.h>
+#include <QtWidgets/qdesktopwidget.h>
+
+// CStartupDlg dialog
+
+CStartupDlg::CStartupDlg(QWidget *pParent)
+ : QDialog(pParent, Qt::MSWindowsFixedSizeDialogHint)
+ , m_Choice(EStartupChoice_Invalid)
+ , m_ui(new Ui::StartupDlg)
+ , m_palette(nullptr)
+{
+ m_ui->setupUi(this);
+}
+
+CStartupDlg::~CStartupDlg()
+{
+ delete m_palette;
+}
+
+static QString GetFileTimeReadable(const Qt3DSFile &inFile)
+{
+ QFileInfo finfo(inFile.GetAbsolutePath().toQString());
+ if (!finfo.exists())
+ return {};
+
+ return finfo.lastModified().toString("MM/dd/yyyy");
+}
+
+void CStartupDlg::showEvent(QShowEvent *ev)
+{
+ OnInitDialog();
+ QDialog::showEvent(ev);
+}
+
+void CStartupDlg::reject()
+{
+ m_Choice = EStartupChoice_Exit;
+ QDialog::reject();
+}
+
+void CStartupDlg::OnInitDialog()
+{
+ QWidget *p = qobject_cast<QWidget *>(parent());
+ if (p) {
+ QRect pRect;
+ if (p->isMaximized()) {
+ pRect = QApplication::desktop()->availableGeometry(
+ QApplication::desktop()->screenNumber(p));
+ } else {
+ pRect = p->frameGeometry();
+ }
+
+ move(pRect.x() + pRect.width() / 2 - width() / 2,
+ pRect.y() + pRect.height() / 2 - height() / 2);
+ }
+
+ connect(m_ui->newDocument, &ClickableLabel::clicked, this, &CStartupDlg::OnNewDocClicked);
+ connect(m_ui->openDocument, &ClickableLabel::clicked, this, &CStartupDlg::OnOpenDocClicked);
+
+ // Load the product version
+ m_ProductVersionStr = QStringLiteral("Qt 3D Studio v") + CStudioPreferences::GetVersionString();
+ m_ui->versionStr->setText(m_ProductVersionStr);
+
+ // Populate the recent document list
+ for (uint theIndex = 0; theIndex < RECENT_COUNT; ++theIndex) {
+ ClickableLabel *recent
+ = findChild<ClickableLabel *>(QStringLiteral("recent%1").arg(theIndex));
+ connect(recent, &ClickableLabel::clicked, this, &CStartupDlg::OnStnClickedStartupRecent);
+
+ recent->setProperty("recentIndex", theIndex);
+
+ if (m_RecentDocs.size() > theIndex) {
+ // Set the name
+ recent->setText(QFileInfo(m_RecentDocs[theIndex]).fileName());
+ // Set path and date to tooltip
+ QString toolTip = m_RecentDocs[theIndex];
+ toolTip.append(QStringLiteral("\n"));
+ toolTip.append(GetFileTimeReadable(m_RecentDocs[theIndex]));
+ recent->setToolTip(toolTip);
+ } else {
+ recent->setEnabled(false);
+ recent->setText(QString());
+ }
+ }
+}
+
+void CStartupDlg::AddRecentItem(const QString &inRecentItem)
+{
+ m_RecentDocs.push_back(inRecentItem);
+}
+
+CStartupDlg::EStartupChoice CStartupDlg::GetChoice()
+{
+ return m_Choice;
+}
+
+QString CStartupDlg::GetRecentDoc() const
+{
+ return m_RecentDocSelected;
+}
+
+void CStartupDlg::OnNewDocClicked()
+{
+ m_Choice = EStartupChoice_NewDoc;
+ QDialog::accept();
+}
+
+void CStartupDlg::OnOpenDocClicked()
+{
+ m_Choice = EStartupChoice_OpenDoc;
+ QDialog::accept();
+}
+
+void CStartupDlg::OnStnClickedStartupRecent()
+{
+ const int index = sender()->property("recentIndex").toInt();
+ OpenRecent(index);
+}
+
+void CStartupDlg::OpenRecent(size_t inIndex)
+{
+ if (inIndex < m_RecentDocs.size()) {
+ m_RecentDocSelected = m_RecentDocs[inIndex];
+ m_Choice = EStartupChoice_OpenRecent;
+ QDialog::accept();
+ }
+}
+
+void CStartupDlg::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event)
+ if (m_palette)
+ return;
+
+ m_palette = new QPalette;
+ QPixmap pic = QPixmap(":/startup/open_dialog.png");
+ m_palette->setBrush(QPalette::Window, pic);
+ setPalette(*m_palette);
+ resize(pic.size());
+ setFixedSize(size());
+}
diff --git a/src/Authoring/Qt3DStudio/UI/StartupDlg.h b/src/Authoring/Qt3DStudio/UI/StartupDlg.h
new file mode 100644
index 00000000..ceb38e7b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StartupDlg.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2006 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_STARTUP_DLG
+#define INCLUDED_STARTUP_DLG 1
+
+#pragma once
+
+//==============================================================================
+// Includes
+//==============================================================================
+
+#include "Qt3DSString.h"
+#include "Qt3DSFile.h"
+
+#include <QtWidgets/qdialog.h>
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+ class StartupDlg;
+}
+QT_END_NAMESPACE
+
+//==============================================================================
+/**
+ * CGLVersionDlg: Dialog class for showing what user can do upon startup
+ */
+//==============================================================================
+class CStartupDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ enum EStartupChoice {
+ EStartupChoice_Invalid = -1,
+ EStartupChoice_NewDoc,
+ EStartupChoice_OpenDoc,
+ EStartupChoice_OpenRecent,
+ EStartupChoice_Exit
+ };
+
+public:
+ CStartupDlg(QWidget *pParent = nullptr); // standard constructor
+ virtual ~CStartupDlg();
+
+protected:
+ const static int RECENT_COUNT = 10;
+
+ void paintEvent(QPaintEvent *event) override;
+ void showEvent(QShowEvent *) override;
+ void reject() override;
+
+protected Q_SLOTS:
+ void OnNewDocClicked();
+ void OnOpenDocClicked();
+ void OnStnClickedStartupRecent();
+ void OpenRecent(size_t inIndex);
+
+private:
+ // Dialog background
+ QPalette *m_palette;
+
+ // Product version string
+ QString m_ProductVersionStr;
+
+ // Choice
+ EStartupChoice m_Choice = EStartupChoice_Invalid;
+
+ // Recent Docs
+ std::vector<QString> m_RecentDocs;
+ QString m_RecentDocSelected = {};
+
+public:
+ void OnInitDialog();
+ void AddRecentItem(const QString &inRecentItem);
+ EStartupChoice GetChoice();
+ QString GetRecentDoc() const;
+
+private:
+ QScopedPointer<Ui::StartupDlg> m_ui;
+};
+
+#endif // INCLUDED_STARTUP_DLG
diff --git a/src/Authoring/Qt3DStudio/UI/StartupDlg.ui b/src/Authoring/Qt3DStudio/UI/StartupDlg.ui
new file mode 100644
index 00000000..a4a7a4d4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StartupDlg.ui
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StartupDlg</class>
+ <widget class="QDialog" name="StartupDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>442</width>
+ <height>650</height>
+ </rect>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </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>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>-1</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>90</number>
+ </property>
+ <property name="rightMargin">
+ <number>10</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="versionStr">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Qt 3D Studio version string</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <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>9</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="newDocument">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Create Project...</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="openDocument">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Open Project...</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <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>12</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="recentDocLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>50</weight>
+ <bold>false</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>RECENT FILES</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <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>8</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent0">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent0.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent1.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent2.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent3.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>3</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent4.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>4</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent5.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent6.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>6</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent7.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>7</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent8.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>8</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ClickableLabel" name="recent9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Recent9.uip</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="docIndex" stdset="0">
+ <number>9</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ClickableLabel</class>
+ <extends>QLabel</extends>
+ <header>ClickableLabel.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.cpp b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.cpp
new file mode 100644
index 00000000..288cee86
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.cpp
@@ -0,0 +1,512 @@
+/****************************************************************************
+**
+** 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 "ui_StudioAppPrefsPage.h"
+
+#include "Doc.h"
+#include "StudioAppPrefsPage.h"
+#include "StudioConst.h"
+#include "StudioProjectSettings.h"
+#include "StudioPreferences.h"
+#include "StudioApp.h"
+#include "StudioPreferences.h"
+#include "CommonConstants.h"
+#include "Views.h"
+#include "Qt3DSDMStudioSystem.h"
+#include "ClientDataModelBridge.h"
+#include "Core.h"
+#include "IStudioRenderer.h"
+
+#include <QtWidgets/qmessagebox.h>
+#include <QtWidgets/qlistview.h>
+#include <QtGui/qstandarditemmodel.h>
+#include <QtCore/qdiriterator.h>
+
+//==============================================================================
+/**
+ * Constructor: Initializes the object.
+ */
+//==============================================================================
+CStudioAppPrefsPage::CStudioAppPrefsPage(QWidget *parent)
+ : CStudioPreferencesPropPage(parent)
+ , m_timebarShowTime(false)
+ , m_interpolationIsSmooth(false)
+ , m_restartNeeded(false)
+ , m_autosaveChanged(false)
+ , m_ui(new Ui::StudioAppPrefsPage)
+{
+ m_font = QFont(CStudioPreferences::GetFontFaceName());
+ m_font.setPixelSize(CStudioPreferences::fontSize());
+
+ // Create a bold font for the group box text
+ m_boldFont = m_font;
+ m_boldFont.setBold(true);
+
+ onInitDialog();
+}
+
+//==============================================================================
+/**
+ * Destructor: Releases the object.
+ */
+//==============================================================================
+CStudioAppPrefsPage::~CStudioAppPrefsPage()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CStudioAppPrefsPage message handlers
+
+void CStudioAppPrefsPage::onInitDialog()
+{
+ m_ui->setupUi(this);
+
+ // Add tool tips for controls
+ m_ui->m_DefaultInterpolation->setToolTip(tr("Set default keyframe interpolation type"));
+ m_ui->m_checkTimelineAbsoluteSnapping->setToolTip(tr("Enable timeline snapping grid"));
+ m_ui->m_checkLegacyViewer->setToolTip(tr("Enable preview with Qt3D Runtime Viewer"));
+ m_ui->m_SnapRangeCombo->setToolTip(tr("Set resolution of timeline snapping grid"));
+ m_ui->m_buttonRestoreDefaults->setToolTip(tr("Click to restore default Studio settings"));
+
+ // Set fonts for child windows.
+ for (auto w : findChildren<QWidget *>())
+ w->setFont(m_font);
+
+ // Make the group text bold
+ for (auto w : findChildren<QGroupBox *>())
+ w->setFont(m_boldFont);
+
+ // Hidden until we have some other Preview configurations than just Viewer
+ m_ui->groupBoxPreview->setVisible(false);
+
+ // Load the settings for the controls
+ loadSettings();
+
+ auto activated = static_cast<void(QComboBox::*)(int)>(&QComboBox::activated);
+ connect(m_ui->m_buttonRestoreDefaults, &QPushButton::clicked,
+ this, &CStudioAppPrefsPage::onButtonRestoreDefaults);
+ connect(m_ui->m_buttonResetLayout, &QPushButton::clicked, [=](){
+ onApply(); // Save changed preferences before resetting, as it causes Studio to shut down
+ CStudioPreferencesPropPage::endDialog(PREFS_RESET_LAYOUT);
+ });
+ connect(m_ui->m_DefaultInterpolation, activated, this, [=](){ setModified(true); });
+ connect(m_ui->m_SnapRangeCombo, activated, this, [=](){ setModified(true); });
+ connect(m_ui->m_checkTimelineAbsoluteSnapping, &QCheckBox::clicked,
+ this, [=](){ setModified(true); enableOptions(); });
+ connect(m_ui->m_checkLegacyViewer, &QCheckBox::clicked,
+ this, [=](){ setModified(true); m_restartNeeded = true; });
+ connect(m_ui->m_EditViewStartupView, activated, this, [=](){ setModified(true); });
+ connect(m_ui->selectorWidth,
+ static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
+ this, [=](){ setModified(true); m_restartNeeded = true; });
+ connect(m_ui->selectorLength,
+ static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
+ this, [=](){ setModified(true); m_restartNeeded = true; });
+ connect(m_ui->autosaveEnabled, &QCheckBox::clicked, this,
+ [=](){ setModified(true); m_autosaveChanged = true; });
+ connect(m_ui->autosaveInterval, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
+ this, [=](){ setModified(true); m_autosaveChanged = true; });
+ connect(m_ui->clearAutosaveFiles, &QPushButton::clicked,
+ this, &CStudioAppPrefsPage::onClearAutosaveFiles);
+#if 0 // Removed until we have some other Preview configurations than just Viewer
+ connect(m_ui->m_PreviewSelector, activated,
+ this, &CStudioAppPrefsPage::onChangePreviewConfiguration);
+#endif
+}
+
+//==============================================================================
+/**
+ * LoadSettings: Load the settings from the CDoc and set the control values.
+ *
+ * @param None
+ */
+//==============================================================================
+void CStudioAppPrefsPage::loadSettings()
+{
+ // Get the Interpolation Preference
+ m_ui->m_DefaultInterpolation->addItem(tr("Smooth"));
+ m_ui->m_DefaultInterpolation->addItem(tr("Linear"));
+
+ long theInterpolationPref = 0;
+ if (CStudioPreferences::GetInterpolation())
+ theInterpolationPref = 0;
+ else
+ theInterpolationPref = 1;
+ m_ui->m_DefaultInterpolation->setCurrentIndex(theInterpolationPref);
+
+ // Timeline snapping grid
+ m_ui->m_checkTimelineAbsoluteSnapping->setChecked(
+ CStudioPreferences::IsTimelineSnappingGridActive());
+
+ // Legacy viewer
+ m_ui->m_checkLegacyViewer->setChecked(CStudioPreferences::IsLegacyViewerActive());
+
+ // Tool handles
+ m_ui->selectorWidth->setValue(CStudioPreferences::getSelectorLineWidth());
+ m_ui->selectorLength->setValue(CStudioPreferences::getSelectorLineLength());
+
+ // The scale mode
+ m_ui->m_SnapRangeCombo->addItem(tr("1 Second"));
+ m_ui->m_SnapRangeCombo->addItem(tr("0.5 Seconds"));
+ m_ui->m_SnapRangeCombo->addItem(tr("0.1 Seconds"));
+ long theResolution = (long)CStudioPreferences::GetTimelineSnappingGridResolution();
+ m_ui->m_SnapRangeCombo->setCurrentIndex(theResolution);
+
+ // Autosave options
+ m_ui->autosaveEnabled->setChecked(CStudioPreferences::GetAutoSavePreference());
+ m_ui->autosaveInterval->setValue(CStudioPreferences::GetAutoSaveDelay());
+
+ initEditStartViewCombo();
+
+ enableOptions();
+
+#if 0 // Removed until we have some other Preview configurations than just Viewer
+ loadPreviewSelections();
+#endif
+}
+
+/**
+ * SaveSettings: Save the settings from the controls to the CDoc
+ *
+ * @param None
+ */
+//==============================================================================
+void CStudioAppPrefsPage::saveSettings()
+{
+ // Default interpolation
+ g_StudioApp.GetCore()->GetDoc()->SetDefaultKeyframeInterpolation(
+ m_ui->m_DefaultInterpolation->currentIndex() == 0);
+
+ // Timeline snapping grid
+ CStudioPreferences::SetTimelineSnappingGridActive(
+ m_ui->m_checkTimelineAbsoluteSnapping->isChecked());
+ long theCurrentSelection = m_ui->m_SnapRangeCombo->currentIndex();
+ CStudioPreferences::SetTimelineSnappingGridResolution((ESnapGridResolution)theCurrentSelection);
+
+ // Legacy viewer
+ CStudioPreferences::SetLegacyViewerActive(m_ui->m_checkLegacyViewer->isChecked());
+
+ // Preferred Startup View
+ CStudioPreferences::SetPreferredStartupView(m_ui->m_EditViewStartupView->currentIndex());
+
+ // Tool handles
+ CStudioPreferences::setSelectorLineWidth(m_ui->selectorWidth->value());
+ CStudioPreferences::setSelectorLineLength(m_ui->selectorLength->value());
+
+ // Autosave options
+ CStudioPreferences::SetAutoSavePreference(m_ui->autosaveEnabled->isChecked());
+ CStudioPreferences::SetAutoSaveDelay(m_ui->autosaveInterval->value());
+ enableAutosave(m_ui->autosaveEnabled->isChecked());
+ setAutosaveInterval(m_ui->autosaveInterval->value());
+ m_autosaveChanged = false;
+
+#if 0 // Removed until we have some other Preview configurations than just Viewer
+ savePreviewSettings();
+#endif
+
+ checkRestartCondition();
+}
+
+//==============================================================================
+/**
+ * OnApply: Handler for the Apply button
+ *
+ * @param None
+ */
+//==============================================================================
+bool CStudioAppPrefsPage::onApply()
+{
+ // Apply was clicked - save settings and disable the Apply button
+ saveSettings();
+
+ setModified(false);
+
+ // Request that the renderer refreshes as settings may have changed
+ g_StudioApp.getRenderer().RequestRender();
+
+ return CStudioPreferencesPropPage::onApply();
+}
+
+//==============================================================================
+/**
+ * OnButtonRestoreDefaults: Restore the defaults and exit the preferences.
+ *
+ * @param None
+ */
+//==============================================================================
+void CStudioAppPrefsPage::onButtonRestoreDefaults()
+{
+ int theChoice = 0;
+
+ // Ask the user if she really wants to do this
+ theChoice = QMessageBox::question(this,
+ tr("Restore Defaults"),
+ tr("Are you sure that you want to restore all program "
+ "\ndefaults? Your current settings will be lost."));
+
+ // If the "yes" button was selected
+ if (theChoice == QMessageBox::Yes) {
+ // Restore default preferences by passing PREFS_RESET_DEFAULTS back
+ // to the CStudioDocPreferences (that called this preferences sheet)
+ CStudioPreferencesPropPage::endDialog(PREFS_RESET_DEFAULTS);
+ }
+}
+
+//==============================================================================
+/**
+ * EnableOptions: Enable/disable options.
+ *
+ * @param None
+ */
+//==============================================================================
+void CStudioAppPrefsPage::enableOptions()
+{
+ m_ui->m_SnapRangeCombo->setEnabled(m_ui->m_checkTimelineAbsoluteSnapping->isChecked());
+}
+
+//==============================================================================
+/**
+ * Initialise the combo box that displays the preferred startup view.
+ * Set the initial selection to that saved to the preferences
+ */
+//==============================================================================
+void CStudioAppPrefsPage::initEditStartViewCombo()
+{
+ Q3DStudio::IStudioRenderer &theRenderer = g_StudioApp.getRenderer();
+ QStringList theCameraNames;
+ theRenderer.GetEditCameraList(theCameraNames);
+ m_ui->m_EditViewStartupView->addItems(theCameraNames);
+ m_ui->m_EditViewStartupView->addItem(tr("Scene Camera View"));
+
+ m_ui->m_EditViewStartupView->insertSeparator(m_ui->m_EditViewStartupView->count() - 1);
+
+ // adding a 1px spacing, else the separator will disappear sometimes (QComboBox bug)
+ qobject_cast<QListView *>(m_ui->m_EditViewStartupView->view())->setSpacing(1);
+
+ long thePreferredView = CStudioPreferences::GetPreferredStartupView();
+ long theNumItems = m_ui->m_EditViewStartupView->count();
+
+ if (thePreferredView >= 0 && thePreferredView < theNumItems - 2)
+ m_ui->m_EditViewStartupView->setCurrentIndex(thePreferredView);
+ else // default to scene camera view
+ m_ui->m_EditViewStartupView->setCurrentIndex(theNumItems - 1);
+}
+
+#if 0 // Removed until we have some other Preview configurations than just Viewer
+void CStudioAppPrefsPage::loadPreviewSelections()
+{
+ // Load the configurations from all the .build files
+ Q3DStudio::CBuildConfigurations &theConfig = g_StudioApp.GetCore()->GetBuildConfigurations();
+ Q3DStudio::CBuildConfigurations::TBuildConfigurations theConfigurations =
+ theConfig.GetConfigurations();
+ Q3DStudio::CBuildConfigurations::TBuildConfigurations::iterator theIter;
+ for (theIter = theConfigurations.begin(); theIter != theConfigurations.end(); ++theIter) {
+ const Q3DStudio::CString &theConfig = theIter->first;
+ m_ui->m_PreviewSelector->addItem(theConfig.toQString());
+ m_ui->m_PreviewSelector->setItemData(m_ui->m_PreviewSelector->count() - 1,
+ QVariant::fromValue(theIter->second));
+ }
+
+ int thePreviewSelected = m_ui->m_PreviewSelector->findText(
+ CStudioPreferences::GetPreviewConfig().toQString());
+ m_ui->m_PreviewSelector->setCurrentIndex(thePreviewSelected);
+ if (thePreviewSelected == -1) {
+ // select the first build configuration, or if no conriguration, the first application, i.e.
+ // AMPlayer
+ m_ui->m_PreviewSelector->setCurrentIndex(0);
+ long thePreviewCount = m_ui->m_PreviewSelector->count();
+ for (long theIndex = 0; theIndex < thePreviewCount; ++theIndex) {
+ if (m_ui->m_PreviewSelector->itemData(
+ theIndex).value<Q3DStudio::CBuildConfiguration *>() != nullptr) {
+ m_ui->m_PreviewSelector->setCurrentIndex(theIndex);
+ break;
+ }
+ }
+ }
+
+ LoadBuildProperties();
+}
+
+//==============================================================================
+/**
+ * When the build configuration is changed, all the properties have to be updated.
+ */
+//==============================================================================
+void CStudioAppPrefsPage::onChangePreviewConfiguration()
+{
+ LoadBuildProperties();
+}
+#endif
+
+void CStudioAppPrefsPage::enableAutosave(bool enabled)
+{
+ if (m_autosaveChanged)
+ g_StudioApp.SetAutosaveEnabled(enabled);
+}
+
+void CStudioAppPrefsPage::setAutosaveInterval(int interval)
+{
+ if (m_autosaveChanged)
+ g_StudioApp.SetAutosaveInterval(interval);
+}
+
+void CStudioAppPrefsPage::onClearAutosaveFiles()
+{
+ // Find all *_autosave.uip files and delete them
+ QDirIterator files(g_StudioApp.GetCore()->GetDoc()->GetDocumentDirectory());
+ while (files.hasNext()) {
+ if (files.next().endsWith(QLatin1String("_autosave.uip")))
+ QFile::remove(files.filePath());
+ }
+}
+
+void CStudioAppPrefsPage::checkRestartCondition()
+{
+ if (m_restartNeeded) {
+ // If special settings have changed, a restart of Studio is needed
+ int retval = QMessageBox::question(this, tr("Restart Needed"),
+ tr("Some settings were changed that require a\n"
+ "restart of the Qt 3D Studio to take effect.\n"
+ "Restart now?"));
+
+ if (retval == QMessageBox::Yes) {
+ CStudioPreferences::savePreferences();
+ CStudioPreferencesPropPage::endDialog(PREFS_SETTINGS_RESTART);
+ }
+
+ // Just show the dialog once (unless the values are changed again)
+ m_restartNeeded = false;
+ }
+}
+
+//==============================================================================
+/**
+ * Load the build properties for the current preview application selected
+ */
+//==============================================================================
+#if 0 // Removed until we have some other Preview configurations than just Viewer
+void CStudioAppPrefsPage::loadBuildProperties()
+{
+ // Remove those dynamic controls
+ RemovePreviewPropertyControls();
+
+ if (m_ui->m_PreviewSelector->count() > 0) {
+ Q3DStudio::CBuildConfiguration *theConfig =
+ m_ui->m_PreviewSelector->itemData(
+ m_ui->m_PreviewSelector->currentIndex())
+ .value<Q3DStudio::CBuildConfiguration *>();
+ if (theConfig) {
+ // Only configuration read from .build files will have the ItemDataPtr set.
+
+ Q3DStudio::CBuildConfiguration::TConfigProperties &theProperties =
+ theConfig->GetBuildProperties();
+
+ auto layout = qobject_cast<QFormLayout *>(m_ui->groupBoxPreview->layout());
+ auto activated = static_cast<void(QComboBox::*)(int)>(&QComboBox::activated);
+
+ if (theProperties.empty() == false) {
+ Q3DStudio::CBuildConfiguration::TConfigProperties::iterator theIter;
+
+ for (theIter = theProperties.begin(); theIter != theProperties.end(); ++theIter) {
+ Q3DStudio::CBuildConfiguration::TConfigPropertyValues &theValues =
+ theIter->GetAcceptableValues();
+ // Only create the combo if there is more than 1 choices
+ if (theValues.size() > 1) {
+ Q3DStudio::CBuildConfiguration::TConfigPropertyValues::iterator
+ theValueIter;
+ long theMaxLength = 0;
+ for (theValueIter = theValues.begin(); theValueIter != theValues.end();
+ ++theValueIter) {
+ long theLabelLength = theValueIter->GetLabel().Length();
+ if (theLabelLength > theMaxLength)
+ theMaxLength = theLabelLength;
+ }
+
+ QLabel *theStaticText = new QLabel(theIter->GetLabel().toQString());
+ theStaticText->setFont(m_Font);
+ QComboBox *thePropertyDropdown = new QComboBox();
+ connect(thePropertyDropdown, activated, [&]() {SetModified(true);});
+ thePropertyDropdown->setFont(m_Font);
+ layout->addRow(theStaticText, thePropertyDropdown);
+
+ m_BuildProperties.push_back(std::make_pair(
+ &*theIter, std::make_pair(theStaticText, thePropertyDropdown)));
+
+ Q3DStudio::CString thePropertyValue =
+ CStudioPreferences::GetPreviewProperty(theIter->GetName());
+ for (theValueIter = theValues.begin(); theValueIter != theValues.end();
+ ++theValueIter) {
+ thePropertyDropdown->addItem(theValueIter->GetLabel().toQString());
+ thePropertyDropdown->setItemData(thePropertyDropdown->count() - 1, QVariant::fromValue(&*theValueIter));
+ if (theValueIter->GetName() == thePropertyValue)
+ thePropertyDropdown->setCurrentIndex(thePropertyDropdown->count() - 1);
+ }
+
+ // Select the first entry
+ if (thePropertyDropdown->currentIndex() == -1)
+ thePropertyDropdown->setCurrentIndex(0);
+ }
+ }
+ }
+ }
+ }
+}
+
+void CStudioAppPrefsPage::SavePreviewSettings()
+{
+ QString thePreviewApp = m_ui->m_PreviewSelector->currentText();
+ CStudioPreferences::SetPreviewConfig(Q3DStudio::CString::fromQString(thePreviewApp));
+
+ std::list<TBuildNameControlPair>::iterator theIter;
+ for (theIter = m_BuildProperties.begin(); theIter != m_BuildProperties.end(); ++theIter) {
+ QComboBox *theCombo = theIter->second.second;
+ Q3DStudio::CString theName = theIter->first->GetName();
+ Q3DStudio::CBuildConfiguration::SConfigPropertyValue *thePropertyValue =
+ theCombo->itemData(theCombo->currentIndex())
+ .value<Q3DStudio::CBuildConfiguration::SConfigPropertyValue *>();
+ CStudioPreferences::SetPreviewProperty(theName, thePropertyValue->GetName());
+ }
+}
+
+//==============================================================================
+/**
+ * Remove all the dynamically added controls that was read in from the build file
+ */
+//==============================================================================
+void CStudioAppPrefsPage::RemovePreviewPropertyControls()
+{
+ // Remove the created control
+ std::list<TBuildNameControlPair>::iterator theIter;
+ for (theIter = m_BuildProperties.begin(); theIter != m_BuildProperties.end(); ++theIter) {
+ delete theIter->second.first;
+ delete theIter->second.second;
+ }
+ m_BuildProperties.clear();
+}
+#endif
diff --git a/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.h b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.h
new file mode 100644
index 00000000..abea304b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** 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 STUDIOAPPPREFSPAGE_H_
+#define STUDIOAPPPREFSPAGE_H_
+
+#include "BuildConfigParser.h"
+
+#include "StudioPreferencesPropSheet.h"
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+ class StudioAppPrefsPage;
+}
+QT_END_NAMESPACE
+
+class CStudioAppPrefsPage : public CStudioPreferencesPropPage
+{
+ Q_OBJECT
+protected:
+ typedef std::pair<QLabel *, QComboBox *> TBuildLabelDropdownPair;
+ typedef std::pair<Q3DStudio::CBuildConfiguration::SConfigProperty *, TBuildLabelDropdownPair>
+ TBuildNameControlPair;
+
+public:
+ explicit CStudioAppPrefsPage(QWidget *parent = nullptr);
+ ~CStudioAppPrefsPage();
+
+public:
+ bool onApply() override;
+
+protected:
+ bool m_timebarShowTime; // TRUE if timebars are to display their time value
+ bool m_interpolationIsSmooth; // TRUE if default interpolation is smooth
+ QFont m_font; // Font for text
+ QFont m_boldFont; // Bold font for drawing the group boxes
+ bool m_restartNeeded;
+ bool m_autosaveChanged;
+
+ void enableOptions();
+ void loadSettings();
+ void saveSettings();
+
+ virtual void onInitDialog();
+ void onButtonRestoreDefaults();
+#if 0 // Removed until we have some other Preview configurations that just Viewer
+ void onChangePreviewConfiguration();
+#endif
+
+ void enableAutosave(bool enabled);
+ void setAutosaveInterval(int interval);
+ void onClearAutosaveFiles();
+ void initEditStartViewCombo();
+ void checkRestartCondition();
+
+protected:
+ std::list<TBuildNameControlPair> m_buildProperties; // List of build properties, either
+ // ComboBox or Static
+
+#if 0 // Removed until we have some other Preview configurations that just Viewer
+ void loadPreviewSelections();
+ void loadBuildProperties();
+ void savePreviewSettings();
+ void removePreviewPropertyControls();
+#endif
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::StudioAppPrefsPage)> m_ui;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.ui b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.ui
new file mode 100644
index 00000000..7ebdc7e2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioAppPrefsPage.ui
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StudioAppPrefsPage</class>
+ <widget class="QWidget" name="StudioAppPrefsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>411</width>
+ <height>525</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>General</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Default Interpolation</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_DefaultInterpolation</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="m_DefaultInterpolation"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="m_SnapRangeCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="m_checkTimelineAbsoluteSnapping">
+ <property name="text">
+ <string>Timeline Snapping Grid</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="m_checkLegacyViewer">
+ <property name="text">
+ <string>Qt3D Viewer</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="m_groupBoxEditingView">
+ <property name="title">
+ <string>Editing View</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="2" colspan="2">
+ <widget class="QComboBox" name="m_EditViewStartupView"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Preferred Startup View</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_EditViewStartupView</cstring>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBoxToolHandles">
+ <property name="title">
+ <string>Manipulator Tool Handles</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="selectorWidth">
+ <property name="toolTip">
+ <string>The width of the 3D view
+manipulator tool handles</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="minimum">
+ <double>1.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.500000000000000</double>
+ </property>
+ <property name="value">
+ <double>3.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Width</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Length</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QDoubleSpinBox" name="selectorLength">
+ <property name="toolTip">
+ <string>The length of the 3D view
+manipulator tool handles</string>
+ </property>
+ <property name="decimals">
+ <number>0</number>
+ </property>
+ <property name="minimum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>50.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBoxAutosave">
+ <property name="title">
+ <string>Autosave</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="autosaveEnabled">
+ <property name="text">
+ <string>Autosave Enabled</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="intervalLabel">
+ <property name="text">
+ <string>Interval (seconds)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QSpinBox" name="autosaveInterval">
+ <property name="toolTip">
+ <string>The length of the 3D view
+manipulator tool handles.</string>
+ </property>
+ <property name="minimum">
+ <number>30</number>
+ </property>
+ <property name="maximum">
+ <number>3600</number>
+ </property>
+ <property name="singleStep">
+ <number>30</number>
+ </property>
+ <property name="value">
+ <number>600</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="clearAutosaveFiles">
+ <property name="toolTip">
+ <string>Clear all autosave files from project folder</string>
+ </property>
+ <property name="text">
+ <string>Clear Autosave Files</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="m_buttonRestoreDefaults">
+ <property name="maximumSize">
+ <size>
+ <width>293</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Restore Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="m_buttonResetLayout">
+ <property name="toolTip">
+ <string>Click to restore default Studio
+palette positions and visibilities</string>
+ </property>
+ <property name="text">
+ <string>Reset Layout</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBoxPreview">
+ <property name="title">
+ <string>Preview</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Configuration</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_PreviewSelector</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="m_PreviewSelector"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.cpp b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.cpp
new file mode 100644
index 00000000..4510a9c2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.cpp
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** 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 "ui_StudioPreferencesPropSheet.h"
+
+#include "StudioPreferences.h"
+#include "StudioPreferencesPropSheet.h"
+#include "StudioProjectSettingsPage.h"
+
+#include <QtWidgets/qpushbutton.h>
+#include <QtCore/qtimer.h>
+
+CStudioPreferencesPropPage::CStudioPreferencesPropPage(QWidget *parent)
+ : QWidget(parent)
+{
+}
+
+void CStudioPreferencesPropPage::setModified(bool modified)
+{
+ setProperty("modified", modified);
+
+ auto s = sheet();
+ if (s) {
+ auto buttons = s->findChild<QDialogButtonBox *>();
+ bool anyModified = false;
+ for (auto page : s->findChildren<CStudioPreferencesPropPage *>())
+ anyModified |= page->property("modified").toBool();
+ buttons->button(QDialogButtonBox::Apply)->setEnabled(anyModified);
+ }
+}
+
+CStudioPreferencesPropSheet* CStudioPreferencesPropPage::sheet()
+{
+ QWidget *parent = parentWidget();
+ while (parent != nullptr) {
+ if (auto sheet = qobject_cast<CStudioPreferencesPropSheet *>(parent))
+ return sheet;
+ parent = parent->parentWidget();
+ }
+ return nullptr;
+}
+
+
+void CStudioPreferencesPropPage::endDialog(int returnCode)
+{
+ auto s = sheet();
+ if (s)
+ s->done(returnCode);
+}
+
+CStudioPreferencesPropSheet::CStudioPreferencesPropSheet(const QString &pszCaption,
+ QWidget *pParentWnd,
+ int iSelectPage)
+ : QDialog(pParentWnd)
+ , m_ui(new Ui::StudioPreferencesPropSheet)
+{
+ setWindowTitle(pszCaption);
+ onInitDialog();
+ m_ui->m_TabCtrl->setCurrentIndex(iSelectPage);
+
+ // Fix the size after property sheets have loaded and done their necessary resizes
+ QTimer::singleShot(500, this, [this]() {
+ window()->setFixedSize(size());
+ });
+}
+
+CStudioPreferencesPropSheet::~CStudioPreferencesPropSheet()
+{
+}
+
+void CStudioPreferencesPropSheet::onInitDialog()
+{
+ m_ui->setupUi(this);
+ m_ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
+
+ m_Font = QFont(CStudioPreferences::GetFontFaceName(), 8);
+ setFont(m_Font);
+
+ connect(m_ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked,
+ this, &CStudioPreferencesPropSheet::apply);
+}
+
+bool CStudioPreferencesPropSheet::apply()
+{
+ for (auto page : findChildren<CStudioPreferencesPropPage *>()) {
+ if (!page->onApply())
+ return false;
+ }
+ return true;
+}
+
+void CStudioPreferencesPropSheet::done(int code)
+{
+ m_returnCode = code;
+ QDialog::done(code);
+}
+
+void CStudioPreferencesPropSheet::accept()
+{
+ if (apply()) {
+ if (!m_returnCode)
+ QDialog::accept();
+ else
+ QDialog::done(m_returnCode);
+ }
+}
+
+void CStudioPreferencesPropSheet::reject()
+{
+ QDialog::reject();
+}
diff --git a/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.h b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.h
new file mode 100644
index 00000000..0e969a4d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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 STUDIOPREFERENCESPROPSHEET_H
+#define STUDIOPREFERENCESPROPSHEET_H
+
+#pragma once
+
+#include <QtWidgets/qdialog.h>
+
+class CStudioProjectSettingsPage;
+class CStudioPreferencesPropSheet;
+class CStudioApp;
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+ class StudioPreferencesPropSheet;
+}
+QT_END_NAMESPACE
+
+class CStudioPreferencesPropPage : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit CStudioPreferencesPropPage(QWidget *parent = nullptr);
+
+ virtual bool onApply() { setModified(false); return true; }
+
+protected:
+ CStudioPreferencesPropSheet* sheet();
+
+ void setModified(bool modified);
+ void endDialog(int returnCode);
+};
+
+class CStudioPreferencesPropSheet : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit CStudioPreferencesPropSheet(const QString &pszCaption, QWidget *pParentWnd = nullptr,
+ int iSelectPage = 0);
+
+protected:
+ QFont m_Font; // Font for text
+
+public:
+ virtual ~CStudioPreferencesPropSheet();
+ void done(int code) override;
+
+protected:
+ virtual void onInitDialog();
+
+ bool apply();
+ void accept() override;
+ void reject() override;
+
+private:
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::StudioPreferencesPropSheet)> m_ui;
+ int m_returnCode = 0;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.ui b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.ui
new file mode 100644
index 00000000..4fd305d0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioPreferencesPropSheet.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StudioPreferencesPropSheet</class>
+ <widget class="QDialog" name="StudioPreferencesPropSheet">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>484</width>
+ <height>382</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Studio Preferences</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="m_TabCtrl">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="CStudioAppPrefsPage" name="m_AppPrefsPage">
+ <attribute name="icon">
+ <iconset resource="../../images.qrc">
+ <normaloff>:/images/prefstab-00.png</normaloff>:/images/prefstab-00.png</iconset>
+ </attribute>
+ <attribute name="title">
+ <string>Studio</string>
+ </attribute>
+ </widget>
+ <widget class="CStudioProjectSettingsPage" name="m_ProjectSettingsPage">
+ <attribute name="icon">
+ <iconset resource="../../images.qrc">
+ <normaloff>:/images/prefstab-01.png</normaloff>:/images/prefstab-01.png</iconset>
+ </attribute>
+ <attribute name="title">
+ <string>Presentation Settings</string>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>CStudioProjectSettingsPage</class>
+ <extends>QWidget</extends>
+ <header>StudioProjectSettingsPage.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>CStudioAppPrefsPage</class>
+ <extends>QWidget</extends>
+ <header>StudioAppPrefsPage.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="../../images.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>StudioPreferencesPropSheet</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>StudioPreferencesPropSheet</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/UI/StudioProjectSettingsPage.cpp b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.cpp
new file mode 100644
index 00000000..36e344c3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.cpp
@@ -0,0 +1,252 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "ui_StudioProjectSettingsPage.h"
+#include "StudioProjectSettingsPage.h"
+#include "StudioProjectSettings.h"
+#include "StudioApp.h"
+#include "Doc.h"
+#include "Views.h"
+#include "MainFrm.h"
+#include "CommonConstants.h"
+#include "StudioPreferences.h"
+#include "Core.h"
+
+CStudioProjectSettingsPage::CStudioProjectSettingsPage(QWidget *parent)
+ : CStudioPreferencesPropPage(parent)
+ , m_aspectRatio(0.0)
+ , m_ui(new Ui::StudioProjectSettingsPage)
+{
+ m_font = QFont(CStudioPreferences::GetFontFaceName());
+ m_font.setPixelSize(CStudioPreferences::fontSize());
+
+ // Create a bold font for the group box text
+ m_boldFont = m_font;
+ m_boldFont.setBold(true);
+
+ onInitDialog();
+}
+
+CStudioProjectSettingsPage::~CStudioProjectSettingsPage()
+{
+}
+
+/**
+ * OnInitDialog: Handle the WM_INITDIALOG message.
+ *
+ * Initialize the dialog by setting the various control values.
+ *
+ * @return Returns TRUE always.
+ */
+void CStudioProjectSettingsPage::onInitDialog()
+{
+ m_ui->setupUi(this);
+
+ m_ui->m_PresentationId->setToolTip(tr("Presentation Id"));
+ m_ui->m_ClientSizeWidth->setToolTip(tr("Presentation Width"));
+ m_ui->m_ClientSizeHeight->setToolTip(tr("Presentation Height"));
+ m_ui->m_checkConstrainProportions->setToolTip(tr("Check to maintain the aspect ratio when "
+ "changing presentation width or height"));
+ m_ui->m_Author->setToolTip(tr("Enter an author name for this presentation"));
+ m_ui->m_Company->setToolTip(tr("Enter a company name for this presentation"));
+
+ // Set fonts for child windows.
+ for (auto w : findChildren<QWidget *>())
+ w->setFont(m_font);
+
+ // Make the group text bold
+ for (auto w : findChildren<QGroupBox *>())
+ w->setFont(m_boldFont);
+
+ // Set the ranges of the client width and height
+ m_ui->m_ClientSizeWidth->setRange(1, 16384);
+ m_ui->m_ClientSizeHeight->setRange(1, 16384);
+
+ // Load the settings for the controls
+ this->loadSettings();
+
+ auto valueChanged = static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged);
+ connect(m_ui->m_PresentationId, &QLineEdit::textEdited, [=](){ this->setModified(true); });
+ connect(m_ui->m_ClientSizeWidth, valueChanged,
+ this, &CStudioProjectSettingsPage::onChangeEditPresWidth);
+ connect(m_ui->m_ClientSizeHeight, valueChanged,
+ this, &CStudioProjectSettingsPage::onChangeEditPresHeight);
+ connect(m_ui->m_checkConstrainProportions, &QCheckBox::clicked,
+ this, &CStudioProjectSettingsPage::onCheckMaintainRatio);
+ connect(m_ui->m_checkUseKtx, &QCheckBox::clicked, [=](){ this->setModified(true); });
+ connect(m_ui->m_Author, &QLineEdit::textEdited, [=](){ this->setModified(true); });
+ connect(m_ui->m_Company, &QLineEdit::textEdited, [=](){ this->setModified(true); });
+}
+
+// LoadSettings: Load the settings from the project settings and set the control values.
+void CStudioProjectSettingsPage::loadSettings()
+{
+ // Presentation Id
+ m_ui->m_PresentationId->setText(g_StudioApp.GetCore()->GetDoc()->getPresentationId());
+
+ // Get the Client size
+ CStudioProjectSettings *theProjectSettings = g_StudioApp.GetCore()->GetStudioProjectSettings();
+ QSize theClientSize = theProjectSettings->getPresentationSize();
+
+ // Set client width & height
+ m_ui->m_ClientSizeWidth->setValue(theClientSize.width());
+ m_ui->m_ClientSizeHeight->setValue(theClientSize.height());
+
+ // Save the aspect ratio
+ m_aspectRatio = double(theClientSize.width()) / double(theClientSize.height());
+
+ // Maintain Aspect Ratio checkbox
+ m_ui->m_checkConstrainProportions->setChecked(theProjectSettings->getMaintainAspect());
+
+ // Portrait mode, i.e. rotate presentation
+ m_ui->m_checkPortraitFormat->setChecked(theProjectSettings->getRotatePresentation());
+
+ // Prefer compressed textures
+ m_ui->m_checkUseKtx->setChecked(theProjectSettings->getPreferCompressedTextures());
+
+ // Author
+ m_ui->m_Author->setText(theProjectSettings->getAuthor());
+
+ // Company
+ m_ui->m_Company->setText(theProjectSettings->getCompany());
+}
+
+// SaveSettings: Save the settings from the controls to the project settings.
+void CStudioProjectSettingsPage::saveSettings()
+{
+ QSize theClientSize;
+ CStudioProjectSettings *theProjectSettings = g_StudioApp.GetCore()->GetStudioProjectSettings();
+
+ // Presentation Id
+ g_StudioApp.GetCore()->getProjectFile().writePresentationId(m_ui->m_PresentationId->text());
+
+ // Presentation width & height
+ theClientSize.setWidth(m_ui->m_ClientSizeWidth->value());
+ theClientSize.setHeight(m_ui->m_ClientSizeHeight->value());
+ theProjectSettings->setPresentationSize(theClientSize);
+
+ // Author
+ QString theAuthor = m_ui->m_Author->text();
+ theProjectSettings->setAuthor(theAuthor);
+
+ // Company
+ QString theCompany = m_ui->m_Company->text();
+ theProjectSettings->setCompany(theCompany);
+
+ g_StudioApp.GetViews()->recheckMainframeSizingMode();
+
+ // Maintain Aspect Ratio checkbox
+ theProjectSettings->setMaintainAspect(m_ui->m_checkConstrainProportions->isChecked());
+
+ // Portrait mode, i.e. rotate presentation
+ theProjectSettings->setRotatePresentation(m_ui->m_checkPortraitFormat->isChecked());
+
+ // Prefer compressed textures
+ theProjectSettings->setPreferCompressedTextures(m_ui->m_checkUseKtx->isChecked());
+}
+
+// OnApply: Handler for the Apply button
+bool CStudioProjectSettingsPage::onApply()
+{
+ // make sure the presentation Id is unique and not empty
+ if (m_ui->m_PresentationId->text().isEmpty()) {
+ g_StudioApp.showPresentationIdEmptyWarning();
+ return false;
+ }
+ if (!g_StudioApp.GetCore()->getProjectFile()
+ .isUniquePresentationId(m_ui->m_PresentationId->text(),
+ g_StudioApp.GetCore()->GetDoc()->getRelativePath())) {
+ g_StudioApp.showPresentationIdUniqueWarning();
+ return false;
+ }
+
+ // Apply was clicked - save settings and disable the Apply button
+ this->saveSettings();
+
+ this->setModified(false);
+
+ return CStudioPreferencesPropPage::onApply();
+}
+
+// OnChangeEditPresWidth: EN_CHANGE handler for the IDC_EDIT_PRESWIDTH field
+void CStudioProjectSettingsPage::onChangeEditPresWidth()
+{
+ this->setModified(true);
+
+ // Should the aspect ratio be maintained?
+ if (m_ui->m_checkConstrainProportions->isChecked()) {
+ long thePresWidth;
+ long thePresHeight;
+
+ thePresWidth = m_ui->m_ClientSizeWidth->value();
+
+ // Change the height
+ thePresHeight = long(thePresWidth / m_aspectRatio);
+
+ QSignalBlocker sb(m_ui->m_ClientSizeHeight);
+ m_ui->m_ClientSizeHeight->setValue(thePresHeight);
+ }
+}
+
+// OnChangeEditPresHeight: EN_CHANGE handler for the IDC_EDIT_PRESHEIGHT field
+void CStudioProjectSettingsPage::onChangeEditPresHeight()
+{
+ this->setModified(true);
+
+ // Should the aspect ratio be maintained?
+ if (m_ui->m_checkConstrainProportions->isChecked()) {
+ long thePresWidth;
+ long thePresHeight;
+
+ thePresHeight = m_ui->m_ClientSizeHeight->value();
+
+ // Change the width
+ thePresWidth = long(thePresHeight * m_aspectRatio);
+
+ QSignalBlocker sb(m_ui->m_ClientSizeWidth);
+ m_ui->m_ClientSizeWidth->setValue(thePresWidth);
+ }
+}
+
+// OnCheckMaintainRatio: The aspect ratio checkbox has changed.
+void CStudioProjectSettingsPage::onCheckMaintainRatio()
+{
+ this->setModified(true);
+
+ long thePresWidth;
+ long thePresHeight;
+
+ // Get the width and height
+ thePresWidth = m_ui->m_ClientSizeWidth->value();
+ thePresHeight = m_ui->m_ClientSizeHeight->value();
+
+ // Save the Aspect Ratio
+ m_aspectRatio = double(thePresWidth) / double(thePresHeight);
+}
diff --git a/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.h b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.h
new file mode 100644
index 00000000..4b2e405d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** 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 STUDIOPROJECTSETTINGSPAGE_H
+#define STUDIOPROJECTSETTINGSPAGE_H
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+#include "StudioPreferencesPropSheet.h"
+
+#ifdef _USENEWCOLORPICKER_
+#include "StudioColorPicker.h"
+#endif
+class CStudioApp;
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class StudioProjectSettingsPage;
+}
+QT_END_NAMESPACE
+
+class CStudioProjectSettingsPage : public CStudioPreferencesPropPage
+{
+ Q_OBJECT
+ // Construction
+public:
+ explicit CStudioProjectSettingsPage(QWidget *parent = nullptr);
+ ~CStudioProjectSettingsPage();
+
+ // Overrides
+public:
+ bool onApply() override;
+
+ // Implementation
+protected:
+ double m_aspectRatio; ///< Stores the presentation width divided by the presentation height
+ QFont m_font; ///< Font for text
+ QFont m_boldFont; ///< Bold font for drawing the group boxes
+
+ void loadSettings();
+ void saveSettings();
+
+protected:
+ // Generated message map functions
+ virtual void onInitDialog();
+ void onChangeEditPresWidth();
+ void onChangeEditPresHeight();
+ void onCheckMaintainRatio();
+
+ QScopedPointer<QT_PREPEND_NAMESPACE(Ui::StudioProjectSettingsPage)> m_ui;
+};
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.ui b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.ui
new file mode 100644
index 00000000..3ef4ccd2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/UI/StudioProjectSettingsPage.ui
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StudioProjectSettingsPage</class>
+ <widget class="QWidget" name="StudioProjectSettingsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>259</width>
+ <height>269</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Presentation</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Presentation Id</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_PresentationId</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="m_PresentationId"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Width x Height</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_ClientSizeWidth</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSpinBox" name="m_ClientSizeWidth">
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="m_ClientSizeHeight">
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QCheckBox" name="m_checkConstrainProportions">
+ <property name="text">
+ <string>Constrain Proportions</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QCheckBox" name="m_checkPortraitFormat">
+ <property name="text">
+ <string>Portrait Format</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="m_checkUseKtx">
+ <property name="toolTip">
+ <string>Force runtime to check first if there
+are ktx compressed textures available
+when loading texture maps.
+Note that this should only be checked
+when ktx textures are present.</string>
+ </property>
+ <property name="text">
+ <string>Use ktx textures if available</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Project Info</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Author</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_Author</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="m_Author"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Company</string>
+ </property>
+ <property name="buddy">
+ <cstring>m_Company</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="m_Company"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>m_PresentationId</tabstop>
+ <tabstop>m_ClientSizeWidth</tabstop>
+ <tabstop>m_ClientSizeHeight</tabstop>
+ <tabstop>m_checkConstrainProportions</tabstop>
+ <tabstop>m_checkPortraitFormat</tabstop>
+ <tabstop>m_checkUseKtx</tabstop>
+ <tabstop>m_Author</tabstop>
+ <tabstop>m_Company</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/Authoring/Qt3DStudio/Utils/ITickTock.h b/src/Authoring/Qt3DStudio/Utils/ITickTock.h
new file mode 100644
index 00000000..9e3d9e4b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/ITickTock.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+#pragma once
+#ifndef ITICKTOCKH
+#define ITICKTOCKH
+
+namespace qt3dsdm {
+class ISignalConnection;
+}
+
+QT_BEGIN_NAMESPACE
+class QWidget;
+QT_END_NAMESPACE
+
+typedef std::function<void()> TTickTockProc;
+
+namespace Q3DStudio {
+/**
+ * ITickTock is meant for a relatively small number of scheduled
+ * events that need to happen on the UI thread. Clients can cancel
+ * any event simply by releasing the returned shared pointer.
+ * Interface is completely threadsafe and the returned signal connection
+ * may safely outlive the interface.
+ */
+class ITickTock
+{
+protected:
+ virtual ~ITickTock() {}
+ static ITickTock *m_Instance;
+
+public:
+ // The timer is canceled if the shared ptr deletes the signal connection. Save to call from any
+ // thread.
+ // The callback, however, (inTickTockProc) will be activated solely from the UI thread.
+ virtual std::shared_ptr<qt3dsdm::ISignalConnection>
+ AddTimer(unsigned long inTime, bool inIsPeriodic, TTickTockProc inTickTockProc,
+ const QString &inName) = 0;
+
+ // Called from UI thread to process all of the messages
+ // that have happened in the timer thread.
+ // Clients should not generally call this, it will be take care of for them.
+ // In the current implementation, MainFrm.h processes the tick tock message
+ // in order to call this function.
+ virtual void ProcessMessages() = 0;
+
+ friend class std::shared_ptr<ITickTock>;
+
+ // m_Instance is set to the first tick tock created, and unset when that tick tock
+ // goes away.
+ static std::shared_ptr<ITickTock> CreateTickTock(long inMessageID, QWidget* inTarget);
+
+ static ITickTock &GetInstance();
+};
+}
+
+#endif
diff --git a/src/Authoring/Qt3DStudio/Utils/ImportUtils.cpp b/src/Authoring/Qt3DStudio/Utils/ImportUtils.cpp
new file mode 100644
index 00000000..faa5a293
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/ImportUtils.cpp
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "Qt3DSCommonPrecompile.h"
+#include "ImportUtils.h"
+#include "Dialogs.h"
+#include "Qt3DSFileTools.h"
+#include "StudioApp.h"
+
+namespace Q3DStudio {
+
+SObjectFileType ImportUtils::GetObjectFileTypeForFile(const QString &filePath,
+ bool inCheckFileExists /*= true*/)
+{
+ QFileInfo info(filePath);
+ if (inCheckFileExists && !info.isFile())
+ return SObjectFileType(OBJTYPE_UNKNOWN, DocumentEditorFileType::Unknown);
+
+ // Mahmoud_TODOs:
+ // 1. change ext to QString (this will require points 2 and 3 below).
+ // 2. change file extensions in CDialogs.cpp to QStringLiterals.
+ // 3. CDialogs doesn't look like the right place for file extensions, should be moved somewhere
+ // else.
+ Q3DStudio::CString ext(Q3DStudio::CString::fromQString(info.suffix())); // file extension
+
+ if (ext.Compare(CDialogs::GetImportFileExtension(), Q3DStudio::CString::ENDOFSTRING, false)) {
+ return SObjectFileType(OBJTYPE_GROUP, DocumentEditorFileType::Import);
+ } else if (ext.Compare(CDialogs::GetMeshFileExtension(), Q3DStudio::CString::ENDOFSTRING,
+ false)) {
+ return SObjectFileType(OBJTYPE_MODEL, DocumentEditorFileType::Mesh);
+ } else if (CDialogs::IsImageFileExtension(ext)) {
+ // Drag-drop image to scene will auto-map to Rectangle.
+ return SObjectFileType(OBJTYPE_MODEL, OBJTYPE_IMAGE, DocumentEditorFileType::Image);
+ } else if (ext.Compare(CDialogs::GetQmlFileExtension(),
+ Q3DStudio::CString::ENDOFSTRING, false)) {
+ return g_StudioApp.isQmlStream(filePath)
+ ? SObjectFileType(OBJTYPE_QML_STREAM, DocumentEditorFileType::QmlStream)
+ : SObjectFileType(OBJTYPE_BEHAVIOR, DocumentEditorFileType::Behavior);
+ } else if (ext.Compare(CDialogs::GetMaterialDataFileExtension(),
+ Q3DStudio::CString::ENDOFSTRING, false)) {
+ return SObjectFileType(OBJTYPE_MATERIALDATA, DocumentEditorFileType::MaterialData);
+ } else if (CDialogs::IsFontFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_TEXT, DocumentEditorFileType::Font);
+ } else if (CDialogs::IsEffectFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_EFFECT, DocumentEditorFileType::Effect);
+ } else if (CDialogs::IsMaterialFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_CUSTOMMATERIAL, DocumentEditorFileType::Material);
+ } else if (CDialogs::IsPathFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_PATH, DocumentEditorFileType::Path);
+ } else if (CDialogs::IsPathBufferExtension(ext)) {
+ return SObjectFileType(OBJTYPE_PATH, DocumentEditorFileType::Path);
+ } else if (CDialogs::IsSoundFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_SOUND, DocumentEditorFileType::Sound);
+ } else if (CDialogs::isPresentationFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_PRESENTATION, DocumentEditorFileType::Presentation);
+ } else if (CDialogs::isProjectFileExtension(ext)) {
+ return SObjectFileType(OBJTYPE_PROJECT, DocumentEditorFileType::Project);
+ }
+
+ return SObjectFileType(OBJTYPE_UNKNOWN, DocumentEditorFileType::Unknown);
+}
+
+DocumentEditorInsertType::Enum ImportUtils::GetInsertTypeForDropType(EDROPDESTINATION inDestination)
+{
+ switch (inDestination) {
+ case EDROPDESTINATION_ON:
+ return DocumentEditorInsertType::LastChild;
+ case EDROPDESTINATION_ABOVE:
+ return DocumentEditorInsertType::PreviousSibling;
+ case EDROPDESTINATION_BELOW:
+ return DocumentEditorInsertType::NextSibling;
+ }
+ assert(0);
+ return DocumentEditorInsertType::LastChild;
+}
+}
diff --git a/src/Authoring/Qt3DStudio/Utils/ImportUtils.h b/src/Authoring/Qt3DStudio/Utils/ImportUtils.h
new file mode 100644
index 00000000..fef91883
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/ImportUtils.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** 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 IMPORTUTILSH
+#define IMPORTUTILSH
+
+#include "DocumentEditorEnumerations.h"
+#include "StudioObjectTypes.h"
+#include "DropSource.h"
+
+namespace Q3DStudio {
+class CFilePath;
+
+struct SObjectFileType
+{
+ EStudioObjectType m_ObjectType; // The Object Type of the File. Used to specify the result
+ // ObjectType when drag-drop file to Scene.
+ EStudioObjectType m_IconType; // The Icon Type of the File. Used for User Interface (what Icon
+ // to display) such as Project Palette.
+ DocumentEditorFileType::Enum m_FileType; // The File Type of the File.
+
+ SObjectFileType(EStudioObjectType inObjectType, EStudioObjectType inIconType,
+ DocumentEditorFileType::Enum inFileType)
+ : m_ObjectType(inObjectType)
+ , m_IconType(inIconType)
+ , m_FileType(inFileType)
+ {
+ }
+
+ SObjectFileType(EStudioObjectType inObjectType, DocumentEditorFileType::Enum inFileType)
+ : m_ObjectType(inObjectType)
+ , m_IconType(inObjectType) // Icon type is same as object type
+ , m_FileType(inFileType)
+ {
+ }
+};
+
+class ImportUtils
+{
+public:
+ static SObjectFileType GetObjectFileTypeForFile(const QString &filePath,
+ bool inCheckExists = true);
+
+ static DocumentEditorInsertType::Enum GetInsertTypeForDropType(EDROPDESTINATION inDestination);
+};
+}
+#endif
diff --git a/src/Authoring/Qt3DStudio/Utils/MouseCursor.cpp b/src/Authoring/Qt3DStudio/Utils/MouseCursor.cpp
new file mode 100644
index 00000000..f8fdc83c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/MouseCursor.cpp
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "MouseCursor.h"
+#include <QtGui/qpixmap.h>
+
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_ARROW = 0; // IDC_ARROW
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_WAIT = 1; // IDC_WAIT
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_RESIZE_LEFTRIGHT = 2; // IDC_SIZEWE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_RESIZE_UPDOWN = 3; // IDC_SIZENS
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_GROUP_MOVE = 4; // IDC_GROUP_MOVE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_GROUP_ROTATE = 5; // IDC_GROUP_ROTATE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_GROUP_SCALE = 6; // IDC_GROUP_SCALE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_ITEM_MOVE = 7; // IDC_ITEM_MOVE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_ITEM_ROTATE = 8; // IDC_ITEM_ROTATE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_ITEM_SCALE = 9; // IDC_ITEM_SCALE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_EDIT_CAMERA_PAN =
+ 10; // IDC_EDIT_CAMERA_PAN
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_EDIT_CAMERA_ROTATE =
+ 11; // IDC_EDIT_CAMERA_ROTATE
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_EDIT_CAMERA_ZOOM =
+ 12; // IDC_EDIT_CAMERA_ZOOM
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_BLANK = 13; // blank cursor
+const CMouseCursor::Qt3DSMouseCursor CMouseCursor::CURSOR_IBEAM = 14;
+
+//=============================================================================
+/**
+ * Constructor
+ */
+CMouseCursor::CMouseCursor()
+ : m_IsThemeCursor(false)
+ , m_ThemeCursor(-1)
+{
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CMouseCursor::~CMouseCursor()
+{
+ Destroy();
+}
+
+//=============================================================================
+/**
+ * @return A platform-specific handle to the cursor, or nullptr if no cursor has been loaded
+ */
+QCursor CMouseCursor::GetHandle()
+{
+ return m_Handle;
+}
+
+//=============================================================================
+/**
+ * If a cursor has been loaded, this function will change the current cursor
+ * to the newly loaded one.
+ */
+void CMouseCursor::Show()
+{
+ qWarning() << Q_FUNC_INFO << "QCursor doesn't work this way - set a breakpoint and debug caller";
+}
+
+//=============================================================================
+/**
+* Sets the cursor position
+* @param inXPos x position of the cursor (in pixels)
+* @param inYPos y position of the cursor (in pixels)
+*/
+void CMouseCursor::SetCursorPos(long inXPos, long inYPos)
+{
+ QCursor::setPos(QPoint(inXPos, inYPos));
+}
+
+//=============================================================================
+/**
+ * Releases the cursor if one has been loaded. Called by the destructor.
+ * WINDOWS IMPLEMENTATION
+ */
+void CMouseCursor::Destroy()
+{
+}
+
+//=============================================================================
+/**
+ * Loads the specified cursor resource. All cursors are expected to be
+ * resources that are loaded from ".cur" files. Some standard cursors are
+ * defined by the system and can be found in the MSDN (IDC_ARROW for example).
+ * WINDOWS IMPLEMENTATION
+ *
+ * @param inCursor ID of the cursor to be loaded
+ * @return true if the cursor was successfully loaded, otherwise false
+ */
+bool CMouseCursor::Load(Qt3DSMouseCursor inCursor)
+{
+ // Convert from our cursors to Windows specific cursors
+ switch (inCursor) {
+ case CURSOR_ARROW:
+ m_Handle = Qt::ArrowCursor;
+ break;
+
+ case CURSOR_WAIT:
+ m_Handle = Qt::WaitCursor;
+ break;
+
+ case CURSOR_RESIZE_LEFTRIGHT:
+ m_Handle = Qt::SizeHorCursor;
+ break;
+
+ case CURSOR_RESIZE_UPDOWN:
+ m_Handle = Qt::SizeVerCursor;
+ break;
+
+ case CURSOR_GROUP_MOVE:
+ m_Handle = QCursor(QPixmap(":/cursors/group_move.png"), 0, 0);
+ break;
+
+ case CURSOR_GROUP_ROTATE:
+ m_Handle = QCursor(QPixmap(":/cursors/group_rotate.png"), 0, 0);
+ break;
+
+ case CURSOR_GROUP_SCALE:
+ m_Handle = QCursor(QPixmap(":/cursors/group_scale.png"), 0, 0);
+ break;
+
+ case CURSOR_ITEM_MOVE:
+ m_Handle = QCursor(QPixmap(":/cursors/item_move.png"), 0, 0);
+ break;
+
+ case CURSOR_ITEM_ROTATE:
+ m_Handle = QCursor(QPixmap(":/cursors/item_rotate.png"), 0, 0);
+ break;
+
+ case CURSOR_ITEM_SCALE:
+ m_Handle = QCursor(QPixmap(":/cursors/item_scale.png"), 0, 0);
+ break;
+
+ case CURSOR_EDIT_CAMERA_PAN:
+ m_Handle = QCursor(QPixmap(":/cursors/edit_camera_pan.png"), 10, 10);
+ break;
+
+ case CURSOR_EDIT_CAMERA_ROTATE:
+ m_Handle = QCursor(QPixmap(":/cursors/edit_camera_rot.png"), 8, 10);
+ break;
+
+ case CURSOR_EDIT_CAMERA_ZOOM:
+ m_Handle = QCursor(QPixmap(":/cursors/edit_camera_zoom.png"), 8, 8);
+ break;
+
+ case CURSOR_BLANK:
+ m_Handle = Qt::BlankCursor;
+ break;
+ case CURSOR_IBEAM:
+ m_Handle = Qt::IBeamCursor;
+ break;
+ default:
+ m_Handle = QCursor();
+ break;
+ }
+ return true;
+}
diff --git a/src/Authoring/Qt3DStudio/Utils/MouseCursor.h b/src/Authoring/Qt3DStudio/Utils/MouseCursor.h
new file mode 100644
index 00000000..e4b8d8d9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/MouseCursor.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** 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_MOUSE_CURSOR_H
+#define INCLUDED_MOUSE_CURSOR_H 1
+
+#pragma once
+
+#include <QCursor>
+
+
+//=============================================================================
+// Includes
+//=============================================================================
+
+//=============================================================================
+/**
+ * Cross-platform cursor class.
+ */
+class CMouseCursor
+{
+public:
+ typedef long Qt3DSMouseCursor;
+
+ static const Qt3DSMouseCursor CURSOR_ARROW;
+ static const Qt3DSMouseCursor CURSOR_WAIT;
+ static const Qt3DSMouseCursor CURSOR_RESIZE_LEFTRIGHT;
+ static const Qt3DSMouseCursor CURSOR_RESIZE_UPDOWN;
+ static const Qt3DSMouseCursor CURSOR_GROUP_MOVE;
+ static const Qt3DSMouseCursor CURSOR_GROUP_ROTATE;
+ static const Qt3DSMouseCursor CURSOR_GROUP_SCALE;
+ static const Qt3DSMouseCursor CURSOR_ITEM_MOVE;
+ static const Qt3DSMouseCursor CURSOR_ITEM_ROTATE;
+ static const Qt3DSMouseCursor CURSOR_ITEM_SCALE;
+ static const Qt3DSMouseCursor CURSOR_EDIT_CAMERA_PAN;
+ static const Qt3DSMouseCursor CURSOR_EDIT_CAMERA_ROTATE;
+ static const Qt3DSMouseCursor CURSOR_EDIT_CAMERA_ZOOM;
+ static const Qt3DSMouseCursor CURSOR_BLANK;
+ static const Qt3DSMouseCursor CURSOR_DROP_INVALID;
+ static const Qt3DSMouseCursor CURSOR_DROP_MOVE;
+ static const Qt3DSMouseCursor CURSOR_DROP_COPY;
+ static const Qt3DSMouseCursor CURSOR_IBEAM;
+
+ CMouseCursor();
+ virtual ~CMouseCursor();
+ QCursor GetHandle();
+ void Show();
+ void Hide();
+ void SetCursorPos(long inXPos, long inYPos);
+ bool Load(Qt3DSMouseCursor inCursor);
+
+protected:
+ void Destroy();
+
+ QCursor m_Handle;
+
+private:
+ bool m_IsThemeCursor;
+ short m_ThemeCursor;
+};
+
+#endif // INCLUDED_MOUSE_CURSOR_H
diff --git a/src/Authoring/Qt3DStudio/Utils/QmlUtils.cpp b/src/Authoring/Qt3DStudio/Utils/QmlUtils.cpp
new file mode 100644
index 00000000..a80c6642
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/QmlUtils.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** 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 "QmlUtils.h"
+
+#include <QtGui/qcolor.h>
+
+QmlUtils::QmlUtils()
+{
+
+}
+
+double QmlUtils::getLuminance(const QColor &color)
+{
+ return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF();
+}
+
+bool QmlUtils::isBright(const QColor &color)
+{
+ return getLuminance(color) > .7;
+}
diff --git a/src/Authoring/Qt3DStudio/Utils/QmlUtils.h b/src/Authoring/Qt3DStudio/Utils/QmlUtils.h
new file mode 100644
index 00000000..0f772fb6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/QmlUtils.h
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** 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 COLORUTILS_H
+#define COLORUTILS_H
+
+#include <QtCore/qobject.h>
+
+class QmlUtils : public QObject
+{
+ Q_OBJECT
+
+public:
+ QmlUtils();
+
+ Q_INVOKABLE double getLuminance(const QColor &color);
+ Q_INVOKABLE bool isBright(const QColor &color);
+};
+
+
+#endif // COLORUTILS_H
diff --git a/src/Authoring/Qt3DStudio/Utils/ResourceCache.cpp b/src/Authoring/Qt3DStudio/Utils/ResourceCache.cpp
new file mode 100644
index 00000000..c51a01f0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/ResourceCache.cpp
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "ResourceCache.h"
+#include "MouseCursor.h"
+#include "StudioUtils.h"
+
+#include <QtCore/qurl.h>
+
+//=============================================================================
+/**
+ * Constructor
+ */
+CResourceCache::CResourceCache()
+{
+}
+
+//=============================================================================
+/**
+ * Destructor: releases all loaded resources.
+ */
+CResourceCache::~CResourceCache()
+{
+ Clear();
+}
+
+//=============================================================================
+/**
+ * Returns a default instance of the resource cache so that the whole application
+ * can use the same cache if desired.
+ * @return Handle to the default instance of the cache
+ */
+CResourceCache *CResourceCache::GetInstance()
+{
+ static CResourceCache theCache;
+ return &theCache;
+}
+
+//=============================================================================
+/**
+ * Retrieves a bitmap image of the specified name. Currently only accepts .png
+ * files.
+ * @param inName Name of the bitmap file to be fetched
+ * @return Pointer to a bitmap object or NULL if the image could not be loaded
+ */
+QPixmap CResourceCache::GetBitmap(const QString &inName)
+{
+ QPixmap theImage;
+
+ // If our image name is not empty, then lets get it...
+ if (!inName.isEmpty()) {
+ TImageMap::iterator thePos = m_Images.find(inName);
+ if (thePos != m_Images.end()) {
+ theImage = thePos->second;
+ } else {
+ const QString resPath = QStringLiteral("%1%2").arg(StudioUtils::resourceImagePath(),
+ inName);
+ if (theImage.load(resPath))
+ m_Images[inName] = theImage;
+ else
+ qWarning() << Q_FUNC_INFO << "missing image at path:" << resPath;
+ }
+ }
+ return theImage;
+}
+
+//=============================================================================
+/**
+ * Retrieves the specified cursor resource. The cursor is loaded if necessary
+ * otherwise a previously loaded cursor of the same ID is returned.
+ * @param inResourceID ID of the cursor to be loaded (see SCursor.h)
+ * @return Pointer to the cursor, or NULL if the cursor could not be loaded
+ */
+QCursor CResourceCache::GetCursor(CMouseCursor::Qt3DSMouseCursor inResourceID)
+{
+ CMouseCursor *theCursor = NULL;
+ CMouseCursor::Qt3DSMouseCursor theKey = inResourceID;
+
+ TCursorMap::iterator thePos = m_Cursors.find(theKey);
+ if (thePos != m_Cursors.end()) {
+ theCursor = thePos->second;
+ } else {
+ theCursor = new CMouseCursor();
+ if (theCursor->Load(inResourceID))
+ m_Cursors[theKey] = theCursor;
+ else {
+ delete theCursor;
+ theCursor = nullptr;
+ }
+ }
+
+ return theCursor ? theCursor->GetHandle() : QCursor();
+}
+
+//=============================================================================
+/**
+ * Clears all the maps of resources and deletes any associated resources.
+ * Called by the destructor.
+ */
+void CResourceCache::Clear()
+{
+ m_Images.clear();
+
+ TCursorMap::iterator theCursorPos = m_Cursors.begin();
+ for (; theCursorPos != m_Cursors.end(); ++theCursorPos) {
+ CMouseCursor *theCursor = theCursorPos->second;
+ delete theCursor;
+ }
+ m_Cursors.clear();
+}
diff --git a/src/Authoring/Qt3DStudio/Utils/ResourceCache.h b/src/Authoring/Qt3DStudio/Utils/ResourceCache.h
new file mode 100644
index 00000000..1a23f232
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/ResourceCache.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** 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_RESOURCE_CACHE_H
+#define INCLUDED_RESOURCE_CACHE_H 1
+
+#pragma once
+
+#include "MouseCursor.h"
+#include "Qt3DSString.h"
+#include <map>
+
+#include <QCursor>
+#include <QPixmap>
+
+class CResImage;
+
+class CResourceCache
+{
+ typedef std::map<QString, QPixmap> TImageMap;
+ typedef std::map<CMouseCursor::Qt3DSMouseCursor, CMouseCursor *> TCursorMap;
+
+public:
+ CResourceCache();
+ virtual ~CResourceCache();
+
+ static CResourceCache *GetInstance();
+
+ QPixmap GetBitmap(const QString &inName);
+ QCursor GetCursor(CMouseCursor::Qt3DSMouseCursor inResourceID);
+
+ void Clear();
+
+protected:
+ TImageMap m_Images;
+ TCursorMap m_Cursors;
+};
+#endif // INCLUDED_RESOURCE_CACHE_H
diff --git a/src/Authoring/Qt3DStudio/Utils/StudioUtils.cpp b/src/Authoring/Qt3DStudio/Utils/StudioUtils.cpp
new file mode 100644
index 00000000..16ca1253
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/StudioUtils.cpp
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "CoreUtils.h"
+#include "StudioPreferences.h"
+#include "StudioClipboard.h"
+#include "Pt.h"
+#include "StudioUtils.h"
+#include "StudioApp.h"
+#include "MainFrm.h"
+
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qdesktopwidget.h>
+#include <QtGui/qdesktopservices.h>
+#include <QtGui/qscreen.h>
+#include <QtGui/qwindow.h>
+#include <QtCore/qurl.h>
+
+QString StudioUtils::resourcePath()
+{
+ return QStringLiteral(":/res");
+}
+
+QString StudioUtils::resourceImagePath()
+{
+ return QStringLiteral(":/images/");
+}
+
+QString StudioUtils::resourceImageUrl()
+{
+ return QStringLiteral("qrc:/images/");
+}
+
+// Returns the qml import path required for binary installations
+QString StudioUtils::qmlImportPath()
+{
+ QString extraImportPath(QStringLiteral("%1/qml"));
+ return extraImportPath.arg(QApplication::applicationDirPath());
+}
+
+qreal StudioUtils::devicePixelRatio(QWindow *window)
+{
+ qreal pixelRatio = 1.0;
+
+ QWindow *w = window ? window
+ : g_StudioApp.m_pMainWnd
+ ? g_StudioApp.m_pMainWnd->windowHandle() : nullptr;
+
+ if (w) {
+ QScreen *s = w->screen();
+ if (s)
+ pixelRatio = s->devicePixelRatio();
+ }
+
+ return pixelRatio;
+}
+
+// Reads the contents of a text file into QDomDocument
+bool StudioUtils::readFileToDomDocument(const QString &filePath, QDomDocument &domDoc)
+{
+ QFile file(filePath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ file.setTextModeEnabled(false);
+ qWarning() << __FUNCTION__ << file.errorString() << "'" << filePath << "'";
+ return false;
+ }
+
+ return domDoc.setContent(&file);
+}
+
+// Opens a text file for saving and reads its contents into QDomDocument
+bool StudioUtils::openDomDocumentSave(QSaveFile &file, QDomDocument &domDoc)
+{
+ if (!readFileToDomDocument(file.fileName(), domDoc))
+ return false;
+ if (!file.open(QIODevice::WriteOnly)) {
+ file.setTextModeEnabled(false);
+ qWarning() << __FUNCTION__ << file.errorString();
+ return false;
+ }
+ return true;
+}
+
+// Saves contents of a QDomDocument into a previously opened text file
+bool StudioUtils::commitDomDocumentSave(QSaveFile &file, const QDomDocument &domDoc)
+{
+ // Disable end-of-line conversions
+ file.setTextModeEnabled(false);
+ // Overwrites entire file
+ if (file.resize(0) && file.write(domDoc.toByteArray(4)) != -1 && file.commit())
+ return true;
+
+ qWarning() << __FUNCTION__ << file.errorString();
+ return false;
+}
+
+// Opens text file for saving without reading its contents
+bool StudioUtils::openTextSave(QSaveFile &file)
+{
+ if (!file.open(QIODevice::WriteOnly)) {
+ file.setTextModeEnabled(false);
+ qWarning() << __FUNCTION__ << file.errorString();
+ return false;
+ }
+ return true;
+}
diff --git a/src/Authoring/Qt3DStudio/Utils/StudioUtils.h b/src/Authoring/Qt3DStudio/Utils/StudioUtils.h
new file mode 100644
index 00000000..792fa002
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/StudioUtils.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** 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_STUDIO_UTILS_H
+#define INCLUDED_STUDIO_UTILS_H
+
+#include <QtXml/qdom.h>
+#include <QtCore/qsavefile.h>
+#include <QtCore/qxmlstream.h>
+#include <QtGui/qwindow.h>
+
+class StudioUtils {
+public:
+ static QString resourceImagePath();
+ static QString resourceImageUrl();
+ static QString resourcePath();
+ static QString qmlImportPath();
+
+ static qreal devicePixelRatio(QWindow *window = nullptr);
+
+ static bool readFileToDomDocument(const QString &filePath, QDomDocument &domDoc);
+ static bool openDomDocumentSave(QSaveFile &file, QDomDocument &domDoc);
+ static bool commitDomDocumentSave(QSaveFile &file, const QDomDocument &domDoc);
+ static bool openTextSave(QSaveFile &file);
+};
+
+#endif // INCLUDED_STUDIO_UTILS_H
diff --git a/src/Authoring/Qt3DStudio/Utils/TickTock.cpp b/src/Authoring/Qt3DStudio/Utils/TickTock.cpp
new file mode 100644
index 00000000..23ae4625
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Utils/TickTock.cpp
@@ -0,0 +1,313 @@
+/****************************************************************************
+**
+** 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 "Qt3DSCommonPrecompile.h"
+#include "ITickTock.h"
+#include "Qt3DSDMSignals.h"
+#include "Thread.h"
+#include "Mutex.h"
+#include "Conditional.h"
+#include "StandardExtensions.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdatetime.h>
+#include <QtWidgets/qwidget.h>
+
+using namespace Q3DStudio;
+using Q3DStudio::CString;
+using namespace qt3dsdm;
+using namespace std;
+
+namespace {
+
+struct TickTockImpl;
+
+struct TickTockItem : public ISignalConnection
+{
+ TTickTockProc m_Callback;
+ bool m_IsPeriodic;
+ unsigned long m_Time;
+ quint64 m_NextTime;
+ CString m_Name;
+ TickTockImpl *m_Impl;
+
+ TickTockItem(TTickTockProc inCallback, bool inPeriodic, unsigned long inTime,
+ quint64 inNextTime, const CString &inName, TickTockImpl &inImpl)
+ : m_Callback(inCallback)
+ , m_IsPeriodic(inPeriodic)
+ , m_Time(inTime)
+ , m_NextTime(inNextTime)
+ , m_Name(inName)
+ , m_Impl(&inImpl)
+ {
+ }
+ // Implemented below to access TickTockImpl API
+ virtual ~TickTockItem();
+
+ // Called when our tick tock impl is through with us.
+ // We cannot access the tick tock impl after this as
+ // it could cause a crash
+ void Release() { m_Impl = nullptr; }
+
+ void Signal()
+ {
+ if (m_Impl)
+ m_Callback();
+ }
+ bool operator<(const TickTockItem &inOther) const { return m_NextTime < inOther.m_NextTime; }
+};
+
+struct TickTockDerefCompare
+{
+ bool operator()(const TickTockItem *lhs, const TickTockItem *rhs) { return *lhs < *rhs; }
+};
+
+typedef vector<TickTockItem *> TTockList;
+
+/**
+ * Struct that implements the ITickTock interface via a thread that runs
+ * and schedules information and a message that is sent back in order to
+ * process that schedule information and send out signals on the UI thread.
+ */
+struct TickTockImpl : public ITickTock, public CRunnable
+{
+ // Mutex to protect our internal datastructures
+ // Mainly m_Tockers and m_SignalledTockers
+ CMutex m_Mutex;
+ // Our thread
+ CThread m_Thread;
+ // Event to wake up schedule thread; used to keep thread from
+ // spinning till next event.
+ CConditional m_Event;
+
+ // The sorted list (by m_NextTime) of tick tock signals
+ TTockList m_Tockers;
+ // The list of signals that are past their time and need to be sent.
+ TTockList m_SignalledTockers;
+ // True if the thread is running and we want it to continue.
+ volatile bool m_IsRunning;
+ // Message to fire into the window in order to get our notifications
+ // processed on the UI thread
+ long m_MessageID;
+ // Target window that we use in order to fire events and get processing
+ // done back on the UI thread from the schedule thread.
+ QWidget *m_Target;
+
+ TickTockImpl(long inMessageID, QWidget *inTarget)
+ : m_Thread(this, "ITickTock", NULL, false)
+ , m_IsRunning(false)
+ , m_MessageID(inMessageID)
+ , m_Target(inTarget)
+ {
+ }
+
+ virtual void Initialize()
+ {
+ m_IsRunning = true;
+ m_Thread.Start();
+ }
+
+ virtual ~TickTockImpl()
+ {
+ if (m_Instance == this)
+ m_Instance = nullptr;
+ {
+ CMutex::Scope __mutexScope(&m_Mutex);
+ UnsafeReleaseTockers(m_Tockers);
+ UnsafeReleaseTockers(m_SignalledTockers);
+ }
+
+ if (m_IsRunning) {
+ m_IsRunning = false;
+ // Wake up the thread to notify it to exit
+ m_Event.Notify();
+ // Wait for the thread to exit.
+ m_Thread.Join();
+ }
+ }
+
+ TSignalConnectionPtr AddTimer(unsigned long inTime, bool inIsPeriodic,
+ TTickTockProc inTickTockProc,
+ const QString &inName) override
+ {
+ // Lock down so we don't conflict with the timer thread.
+ CMutex::Scope __mutexScope(&m_Mutex);
+
+ std::shared_ptr<TickTockItem> retval =
+ std::make_shared<TickTockItem>(inTickTockProc, inIsPeriodic, inTime,
+ QDateTime::currentMSecsSinceEpoch() + inTime,
+ Q3DStudio::CString::fromQString(inName),
+ std::ref(*this));
+ if (inTime > 0) {
+ UnsafeInsertTimer(*retval);
+ } else {
+ assert(inIsPeriodic == false);
+ m_SignalledTockers.push_back(retval.get());
+ }
+ m_Event.Notify();
+
+ return retval;
+ }
+
+ void RemoveTimer(TickTockItem &inItem)
+ {
+ CMutex::Scope __mutexScope(&m_Mutex);
+ UnsafeRemoveTimer(inItem, m_Tockers);
+ UnsafeRemoveTimer(inItem, m_SignalledTockers);
+ }
+
+ //=============================================================================
+ /**
+ * Call from the main processing thread to process any existing messages.
+ * This should be called when a message of the type specified in the constructor
+ * is recieved. This will call all the functors on the timers that have
+ * triggered.
+ */
+ void ProcessMessages() override
+ {
+ quint64 theCurrentTime = QDateTime::currentMSecsSinceEpoch();
+
+ CMutex::Scope __mutexScope(&m_Mutex);
+ // Go through all the timers looking for expired ones
+ for (TTockList::iterator theTocks = m_SignalledTockers.begin(),
+ end = m_SignalledTockers.end();
+ theTocks != end; ++theTocks) {
+ TickTockItem *theTock = (*theTocks);
+ // If this item hasn't been released in another thread.
+ if (theTock->m_Impl) {
+ theTock->Signal();
+
+ // If it is periodic, re-add it.
+ if (theTock->m_IsPeriodic == true) {
+ theTock->m_NextTime = theCurrentTime + theTock->m_Time;
+ UnsafeInsertTimer(*theTock);
+ }
+ // If it isn't, then we forget about it. The client's have a shared-ptr
+ // to the object so it will get deleted eventually, it just isn't any of
+ // our business any more
+ else
+ theTock->Release();
+ }
+ }
+
+ m_SignalledTockers.clear();
+ m_Event.Notify();
+ }
+
+ void Run(void *) override
+ {
+ unsigned long theNextTime = (unsigned long)-1;
+ // Stay in this loop until the running flag is turned off on the constructor.
+ while (m_IsRunning) {
+ // Wait for either the next time or the change notification
+ m_Event.Wait(theNextTime);
+
+ CMutex::Scope __mutexScope(&m_Mutex);
+
+ // Get the amount of time this should sleep for.
+ theNextTime = (unsigned long)-1;
+ quint64 theCurrentTime = QDateTime::currentMSecsSinceEpoch();
+ // We know that m_Tocks is sorted by m_NextTime.
+ // So we run through it linearly, transferring singalled items
+ // into the signalled vector.
+ size_t idx = 0, end = m_Tockers.size();
+ for (; idx < end; ++idx) {
+ TickTockItem *item(m_Tockers[idx]);
+ if (item->m_NextTime <= theCurrentTime && item->m_Impl != nullptr) {
+ m_SignalledTockers.push_back(item);
+ } else
+ break;
+ }
+ // We then remove all signalled items.
+ if (m_Tockers.empty() == false)
+ m_Tockers.erase(m_Tockers.begin(), m_Tockers.begin() + idx);
+
+ // And we reset next time to the difference between the current time
+ // and the first tock's next time.
+ if (m_Tockers.empty() == false) {
+ assert(m_Tockers.front()->m_NextTime > theCurrentTime);
+ theNextTime = m_Tockers.front()->m_NextTime - theCurrentTime;
+ }
+
+ if (m_SignalledTockers.empty() == false) {
+ // Send the async notification that timers have expired.
+ qApp->postEvent(m_Target, new QTimerEvent(m_MessageID));
+ }
+ }
+ }
+
+private:
+ ////////////////////////////////////////////////////////////////
+ // Unsafe functions are functions that require the mutex locked
+ // in order to function correctly
+ ////////////////////////////////////////////////////////////////
+ void UnsafeInsertTimer(TickTockItem &inItem)
+ {
+ binary_sort_insert_unique(m_Tockers, &inItem, TickTockDerefCompare());
+ }
+
+ void UnsafeReleaseTockers(TTockList &inTockers)
+ {
+ for (size_t idx = 0, end = inTockers.size(); idx < end; ++idx)
+ inTockers[idx]->Release();
+ inTockers.clear();
+ }
+
+ // Unsafe means we don't have the mutex
+ void UnsafeRemoveTimer(TickTockItem &inItem, TTockList &ioList)
+ {
+ TTockList::iterator theTimer = std::find(ioList.begin(), ioList.end(), &inItem);
+ if (theTimer != ioList.end())
+ ioList.erase(theTimer);
+ }
+};
+
+TickTockItem::~TickTockItem()
+{
+ if (m_Impl != NULL)
+ m_Impl->RemoveTimer(*this);
+ m_Impl = NULL;
+}
+}
+
+ITickTock *ITickTock::m_Instance(NULL);
+
+std::shared_ptr<ITickTock> ITickTock::CreateTickTock(long inMessageID, QWidget *inWnd)
+{
+ std::shared_ptr<TickTockImpl> theTickTock(
+ std::make_shared<TickTockImpl>(inMessageID, std::ref(inWnd)));
+ theTickTock->Initialize();
+ if (m_Instance == NULL)
+ m_Instance = theTickTock.get();
+ return theTickTock;
+}
+
+ITickTock &ITickTock::GetInstance()
+{
+ return *m_Instance;
+}
diff --git a/src/Authoring/Qt3DStudio/Workspace/Dialogs.cpp b/src/Authoring/Qt3DStudio/Workspace/Dialogs.cpp
new file mode 100644
index 00000000..2a5a6780
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Workspace/Dialogs.cpp
@@ -0,0 +1,1562 @@
+/****************************************************************************
+**
+** 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 "qtAuthoring-config.h"
+#include "Dialogs.h"
+#include "StudioApp.h"
+#include "Core.h"
+#include "Doc.h"
+#include "MainFrm.h"
+#include "InterpolationDlg.h"
+#include "Qt3DSMessageBox.h"
+#include "ProgressView.h"
+#include "TimeEditDlg.h"
+#include "DurationEditDlg.h"
+#include "StudioPreferences.h"
+#include "ResetKeyframeValuesDlg.h"
+#include "GLVersionDlg.h"
+#include "Qt3DSMacros.h"
+#include "ImportUtils.h"
+
+#include <QtWidgets/qcolordialog.h>
+#include <QtWidgets/qfiledialog.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtWidgets/qcheckbox.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtWidgets/qdesktopwidget.h>
+#include <QtWidgets/qpushbutton.h>
+#include <QtGui/qscreen.h>
+#include <QtWidgets/qlayout.h>
+#include <QtWidgets/qdialogbuttonbox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qstyle.h>
+
+namespace {
+
+inline Q3DStudio::CString CreateExtensionsList(const char **extList)
+{
+ Q3DStudio::CString retval;
+ for (const char **ext = extList; *ext != nullptr; ++ext) {
+ if (retval.Length())
+ retval += " ";
+ retval += Q3DStudio::CString("*.") + *ext;
+ }
+ return retval;
+}
+
+struct SAllowedTypesEntry
+{
+ Q3DStudio::DocumentEditorFileType::Enum m_FileType;
+ QString m_ResourceString; // Model Files, Image Files, etc
+ const char **m_FileExtensions;
+};
+
+const char *imgExts[] = {
+ "png", "jpg", "jpeg", "dds", "bmp", "gif", "hdr", "ktx", nullptr,
+};
+
+const wchar_t *wideImgExts[] = {
+ L"png", L"jpg", L"jpeg", L"dds", L"bmp", L"gif", L"hdr", L"ktx", nullptr,
+};
+
+const char *modelExts[] = {
+ CDialogs::GetDAEFileExtension(),
+ #ifdef QT_3DSTUDIO_FBX
+ CDialogs::GetFbxFileExtension(),
+ #endif
+ nullptr,
+};
+
+const char *meshExts[] = {
+ CDialogs::GetMeshFileExtension(), nullptr,
+};
+
+const char *importExts[] = {
+ CDialogs::GetImportFileExtension(), nullptr,
+};
+
+const char *behaviorExts[] = {
+ CDialogs::GetQmlFileExtension(), nullptr,
+};
+
+const char *presentationExts[] = {
+ "uip", nullptr,
+};
+
+const wchar_t *widePresentationExts[] = {
+ L"uip", nullptr,
+};
+
+const char *qmlStreamExts[] = {
+ "qml", nullptr,
+};
+
+const wchar_t *wideQmlStreamExts[] = {
+ L"qml", nullptr,
+};
+
+const char *projectExts[] = {
+ "uia", nullptr,
+};
+
+const wchar_t *wideProjectExts[] = {
+ L"uia", nullptr,
+};
+
+const char *fontExts[] = {
+ "ttf", "otf", nullptr,
+};
+const wchar_t *wideFontExts[] = {
+ L"ttf", L"otf", nullptr,
+};
+
+const char *effectExts[] = {
+ "effect", nullptr,
+};
+
+const wchar_t *wideEffectExts[] = {
+ L"effect", nullptr,
+};
+
+const char *materialExts[] = {
+ "material", "shader", "materialdef", nullptr,
+};
+
+const wchar_t *wideMaterialExts[] = {
+ L"material", L"shader", L"materialdef", nullptr,
+};
+
+const char *soundExts[] = {
+ "wav", nullptr,
+};
+
+const wchar_t *wideSoundExts[] = {
+ L"wav", nullptr,
+};
+
+// List of file types allowed during import
+// Note: Despite its name, Q3DStudio::DocumentEditorFileType::DAE type includes
+// all supported model types
+SAllowedTypesEntry g_AllowedImportTypes[] = {
+ { Q3DStudio::DocumentEditorFileType::Presentation, QObject::tr("Presentations"),
+ presentationExts },
+ { Q3DStudio::DocumentEditorFileType::QmlStream, QObject::tr("Qml streams"), qmlStreamExts },
+ { Q3DStudio::DocumentEditorFileType::DAE, QObject::tr("Model Files"), modelExts },
+ { Q3DStudio::DocumentEditorFileType::Image, QObject::tr("Image Files"), imgExts },
+ { Q3DStudio::DocumentEditorFileType::Behavior, QObject::tr("Behavior Scripts"), behaviorExts },
+ { Q3DStudio::DocumentEditorFileType::Effect, QObject::tr("Effect Files"), effectExts },
+ { Q3DStudio::DocumentEditorFileType::Font, QObject::tr("Font Files"), fontExts },
+ { Q3DStudio::DocumentEditorFileType::Material, QObject::tr("Material Files"), materialExts },
+};
+int g_NumAllowedImportTypes = sizeof(g_AllowedImportTypes) / sizeof(*g_AllowedImportTypes);
+
+// List of file types allowed for file references
+SAllowedTypesEntry g_AllowedFileReferencesTypes[] = {
+ { Q3DStudio::DocumentEditorFileType::Image, QObject::tr("Image Files"), imgExts },
+ { Q3DStudio::DocumentEditorFileType::Behavior, QObject::tr("Behavior Scripts"), behaviorExts },
+ { Q3DStudio::DocumentEditorFileType::Mesh, QObject::tr("Mesh Files"), meshExts },
+ { Q3DStudio::DocumentEditorFileType::Import, QObject::tr("Import Files"), importExts },
+ { Q3DStudio::DocumentEditorFileType::Effect, QObject::tr("Effect Files"), effectExts },
+};
+int g_NumAllowedFileReferencesTypes =
+ sizeof(g_AllowedFileReferencesTypes) / sizeof(*g_AllowedFileReferencesTypes);
+}
+
+/**
+ * @param inShowGUI true if dialogs should be displayed or piped to std:cout instead
+ */
+CDialogs::CDialogs(bool inShowGUI /*= true*/)
+ : m_ProgressPalette(nullptr)
+ , m_ShowGUI(inShowGUI)
+ , m_LastSaveFile(QStringLiteral("./"))
+{
+ const auto effectExt = effectExtensions();
+ const auto fontExt = fontExtensions();
+ const auto mapExt = mapExtensions();
+ const auto materialExt = materialExtensions();
+ const auto modelExt = modelExtensions();
+ const auto behaviorExt = behaviorExtensions();
+ const auto presentationExt = presentationExtensions();
+
+ for (const auto ext : effectExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("effects"));
+ for (const auto ext : fontExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("fonts"));
+ for (const auto ext : mapExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("maps"));
+ for (const auto ext : materialExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("materials"));
+ for (const auto ext : modelExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("models"));
+ for (const auto ext : behaviorExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("scripts"));
+ for (const auto ext : presentationExt)
+ m_defaultDirForSuffixMap.insert(ext, QStringLiteral("presentations"));
+}
+
+CDialogs::~CDialogs()
+{
+}
+
+/**
+ * Displays a dialog asking the user to choose the keyframe interpolation.
+ *
+ * @param ioEaseIn value to be set as the ease in default - passes back the value chosen by the user
+ * @param ioEaseOut value to be set as the ease out default - passes back the value chosen by the
+ * user
+ * @return true if the user clicked OK on the dialog (indicating that the values should be updated
+ * on the track)
+ */
+bool CDialogs::PromptForKeyframeInterpolation(float &ioEaseIn, float &ioEaseOut)
+{
+ bool theReturnValue = false;
+
+ CInterpolationDlg theInterpolationDialog;
+ theInterpolationDialog.setEaseIn(ioEaseIn);
+ theInterpolationDialog.setEaseOut(ioEaseOut);
+
+ // If the user presses the OK button
+ if (theInterpolationDialog.exec() == QDialog::Accepted) {
+ // Retrieve the new interpolation values
+ ioEaseIn = theInterpolationDialog.easeIn();
+ ioEaseOut = theInterpolationDialog.easeOut();
+ theReturnValue = true;
+ }
+
+ return theReturnValue;
+}
+
+/**
+ * Notify the user that the deletion of an asset has failed.
+ */
+void CDialogs::DisplayAssetDeleteFailed()
+{
+ QString theMessage = QObject::tr("Studio was unable to save your project data. Please close "
+ "down Studio and try again.");
+ QString theTitle = QObject::tr("General Error");
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theMessage, Qt3DSMessageBox::ICON_ERROR, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theMessage;
+ }
+}
+
+// Get the export choice.
+Qt3DSFile CDialogs::GetExportChoice(const Q3DStudio::CString &, const Q3DStudio::CString &)
+{
+ // Need to fix this for windows if we decide to use it
+ return Qt3DSFile("", false, false);
+}
+
+/**
+ * Notify that we are unable to refresh the resource.
+ */
+void CDialogs::DisplayRefreshResourceFailed(const QString &inResourceName,
+ const QString &inDescription)
+{
+ QString theTitle = QObject::tr("Refresh File Error");
+ QString theText = QObject::tr("Studio was unable to refresh the resource '%1'.\n")
+ .arg(inResourceName);
+
+ if (!inDescription.isEmpty())
+ theText += inDescription;
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theText, Qt3DSMessageBox::ICON_WARNING, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theText;
+ }
+}
+
+/**
+ * Notify the user that the loading of the requested resource failed.
+ *
+ * @param inURL the URL for the asset that was to have been imported
+ * @param inDescription description for the failure, if any
+ * @param inWarningsOnly not a failure, just warnings
+ */
+void CDialogs::DisplayImportFailed(const QUrl &inURL, const QString &inDescription,
+ bool inWarningsOnly)
+{
+ // Notify the user we couldn't load the resource.
+ QString theTitle;
+ QString theText;
+ QString theMsgText;
+
+ theTitle = QObject::tr("Studio Import Resource ");
+ theTitle += !inWarningsOnly ? QObject::tr("Error") : QObject::tr("Warning");
+
+ // Determine the asset type
+ EStudioObjectType theAssetType =
+ Q3DStudio::ImportUtils::GetObjectFileTypeForFile(inURL.path(), false)
+ .m_ObjectType;
+
+ bool theIsStudioObject = theAssetType != OBJTYPE_UNKNOWN;
+
+ // Is this a behavior file, but perhaps incorrectly formatted?
+ if (theAssetType == OBJTYPE_BEHAVIOR) {
+ // Load the message about the behavior format
+ if (inWarningsOnly) {
+ theText = QObject::tr("Warnings were detected during import of the behavior script."
+ "\nPlease check the file.\n");
+ } else {
+ theText = QObject::tr("Studio was unable to import the behavior script.\nPlease check "
+ "the file and try again.\nNote that behavior files must be "
+ "syntactically correct to be importable.");
+ }
+ if (!inDescription.isEmpty())
+ theText += QStringLiteral("\n") + inDescription;
+ } else if (theAssetType != OBJTYPE_UNKNOWN || theIsStudioObject) {
+ // Valid registered file type, but invalid file
+
+ bool theNoDescription = inDescription.isEmpty();
+ // Load default text stating that the import resource failed.
+ // descriptions if present are presented as "reasons" for failure.
+ if (!inWarningsOnly || theNoDescription) {
+ theText = QObject::tr("Studio was unable to import the resource%1")
+ .arg(theNoDescription ? QStringLiteral(".\n")
+ : QObject::tr(" due to the following reason(s):\n"));
+ }
+ if (!theNoDescription)
+ theText += inDescription;
+ else
+ theText += QObject::tr("Please check the above file and try again.");
+ } else {
+ // Display the warning messsage if we have one
+ // instead of a meaningless message. This provides more feed back
+ if (!inDescription.isEmpty()) {
+ theText += inDescription;
+ } else {
+ theText = QObject::tr("Studio was unable to import the resource file.\nThis "
+ "resource file type is not currently supported.\n");
+ }
+ }
+
+ theMsgText = !inWarningsOnly ? QObject::tr("Import resource failed:")
+ : QObject::tr("Import resource succeeded with warning(s):");
+ theMsgText += QStringLiteral("\n%1\n\n").arg(inURL.toDisplayString()) + theText;
+
+ // Display the failed import resource message.
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theMsgText, Qt3DSMessageBox::ICON_WARNING, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theMsgText;
+ }
+}
+
+// Inform user that UIP file contained datainput bindings for datainput names not found
+// from UIA file. Returns true if user wants to delete invalid datainput bindings
+// automatically
+bool CDialogs::DisplayUndefinedDatainputDlg(
+ const QMultiMap<QString,
+ QPair<qt3dsdm::Qt3DSDMInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle>> *map)
+{
+ const auto keys = map->uniqueKeys();
+ QString theTitle = QObject::tr("Missing Data Input");
+ QLabel *theText = new QLabel;
+ int keysSize = keys.size();
+ if (keysSize > 1) {
+ theText->setText(QObject::tr("Could not find Data Inputs. "
+ "%1 Data Inputs used as controllers are undefined.")
+ .arg(keysSize));
+ } else {
+ theText->setText(QObject::tr("Could not find Data Input. "
+ "%1 Data Input used as controller is undefined.")
+ .arg(keysSize));
+ }
+
+ QString theSmallText;
+ for (auto it : keys)
+ theSmallText.append(QStringLiteral("\n") + it);
+
+ theSmallText.append(QStringLiteral("\n"));
+
+ QLabel *diList = new QLabel(theSmallText);
+
+ QLabel *noUndoText = new QLabel;
+ noUndoText->setText(QObject::tr("Clear cannot be undone.\n"));
+ theText->setIndent(10);
+ diList->setIndent(20);
+ noUndoText->setIndent(10);
+
+
+ QIcon warn(QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning));
+ QLabel *warnLab = new QLabel();
+ const int size = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize);
+ warnLab->setPixmap(warn.pixmap(QSize(size, size)));
+
+ QDialog msgBox(g_StudioApp.m_pMainWnd, Qt::WindowCloseButtonHint | Qt::WindowTitleHint
+ | Qt::MSWindowsFixedSizeDialogHint);
+ QGridLayout *layout = new QGridLayout();
+
+ layout->addWidget(warnLab, 1, 1);
+ layout->addWidget(theText, 1, 2);
+
+ QPushButton *ok = new QPushButton(QObject::tr("Clear invalid controllers now"));
+ QPushButton *cancel = new QPushButton(QObject::tr("I'll fix controllers manually"));
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal);
+ buttonBox->addButton(ok, QDialogButtonBox::AcceptRole);
+ buttonBox->addButton(cancel, QDialogButtonBox::RejectRole);
+ layout->addWidget(buttonBox, 4, 1, 1, 3, Qt::AlignCenter);
+ layout->addWidget(diList, 2, 2, Qt::AlignVCenter);
+ layout->addWidget(noUndoText, 3, 2);
+
+ msgBox.setLayout(layout);
+
+ msgBox.setWindowTitle(theTitle);
+
+ QObject::connect(buttonBox, &QDialogButtonBox::accepted, &msgBox, &QDialog::accept);
+ QObject::connect(buttonBox, &QDialogButtonBox::rejected, &msgBox, &QDialog::reject);
+
+ auto res = msgBox.exec();
+
+ return res == QDialog::Accepted ? true : false;
+}
+
+QString CDialogs::ConfirmRefreshModelFile(const QString &inFile)
+{
+ // this produces an extension string which contains all allowed formats specified in
+ // g_AllowedImportTypes
+ // currently DAE and FBX
+ QString initialFilter;
+ QString theFileFilter =
+ CreateAllowedTypesString(Q3DStudio::DocumentEditorFileType::DAE, initialFilter,
+ true, true);
+
+ return QFileDialog::getOpenFileName(g_StudioApp.m_pMainWnd, QObject::tr("Open"),
+ inFile, theFileFilter, nullptr);
+}
+
+QList<QUrl> CDialogs::SelectAssets(QString &outPath,
+ Q3DStudio::DocumentEditorFileType::Enum assetType)
+{
+ QFileDialog fd(g_StudioApp.m_pMainWnd);
+ fd.setDirectory(outPath);
+ fd.setFileMode(QFileDialog::ExistingFiles);
+ QString initialFilter;
+ fd.setNameFilter(CreateAllowedTypesString(
+ assetType, initialFilter, true,
+ assetType != Q3DStudio::DocumentEditorFileType::Unknown));
+ fd.selectNameFilter(initialFilter);
+ fd.setWindowTitle(QObject::tr("Import Assets"));
+
+ QList<QUrl> files;
+ if (fd.exec()) {
+ files = fd.selectedUrls();
+ QString newOutPath = fd.directory().absolutePath();
+ QString contentPath = QDir::fromNativeSeparators(
+ Qt3DSFile::GetApplicationDirectory() + QStringLiteral("/Content"));
+
+ if (assetType != Q3DStudio::DocumentEditorFileType::Unknown
+ || (assetType == Q3DStudio::DocumentEditorFileType::Unknown
+ && !newOutPath.startsWith(contentPath))) {
+ // Return the new path if we are browsing a specific asset type, or we are browsing
+ // outside the Content folder.
+ outPath = newOutPath;
+ }
+ }
+
+ return files;
+}
+
+QString CDialogs::defaultDirForUrl(const QUrl &url)
+{
+ QString defaultDir;
+ if (!url.isLocalFile())
+ return defaultDir;
+
+ const QFileInfo fi(url.toLocalFile());
+ const QString suffix = fi.suffix();
+
+ defaultDir = m_defaultDirForSuffixMap.value(suffix.toLower());
+
+ return defaultDir;
+}
+
+/**
+ * Notify the user that the presentation we tried to load has failed.
+ * @param loadFileInfo QFileInfo for the failing file
+ * @param errrorText error message
+ */
+void CDialogs::DisplayLoadingPresentationFailed(const QFileInfo &loadFileInfo,
+ const QString &loadFileName,
+ const QString &errorText)
+{
+ QString theErrorMessage = loadFileInfo.isFile() ? loadFileInfo.fileName() : loadFileName;
+
+ if (errorText.isEmpty())
+ theErrorMessage += QObject::tr(" failed to load.");
+ else
+ theErrorMessage += errorText;
+
+ QString theErrorTitle = QObject::tr("Open File Error");
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theErrorTitle, theErrorMessage, Qt3DSMessageBox::ICON_WARNING, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theErrorTitle << ": " << theErrorMessage;
+ }
+}
+
+/**
+ * Notify the user that the presentation we tried to save has failed.
+ *
+ * @param inSavedLocation The AKFile that we failed to save.
+ */
+void CDialogs::DisplaySavingPresentationFailed()
+{
+ QString theErrorMessage = QObject::tr("Unable to save presentation. Please ensure that the "
+ "file is not set as read-only.");
+ QString theErrorTitle = QObject::tr("Qt 3D Studio");
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theErrorTitle, theErrorMessage, Qt3DSMessageBox::ICON_WARNING, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theErrorTitle << ": " << theErrorMessage;
+ }
+}
+
+/**
+ * Display a message box to indicate failure to overwrite a read-only file
+ *
+ * @param inSavedLocation
+ * the file location to be saved
+ *
+ * @return void
+ */
+void CDialogs::DisplaySaveReadOnlyFailed(const QString &inSavedLocation)
+{
+ QString theMsg = QObject::tr("Studio cannot save the file '%1'. The file is marked Read-Only."
+ "\nSave the file with another file name or to a different "
+ "location.").arg(inSavedLocation);
+ QString theTitle = QObject::tr("Qt 3D Studio");
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theMsg, Qt3DSMessageBox::ICON_WARNING, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theMsg;
+ }
+}
+
+void CDialogs::DisplayObjectRenamed(const QString &origName, const QString &newName, bool async)
+{
+ QString title = QObject::tr("Warning");
+ QString message = QObject::tr("Object %1 was renamed to %2 because "
+ "original name was duplicated "
+ "under its parent.").arg(origName).arg(newName);
+ if (async) {
+ QTimer::singleShot(0, [this, title, message]() {
+ DisplayMessageBox(title, message, Qt3DSMessageBox::ICON_WARNING, false);
+ });
+ } else {
+ DisplayMessageBox(title, message, Qt3DSMessageBox::ICON_WARNING, false);
+ }
+}
+
+/**
+ * Displays a Qt3DSMessageBox using the specified parameters. The message box
+ * is modal to the main frame. This provides an easy way to place modal dialogs
+ * to the user, without requiring your class to know about the main frame or
+ * window refs.
+ * @param inTitle Title of the message box (not used on Mac)
+ * @param inText Text of the message
+ * @param inIcon Icon to be displayed next to the text
+ * @param inShowCancel true to show a Cancel button, false only show an OK button
+ * @return Indication of which button was pressed to dismiss the dialog
+ */
+Qt3DSMessageBox::EMessageBoxReturn
+CDialogs::DisplayMessageBox(const QString &inTitle, const QString &inText,
+ Qt3DSMessageBox::EMessageBoxIcon inIcon, bool inShowCancel,
+ QWidget *parent)
+{
+ Qt3DSMessageBox::EMessageBoxReturn theUserChoice;
+
+ if (m_ShowGUI) {
+ if (parent == nullptr)
+ parent = g_StudioApp.m_pMainWnd;
+ theUserChoice =
+ Qt3DSMessageBox::Show(inTitle, inText, inIcon,
+ inShowCancel, parent);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << inTitle << ": " << inText;
+ theUserChoice = Qt3DSMessageBox::MSGBX_OK;
+ }
+
+ return theUserChoice;
+}
+
+void CDialogs::asyncDisplayMessageBox(const QString &title, const QString &text,
+ Qt3DSMessageBox::EMessageBoxIcon icon, QWidget *parent)
+{
+ if (m_ShowGUI) {
+ if (parent == nullptr)
+ parent = g_StudioApp.m_pMainWnd;
+ QTimer::singleShot(0, [title, text, icon, parent]() {
+ Qt3DSMessageBox::Show(title, text, icon, false, parent);
+ });
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << title << ": " << text;
+ }
+}
+
+int CDialogs::DisplayChoiceBox(const QString &inTitle, const QString &inText, int inIcon)
+{
+ if (m_ShowGUI) {
+ QMessageBox box;
+ box.setWindowTitle(inTitle);
+ box.setText(inText);
+ switch (inIcon) {
+ case Qt3DSMessageBox::ICON_WARNING:
+ box.setIcon(QMessageBox::Warning);
+ break;
+ case Qt3DSMessageBox::ICON_ERROR:
+ box.setIcon(QMessageBox::Critical);
+ break;
+ case Qt3DSMessageBox::ICON_INFO:
+ box.setIcon(QMessageBox::Information);
+ break;
+ default:
+ break;
+ }
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+ switch (box.exec()) {
+ case QMessageBox::Yes:
+ return IDYES;
+ case QMessageBox::No:
+ return IDNO;
+ default:
+ Q_UNREACHABLE();
+ }
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << inTitle << ": " << inText;
+ return IDYES;
+ }
+}
+
+/**
+ * Display a box to choose whether to override or skip an existing asset during import
+ *
+ * @return user choice (Yes, No, YesToAll, NoToAll)
+ */
+int CDialogs::displayOverrideAssetBox(const QString &assetPath)
+{
+ if (m_ShowGUI) {
+ QMessageBox box;
+ box.setWindowTitle(QObject::tr("Asset Exists"));
+ box.setText(QObject::tr("The following asset already exists, do you want to override it?"));
+ box.setInformativeText(assetPath);
+ box.setIcon(QMessageBox::Warning);
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+ box.setButtonText(QMessageBox::Yes, QObject::tr("Override"));
+ box.setButtonText(QMessageBox::No, QObject::tr("Skip"));
+ box.setDefaultButton(QMessageBox::No);
+ box.setCheckBox(new QCheckBox(QObject::tr("Do this action for all and don't ask again."),
+ &box));
+ int choice = box.exec();
+ if (box.checkBox()->isChecked())
+ choice = choice == QMessageBox::Yes ? QMessageBox::YesToAll : QMessageBox::NoToAll;
+ return choice;
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << "Asset Override: " << assetPath;
+ return QMessageBox::No;
+ }
+}
+
+const char *CDialogs::GetDAEFileExtension()
+{
+ return "dae";
+}
+
+const char *CDialogs::GetFbxFileExtension()
+{
+ return "fbx";
+}
+
+// Null terminated list
+const char **CDialogs::GetImgFileExtensions()
+{
+ return imgExts;
+}
+
+const char *CDialogs::GetImportFileExtension()
+{
+ return "import";
+}
+
+const char *CDialogs::GetMeshFileExtension()
+{
+ return "mesh";
+}
+
+const char *CDialogs::GetQmlFileExtension()
+{
+ return "qml";
+}
+
+const char *CDialogs::GetMaterialDataFileExtension()
+{
+ return "materialdef";
+}
+
+const char **CDialogs::GetFontFileExtensions()
+{
+ return fontExts;
+}
+
+const char **CDialogs::GetEffectFileExtensions()
+{
+ return effectExts;
+}
+
+const char **CDialogs::GetMaterialFileExtensions()
+{
+ return materialExts;
+}
+const char **CDialogs::GetSoundFileExtensions()
+{
+ return soundExts;
+}
+
+bool IsFileExtension(const char *inExt, const char **inExts)
+{
+ if (inExt == nullptr)
+ return false;
+ for (const char **ext = inExts; *ext != nullptr; ++ext) {
+ if (QString::compare(inExt, *ext, Qt::CaseInsensitive) == 0)
+ return true;
+ }
+ return false;
+}
+
+bool CDialogs::IsImageFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, imgExts);
+}
+
+bool CDialogs::IsFontFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, fontExts);
+}
+
+bool CDialogs::IsEffectFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, effectExts);
+}
+
+bool CDialogs::IsMaterialFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, materialExts);
+}
+
+bool CDialogs::IsSoundFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, soundExts);
+}
+
+bool CDialogs::isMeshFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, meshExts);
+}
+
+bool CDialogs::isImportFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, importExts);
+}
+
+bool CDialogs::isPresentationFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, presentationExts);
+}
+
+bool CDialogs::isProjectFileExtension(const char *inExt)
+{
+ return IsFileExtension(inExt, projectExts);
+}
+
+const wchar_t **CDialogs::GetWideImgFileExtensions()
+{
+ return wideImgExts;
+}
+
+const wchar_t *CDialogs::GetWideDAEFileExtension()
+{
+ return L"dae";
+}
+
+const wchar_t *CDialogs::GetWideFbxFileExtension()
+{
+ return L"fbx";
+}
+
+const wchar_t *CDialogs::GetWideImportFileExtension()
+{
+ return L"import";
+}
+
+const wchar_t *CDialogs::GetWideMeshFileExtension()
+{
+ return L"mesh";
+}
+
+const wchar_t **CDialogs::GetWideFontFileExtensions()
+{
+ return wideFontExts;
+}
+
+const wchar_t **CDialogs::GetWideEffectFileExtensions()
+{
+ return wideEffectExts;
+}
+
+const wchar_t **CDialogs::GetWideMaterialFileExtensions()
+{
+ return wideMaterialExts;
+}
+
+bool IsFileExtension(const wchar_t *inExt, const wchar_t **inExts)
+{
+ if (inExt == nullptr)
+ return false;
+ for (const wchar_t **ext = inExts; *ext != nullptr; ++ext) {
+ if (QString::compare(QString::fromWCharArray(inExt),
+ QString::fromWCharArray(*ext), Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CDialogs::IsImageFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideImgExts);
+}
+
+bool CDialogs::IsFontFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideFontExts);
+}
+
+bool CDialogs::IsEffectFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideEffectExts);
+}
+
+bool CDialogs::IsMaterialFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideMaterialExts);
+}
+
+bool CDialogs::IsPathFileExtension(const wchar_t *inExt)
+{
+ return QString::compare(QString::fromWCharArray(inExt), "svg", Qt::CaseInsensitive) == 0;
+}
+
+bool CDialogs::IsPathBufferExtension(const wchar_t *inExt)
+{
+ return QString::compare(QString::fromWCharArray(inExt), "path", Qt::CaseInsensitive) == 0;
+}
+
+bool CDialogs::IsSoundFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideSoundExts);
+}
+
+bool CDialogs::isPresentationFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, widePresentationExts);
+}
+
+bool CDialogs::isProjectFileExtension(const wchar_t *inExt)
+{
+ return IsFileExtension(inExt, wideProjectExts);
+}
+
+/**
+ * CreateAllowedTypesString: Creates the string used to determine allowable types
+ * for import or for filereferences
+ * @return the string that dynamically created with the extensions supported.
+ */
+QString CDialogs::CreateAllowedTypesString(Q3DStudio::DocumentEditorFileType::Enum fileTypeFilter,
+ QString &outInitialFilter, bool forImport,
+ bool exclusive)
+{
+ QString theReturnString;
+ QString combinedFilter;
+ int theCount = forImport ? g_NumAllowedImportTypes : g_NumAllowedFileReferencesTypes;
+ for (int idx = 0; idx < theCount; ++idx) {
+ const SAllowedTypesEntry &entry =
+ forImport ? g_AllowedImportTypes[idx] : g_AllowedFileReferencesTypes[idx];
+ if (!exclusive || fileTypeFilter == entry.m_FileType) {
+ QString theTypeString(entry.m_ResourceString);
+ QString theExtensions(CreateExtensionsList(entry.m_FileExtensions).toQString());
+ const QString filterString = theTypeString + " (" + theExtensions + ");;";
+ theReturnString += filterString;
+ if (exclusive)
+ outInitialFilter = filterString;
+ else
+ combinedFilter += theExtensions + " ";
+ }
+ }
+ if (!combinedFilter.isEmpty()) {
+ combinedFilter.chop(1); // Remove last separator
+ theReturnString.prepend(QObject::tr("All Supported Asset types")
+ + " (" + combinedFilter + ");;");
+ outInitialFilter = QObject::tr("All Supported Asset types")
+ + " (" + combinedFilter + ")";
+ }
+ theReturnString.chop(2);
+ if (exclusive)
+ outInitialFilter.chop(2);
+
+ return theReturnString;
+}
+
+/**
+ * Display a error dialog box with the given text string that describes the error.
+ */
+void CDialogs::DisplayKnownErrorDialog(const QString &inErrorText)
+{
+ // make sure this is valid
+ if (!inErrorText.isEmpty()) {
+ QString theTitle = QObject::tr("Qt 3D Studio");
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, inErrorText, Qt3DSMessageBox::ICON_ERROR,
+ false, g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << inErrorText;
+ }
+ }
+}
+
+/**
+ * Prompt the user to save the document before losing their changes.
+ * This is used when closing, loading or newing up a document when the current
+ * one has modifications.
+ * @return the user's choice.
+ */
+CDialogs::ESavePromptResult CDialogs::PromptForSave()
+{
+ QString theDocTitle;
+
+ Qt3DSFile theCurrentDoc = g_StudioApp.GetCore()->GetDoc()->GetDocumentPath();
+ if (theCurrentDoc.IsFile())
+ theDocTitle = theCurrentDoc.GetName().toQString();
+ else // if the current doc has not been saved then use the default title.
+ theDocTitle = QObject::tr("Untitled");
+
+ QString thePrompt = QObject::tr("Save changes to %1?").arg(theDocTitle);
+
+ int theChoice = QMessageBox::warning(nullptr, QObject::tr("Qt 3D Studio"),
+ thePrompt, QMessageBox::Yes | QMessageBox::No
+ | QMessageBox::Cancel);
+
+ ESavePromptResult theResult = CANCEL_OPERATION;
+
+ switch (theChoice) {
+ case QMessageBox::Yes:
+ theResult = SAVE_FIRST;
+ break;
+ case QMessageBox::No:
+ theResult = CONTINUE_NO_SAVE;
+ break;
+ case QMessageBox::Cancel:
+ theResult = CANCEL_OPERATION;
+ break;
+ default:
+ break;
+ }
+
+ return theResult;
+}
+
+/**
+ * Prompt the user for a file to save to.
+ * If browsing for location for presentation (isProject == false), then
+ * allow browsing only inside current project.
+ * If browsing for a location for a project, then disallow browsing inside current project.
+ * If isCopy is true, then create a copy of the existing project rather than a new one.
+ * isCopy is ignored if isProject is false;
+ * Return an invalid file if the user cancels the save dialog.
+ */
+QString CDialogs::GetSaveAsChoice(const QString &inDialogTitle, bool isProject, bool isCopy)
+{
+ QString projPath(QDir::cleanPath(g_StudioApp.GetCore()->getProjectFile().getProjectPath()));
+ QString previousFolder;
+ QString theFilename = g_StudioApp.GetCore()->GetDoc()->GetDocumentPath();
+
+ if (theFilename.isEmpty() || isProject)
+ theFilename = QObject::tr("Untitled");
+
+ QString theFileExt = QStringLiteral(".uip");
+
+ QFileDialog theFileDlg;
+ theFileDlg.setOption(QFileDialog::DontConfirmOverwrite);
+
+ const QFileInfo fi(m_LastSaveFile);
+ // TODO: introduce a workspace concept?
+ theFileDlg.setDirectory(
+ fi.path() == QLatin1String(".")
+ ? QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
+ : fi.path());
+ theFileDlg.setAcceptMode(QFileDialog::AcceptSave);
+ theFileDlg.setDefaultSuffix(theFileExt);
+ if (!inDialogTitle.isEmpty())
+ theFileDlg.setWindowTitle(inDialogTitle);
+
+ if (isProject) {
+ theFileDlg.setLabelText(QFileDialog::FileName, QObject::tr("Project Name"));
+ if (!isCopy)
+ theFileDlg.setLabelText(QFileDialog::Accept, QObject::tr("Create"));
+ // Limit browsing to outside project directory
+ if (!projPath.isEmpty()) {
+ QDir projDir(projPath);
+ projDir.cdUp();
+ previousFolder = projDir.absolutePath();
+ if (isCopy)
+ theFileDlg.setDirectory(previousFolder);
+ connect(&theFileDlg, &QFileDialog::directoryEntered,
+ [&](const QString &dir) {
+ QFileInfo fi(QDir::cleanPath(dir));
+ const QString absPath = fi.absoluteFilePath();
+ if (absPath.startsWith(projPath))
+ theFileDlg.setDirectory(previousFolder);
+ else
+ previousFolder = absPath;
+ });
+ }
+ } else {
+ // Limit browsing to project directory
+ connect(&theFileDlg, &QFileDialog::directoryEntered,
+ [&theFileDlg, &projPath](const QString &dir) {
+ QFileInfo fi(QDir::cleanPath(dir));
+ const QString absPath = fi.absoluteFilePath();
+ if (!absPath.startsWith(projPath))
+ theFileDlg.setDirectory(projPath);
+ });
+ // Note that since we are using native file dialog, we cannot change the sidebar or
+ // "look in" combo contents of the file dialog, which may cause bit of confusion.
+ }
+
+ bool theShowDialog = true;
+ QString theFile;
+ while (theShowDialog && theFileDlg.exec()) {
+ theShowDialog = false;
+ QString selectedName = theFileDlg.selectedFiles().front();
+
+ // Make sure file name has correct extension
+ if (isProject && isCopy && selectedName.endsWith(theFileExt))
+ selectedName.chop(theFileExt.length());
+ else if (!selectedName.endsWith(theFileExt))
+ selectedName.append(theFileExt);
+
+ if (!isProject) {
+ // If user somehow manages to select a file path outside project directory, save to
+ // default presentations directory
+ QFileInfo fi(QDir::cleanPath(selectedName));
+ const QString absPath = fi.absoluteFilePath();
+ if (!absPath.startsWith(projPath))
+ selectedName = projPath + QStringLiteral("/presentations/") + fi.fileName();
+ }
+
+ theFile = selectedName;
+ m_LastSaveFile = selectedName;
+ // New directory is only created when creating a new project.
+ if (isProject) {
+ Q3DStudio::CFilePath theFinalDir;
+ Q3DStudio::CFilePath theFinalDoc;
+ g_StudioApp.GetCore()->GetCreateDirectoryFileName(selectedName,
+ theFinalDir, theFinalDoc);
+
+ // Update last save file to final doc
+ m_LastSaveFile = theFinalDoc.absoluteFilePath();
+
+ // File dialog shouldn't allow choosing existing folder for project, but since we
+ // are using native dialog, let's play it safe and check.
+ if (theFinalDir.Exists()) {
+ const QString title(QObject::tr("Existing directory"));
+ const QString warning = QObject::tr("%1 already exists.")
+ .arg(theFinalDir.GetFileName().toQString());
+ QMessageBox::warning(nullptr, title, warning);
+ // Reset the file and show the file dialog again
+ theFile.clear();
+ theShowDialog = true;
+ continue;
+ }
+ }
+ }
+
+ return theFile;
+}
+
+QString CDialogs::getImportVariantsDlg()
+{
+ QString docDir = QFileInfo(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath()).absolutePath();
+
+ QFileDialog dlg;
+ dlg.setDirectory(docDir);
+ dlg.setWindowTitle(tr("Import variants"));
+ dlg.setDefaultSuffix(QStringLiteral(".variants"));
+ dlg.setNameFilters({tr("All supported files (*.variants *.uia)"),
+ tr("Variants files (*.variants)"), tr("Project files (*.uia)")});
+ auto result = dlg.exec();
+
+ if (result == QDialog::Accepted && !dlg.selectedFiles().empty())
+ return dlg.selectedFiles().front();
+
+ return {};
+}
+
+QString CDialogs::getExportVariantsDlg()
+{
+ QString docDir = QFileInfo(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath()).absolutePath();
+
+ QFileDialog dlg;
+ dlg.setDirectory(docDir);
+ dlg.setAcceptMode(QFileDialog::AcceptSave);
+ dlg.setWindowTitle(tr("Export variants"));
+ dlg.setDefaultSuffix(QStringLiteral(".variants"));
+ dlg.setNameFilters({QObject::tr("Variants files (*.variants)")});
+ dlg.exec();
+
+ if (!dlg.selectedFiles().empty())
+ return dlg.selectedFiles().front();
+
+ return {};
+}
+
+/**
+ * Prompt the user for a file to create.
+ * @param isProject true: new project, false: new presentation
+ * @return an invalid file if the user cancels the save dialog.
+ */
+QString CDialogs::GetNewDocumentChoice(const QString &inInitialDirectory, bool isProject)
+{
+ if (inInitialDirectory.size())
+ m_LastSaveFile = inInitialDirectory + QStringLiteral("/");
+ QString title = isProject ? QObject::tr("Create New Project")
+ : QObject::tr("Create New Presentation");
+ return GetSaveAsChoice(title, isProject);
+}
+
+/**
+ * Prompt the user for a file to open.
+ * This will return an invalid file if the user cancels the save dialog.
+ */
+QString CDialogs::GetFileOpenChoice(const QString &inInitialDirectory)
+{
+ QFileInfo theFile;
+ QString theImportFilter = QObject::tr("Studio UI Presentation (*.uip *.uia)");
+
+ QFileDialog theFileDlg(g_StudioApp.m_pMainWnd, QString(),
+ (inInitialDirectory == QLatin1String("."))
+ ? QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
+ : inInitialDirectory,
+ theImportFilter);
+ theFileDlg.setAcceptMode(QFileDialog::AcceptOpen);
+
+ if (theFileDlg.exec() == QDialog::Accepted) {
+ theFile.setFile(theFileDlg.selectedFiles().first());
+ m_LastSaveFile = theFile.absoluteFilePath();
+ }
+
+ return theFile.absoluteFilePath();
+}
+
+/**
+ * Prompt the user to make sure they want to revert the current project.
+ * @return true if they do want to continue with the revert.
+ */
+bool CDialogs::ConfirmRevert()
+{
+ bool theConfirmation = false;
+ QString thePrompt = QObject::tr("All changes that have been made to your project since your "
+ "last save will be lost.\n\nDo you want to continue?");
+ QString theTitle = QObject::tr("Qt 3D Studio");
+
+ Qt3DSMessageBox::EMessageBoxReturn theChoice;
+
+ if (m_ShowGUI) {
+ theChoice = Qt3DSMessageBox::Show(theTitle, thePrompt, Qt3DSMessageBox::ICON_WARNING, true,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << thePrompt;
+ theChoice = Qt3DSMessageBox::MSGBX_OK;
+ }
+
+ // user decided to go ahead and delete all unused resources
+ if (theChoice == Qt3DSMessageBox::MSGBX_OK)
+ theConfirmation = true;
+
+ return theConfirmation;
+}
+
+/**
+ * Displays a progress screen, if there is not one aleady being shown. The
+ * progress screen doesn't get dismissed until you call
+ * CDialogs::DestroyProgressScreen().
+ * @param inActionText text to be displayed as the action
+ * @param inAdditionalText additional text, for example a file name
+ */
+void CDialogs::DisplayProgressScreen(const QString &inActionText,
+ const QString &inAdditionalText)
+{
+ if (m_ShowGUI && !m_ProgressPalette) {
+ m_ProgressPalette = new CProgressView(g_StudioApp.m_pMainWnd);
+ m_ProgressPalette->SetActionText(inActionText);
+ m_ProgressPalette->SetAdditionalText(inAdditionalText);
+ m_ProgressPalette->show();
+ qApp->processEvents();
+ }
+}
+
+/**
+ * If a loading screen is currently being shown, this function destroys it. You
+ * can show the loading screen again with another call to
+ * CDialogs::DisplayLoadingScreen().
+ */
+void CDialogs::DestroyProgressScreen()
+{
+ if (m_ShowGUI && m_ProgressPalette) {
+ delete m_ProgressPalette;
+ m_ProgressPalette = nullptr;
+ }
+}
+
+/**
+ * Inform the user that the environment variables entered does not match the format
+ * expected, listing down all those settings that are wrong.
+ * @param inErrorMessage the listing of all those errors.
+ */
+void CDialogs::DisplayEnvironmentVariablesError(const Q3DStudio::CString &inErrorMessage)
+{
+ QString theTitle = QObject::tr("Unable to accept all Environment Variables");
+ QString theMessage = QObject::tr("The following variables will not be saved:\n")
+ + inErrorMessage.toQString()
+ + QObject::tr("\nVariables must be listed in the following format:"
+ "\n{Variable} = Value\n");
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theMessage, Qt3DSMessageBox::ICON_ERROR, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theMessage;
+ }
+}
+
+/**
+ * Reset settings.
+ * Typically inCurrentDocPath is only set when Studio is first launched.
+ * @param inCurrentDocPath the current document path, if any. Application directory if
+ *there is none.
+ */
+void CDialogs::ResetSettings(const QString &inCurrentDocPath)
+{
+ // Initialize the default dir/paths to the current document path if specified, otherwise leave
+ // everything as it is.
+ if (!inCurrentDocPath.isEmpty())
+ m_LastSaveFile = inCurrentDocPath;
+}
+
+bool CDialogs::DisplayResetKeyframeValuesDlg()
+{
+ CResetKeyframeValuesDlg theDialog;
+ return theDialog.exec() == QDialog::Accepted;
+}
+
+/**
+ * User trying to do a pathological paste, such as pasting a component copied from a different
+ *instance
+ * of Studio into an instance of the same component that already exists in the current instance
+ *of Studio, and
+ * is potentially replaced and deleted.
+ */
+void CDialogs::DisplayPasteFailed()
+{
+ QString theTitle = QObject::tr("Paste Error");
+ QString theMessage = QObject::tr("Sorry, the attempted paste operation cannot be completed,"
+ " the destination is invalid.");
+
+ if (m_ShowGUI) {
+ Qt3DSMessageBox::Show(theTitle, theMessage, Qt3DSMessageBox::ICON_ERROR, false,
+ g_StudioApp.m_pMainWnd);
+ } else {
+ qCDebug(qt3ds::TRACE_INFO) << theTitle << ": " << theMessage;
+ }
+}
+
+/**
+ * Video card OpenGL version is too low to be supported.
+ */
+void CDialogs::DisplayGLVersionError(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inMinVersion)
+{
+ DisplayGLVersionDialog(inGLVersion, inMinVersion, true);
+}
+
+/**
+ * Video card OpenGL version is outdated, but could be usable.
+ */
+void CDialogs::DisplayGLVersionWarning(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inRecommendedVersion)
+{
+ DisplayGLVersionDialog(inGLVersion, inRecommendedVersion, false);
+}
+
+void CDialogs::asyncDisplayTimeEditDialog(long time, IDoc *doc, long objectAssociation,
+ KeyframeManager *keyframesManager) const
+{
+ QTimer::singleShot(0, [time, doc, objectAssociation, keyframesManager]() {
+ CTimeEditDlg timeEditDlg(keyframesManager);
+ timeEditDlg.showDialog(time, doc, objectAssociation);
+ });
+}
+
+void CDialogs::asyncDisplayDurationEditDialog(long startTime, long endTime,
+ ITimeChangeCallback *callback) const
+{
+ QTimer::singleShot(0, [startTime, endTime, callback]() {
+ CDurationEditDlg durationEditDlg;
+ durationEditDlg.showDialog(startTime, endTime, callback);
+ });
+}
+
+void CDialogs::showWidgetBrowser(QWidget *screenWidget, QWidget *browser, const QPoint &point,
+ WidgetBrowserAlign align, QSize customSize)
+{
+ QSize popupSize = customSize.isEmpty() ? CStudioPreferences::browserPopupSize() : customSize;
+ browser->resize(popupSize);
+ QPoint newPos = point;
+
+ // Make sure the popup doesn't go outside the screen
+ int screenNum = QApplication::desktop()->screenNumber(screenWidget);
+ QScreen *screen = nullptr;
+
+ // If we are somehow not on any screen, just show the browser at upper left corner of the
+ // primary screen.
+ if (screenNum < 0) {
+ screen = QGuiApplication::primaryScreen();
+ newPos = QPoint(25, 25) + QPoint(popupSize.width(), popupSize.height());
+ } else {
+ screen = QGuiApplication::screens().at(screenNum);
+ }
+ QRect screenRect = screen->availableGeometry();
+
+ const int controlH = CStudioPreferences::controlBaseHeight();
+ if (align == WidgetBrowserAlign::ComboBox) {
+ // position the popup below the combobox
+ newPos -= QPoint(popupSize.width(), -controlH) + screenRect.topLeft();
+ // if no space below the combobox, move it above it
+ if (newPos.y() + popupSize.height() > screenRect.height())
+ newPos.setY(newPos.y() - popupSize.height() - controlH);
+ } else if (align == WidgetBrowserAlign::ToolButton) {
+ // The point is assumed to be the lower right corner of the button
+ newPos -= QPoint(popupSize.width(), popupSize.height()) + screenRect.topLeft();
+ if (newPos.y() < 0)
+ newPos.setY(newPos.y() + popupSize.height() - controlH);
+ } else { // WidgetBrowserAlign::Center
+ newPos -= QPoint(popupSize.width() / 2, popupSize.height() / 2) + screenRect.topLeft();
+ }
+
+ if (newPos.y() < 0)
+ newPos.setY(0);
+
+ if (newPos.x() + popupSize.width() > screenRect.width())
+ newPos.setX(screenRect.width() - popupSize.width());
+ else if (newPos.x() < 0)
+ newPos.setX(0);
+
+ newPos += screenRect.topLeft();
+
+ browser->move(newPos);
+
+ // Show asynchronously to avoid flashing blank window on first show
+ QTimer::singleShot(0, screenWidget, [browser, popupSize, screenWidget] {
+ browser->show();
+ browser->activateWindow();
+ browser->setFocus();
+ // Make sure we are the desired size after show, as showing on a different screen
+ // can cause incorrectly sized popup.
+ QTimer::singleShot(0, screenWidget, [browser, popupSize] {
+ browser->resize(popupSize);
+ });
+ });
+}
+
+/**
+ * Display the error dialog or warning dialog that OpenGL version is lower than what is
+ *expected
+ */
+void CDialogs::DisplayGLVersionDialog(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inRecommendedVersion, bool inError)
+{
+ QString theTitle;
+ QString theMessage;
+
+ if (inError) {
+ theTitle = QObject::tr("Error");
+ theMessage = QObject::tr("OpenGL version %1 is unsupported.\nPlease use a video card and "
+ "driver that supports at least OpenGL %2 or higher.").arg(
+ inGLVersion.toQString()).arg(inRecommendedVersion.toQString());
+ } else {
+ theTitle = QObject::tr("Warning");
+ theMessage = QObject::tr("OpenGL version %1 detected.\nA video card with an updated driver "
+ "capable of OpenGL %2 is recommended or there may be rendering "
+ "errors.").arg(inGLVersion.toQString()).arg(
+ inRecommendedVersion.toQString());
+ }
+
+ CGLVersionDlg theGLVersionDlg;
+ theGLVersionDlg.Initialize(theTitle, theMessage, inError);
+ theGLVersionDlg.exec();
+
+ if (theGLVersionDlg.GetDontShowAgain())
+ CStudioPreferences::SetDontShowGLVersionDialog(true);
+}
+
+QStringList CDialogs::effectExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : effectExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::fontExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : fontExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::mapExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : imgExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::materialExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : materialExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::modelExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : modelExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::behaviorExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : behaviorExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::presentationExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : presentationExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QStringList CDialogs::qmlStreamExtensions()
+{
+ static QStringList exts;
+ if (exts.isEmpty()) {
+ for (const char *ext : qmlStreamExts) {
+ if (ext)
+ exts << QString::fromLatin1(ext);
+ }
+ }
+ return exts;
+}
+
+QColor CDialogs::displayColorDialog(const QColor &color, bool showAlpha) const
+{
+ QColorDialog theColorDlg;
+ theColorDlg.setOption(QColorDialog::DontUseNativeDialog);
+
+ if (showAlpha)
+ theColorDlg.setOption(QColorDialog::ShowAlphaChannel);
+
+ theColorDlg.setCurrentColor(color);
+ connect(&theColorDlg, &QColorDialog::currentColorChanged, this, &CDialogs::onColorChanged);
+ int result = theColorDlg.exec();
+ disconnect(&theColorDlg, &QColorDialog::currentColorChanged, this, &CDialogs::onColorChanged);
+ if (result == QDialog::Accepted)
+ return theColorDlg.selectedColor();
+ else
+ return color;
+}
diff --git a/src/Authoring/Qt3DStudio/Workspace/Dialogs.h b/src/Authoring/Qt3DStudio/Workspace/Dialogs.h
new file mode 100644
index 00000000..02f0b8d3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Workspace/Dialogs.h
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 1999-2001 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_DIALOGS_H
+#define INCLUDED_DIALOGS_H
+
+#include "Qt3DSFile.h"
+#include "StudioObjectTypes.h"
+#include "Qt3DSMessageBox.h"
+#include "Qt3DSFileTools.h"
+#include "DocumentEditorEnumerations.h"
+
+#include <QtWidgets/qmessagebox.h>
+
+class IDoc;
+class CProgressView;
+class KeyframeManager;
+class ITimeChangeCallback;
+
+class CDialogs : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum ESavePromptResult {
+ CANCEL_OPERATION,
+ CONTINUE_NO_SAVE,
+ SAVE_FIRST,
+ };
+
+ CDialogs(bool inShowGUI = true);
+ virtual ~CDialogs();
+
+ void DisplayAssetDeleteFailed();
+ void DisplayRefreshResourceFailed(const QString &inResourceName, const QString &inDescription);
+ QString ConfirmRefreshModelFile(const QString &inOriginalPath);
+ QList<QUrl> SelectAssets(QString &outPath, Q3DStudio::DocumentEditorFileType::Enum assetType);
+
+ QString defaultDirForUrl(const QUrl &url);
+
+ static QStringList effectExtensions();
+ static QStringList fontExtensions();
+ static QStringList mapExtensions();
+ static QStringList materialExtensions();
+ static QStringList modelExtensions();
+ static QStringList behaviorExtensions();
+ static QStringList presentationExtensions();
+ static QStringList qmlStreamExtensions();
+
+ // This is not an appropriate place for these, but better
+ // in an inappropriate place than duplicated
+ static const char *GetDAEFileExtension();
+ static const char *GetFbxFileExtension();
+ // Null terminated list
+ static const char **GetImgFileExtensions();
+ static const char *GetImportFileExtension();
+ static const char *GetMeshFileExtension();
+ static const char *GetQmlFileExtension();
+ static const char *GetMaterialDataFileExtension();
+ static const char **GetFontFileExtensions();
+ static const char **GetEffectFileExtensions();
+ static const char **GetMaterialFileExtensions();
+ static const char **GetSoundFileExtensions();
+ static bool IsImageFileExtension(const char *inExt);
+ static bool IsFontFileExtension(const char *inExt);
+ static bool IsEffectFileExtension(const char *inExt);
+ static bool IsMaterialFileExtension(const char *inExt);
+ static bool IsSoundFileExtension(const char *inExt);
+ static bool isPresentationFileExtension(const char *inExt);
+ static bool isMeshFileExtension(const char *inExt);
+ static bool isImportFileExtension(const char *inExt);
+ static bool isProjectFileExtension(const char *inExt);
+
+ static const wchar_t *GetWideDAEFileExtension();
+ static const wchar_t *GetWideFbxFileExtension();
+ static const wchar_t *GetWideImportFileExtension();
+ static const wchar_t *GetWideMeshFileExtension();
+ static const wchar_t **GetWideFontFileExtensions();
+ static const wchar_t **GetWideImgFileExtensions();
+ static const wchar_t **GetWideEffectFileExtensions();
+ static const wchar_t **GetWideMaterialFileExtensions();
+ static const wchar_t **GetWideSoundFileExtensions();
+ static bool IsImageFileExtension(const wchar_t *inExt);
+ static bool IsFontFileExtension(const wchar_t *inExt);
+ static bool IsEffectFileExtension(const wchar_t *inExt);
+ static bool IsMaterialFileExtension(const wchar_t *inExt);
+ static bool IsPathFileExtension(const wchar_t *inExt);
+ static bool IsPathBufferExtension(const wchar_t *inExt);
+ static bool IsSoundFileExtension(const wchar_t *inExt);
+ static bool isPresentationFileExtension(const wchar_t *inExt);
+ static bool isProjectFileExtension(const wchar_t *inExt);
+
+ Qt3DSFile GetExportChoice(const Q3DStudio::CString &inExtension,
+ const Q3DStudio::CString &inDefaultName);
+
+ QString GetSaveAsChoice(const QString &inDialogTitle = {}, bool isProject = false,
+ bool isCopy = false);
+ QString GetNewDocumentChoice(const QString &inInitialDirectory = {}, bool isProject = true);
+ QString GetFileOpenChoice(const QString &inInitialDirectory = {});
+ QString getExportVariantsDlg();
+ QString getImportVariantsDlg();
+
+ void DisplayImportFailed(const QUrl &inURL, const QString &inDescription, bool inWarningsOnly);
+ void DisplayLoadingPresentationFailed(const QFileInfo &loadFileInfo,
+ const QString &loadFileName, const QString &errorText);
+ void DisplaySavingPresentationFailed();
+ void DisplaySaveReadOnlyFailed(const QString &inSavedLocation);
+ void DisplayObjectRenamed(const QString &origName, const QString &newName, bool async = false);
+ Qt3DSMessageBox::EMessageBoxReturn DisplayMessageBox(const QString &inTitle,
+ const QString &inText,
+ Qt3DSMessageBox::EMessageBoxIcon inIcon,
+ bool inShowCancel,
+ QWidget *parent = nullptr);
+ void asyncDisplayMessageBox(const QString &title, const QString &text,
+ Qt3DSMessageBox::EMessageBoxIcon icon, QWidget *parent = nullptr);
+ int displayOverrideAssetBox(const QString &assetPath);
+ int DisplayChoiceBox(const QString &inTitle, const QString &inText, int inIcon);
+ void DisplayKnownErrorDialog(const QString &inErrorText);
+ QColor displayColorDialog(const QColor &color, bool showAlpha = false) const;
+
+ ESavePromptResult PromptForSave();
+ bool PromptForKeyframeInterpolation(float &ioEaseIn, float &ioEaseOut);
+
+ bool ConfirmRevert();
+
+ void DisplayProgressScreen(const QString &inActionText,
+ const QString &inAdditionalText);
+ void DestroyProgressScreen();
+
+ void DisplayEnvironmentVariablesError(const Q3DStudio::CString &inErrorMessage);
+
+ void ResetSettings(const QString &inCurrentDocPath = {});
+
+ bool DisplayResetKeyframeValuesDlg();
+ void DisplayPasteFailed();
+
+ bool DisplayUndefinedDatainputDlg(
+ const QMultiMap<QString,
+ QPair<qt3dsdm::Qt3DSDMInstanceHandle,
+ qt3dsdm::Qt3DSDMPropertyHandle>> *map);
+
+ static void DisplayGLVersionError(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inMinVersion);
+ static void DisplayGLVersionWarning(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inRecommendedVersion);
+
+ void asyncDisplayTimeEditDialog(long time, IDoc *doc, long objectAssociation,
+ KeyframeManager *keyframesManager = nullptr) const;
+ void asyncDisplayDurationEditDialog(long startTime, long endTime,
+ ITimeChangeCallback *callback) const;
+
+ enum class WidgetBrowserAlign {
+ ComboBox,
+ ToolButton,
+ Center
+ };
+ static void showWidgetBrowser(QWidget *screenWidget, QWidget *browser, const QPoint &point,
+ WidgetBrowserAlign align = WidgetBrowserAlign::ComboBox,
+ QSize customSize = {});
+
+Q_SIGNALS:
+ void onColorChanged(const QColor &color);
+
+protected:
+ QString CreateAllowedTypesString(Q3DStudio::DocumentEditorFileType::Enum fileTypeFilter,
+ QString &outInitialFilter, bool forImport, bool exclusive);
+ static void DisplayGLVersionDialog(const Q3DStudio::CString &inGLVersion,
+ const Q3DStudio::CString &inRecommendedVersion,
+ bool inError);
+
+ CProgressView *m_ProgressPalette = nullptr;
+ bool m_ShowGUI = true;
+ QString m_LastSaveFile; // Path to the file was previously saved
+ QHash<QString, QString> m_defaultDirForSuffixMap;
+};
+#endif // INCLUDED_DIALOGS_H
diff --git a/src/Authoring/Qt3DStudio/Workspace/Views.cpp b/src/Authoring/Qt3DStudio/Workspace/Views.cpp
new file mode 100644
index 00000000..78ec4dd8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Workspace/Views.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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 "Views.h"
+#include "MainFrm.h"
+
+//=============================================================================
+/**
+ * Constructor
+ */
+CViews::CViews()
+ : m_mainFrame(nullptr)
+{
+}
+
+//=============================================================================
+/**
+ * Destructor
+ */
+CViews::~CViews()
+{
+}
+
+//=============================================================================
+/**
+ *
+ */
+void CViews::createViews(bool silent)
+{
+ // To create the main window, this code creates a new frame window
+ // object and then sets it as the application's main window object
+ m_mainFrame.reset(new CMainFrame);
+ if (!silent)
+ m_mainFrame->show();
+}
+
+//=============================================================================
+/**
+ * Register all Application specific shortcut keys.
+ * Used to map hotkeys that are active through the whole app, not just one
+ * view.
+ * @param inHotKeys the handler to register on.
+ */
+void CViews::registerGlobalKeyboardShortcuts(CHotKeys *inHotKeys, QWidget *actionParent)
+{
+ if (m_mainFrame)
+ m_mainFrame->RegisterGlobalKeyboardShortcuts(inHotKeys, actionParent);
+}
+
+CMainFrame *CViews::getMainFrame()
+{
+ return m_mainFrame.data();
+}
+
+//=============================================================================
+/**
+ *
+ */
+void CViews::recheckMainframeSizingMode()
+{
+ if (m_mainFrame != nullptr)
+ m_mainFrame->RecheckSizingMode();
+}
diff --git a/src/Authoring/Qt3DStudio/Workspace/Views/Views.h b/src/Authoring/Qt3DStudio/Workspace/Views/Views.h
new file mode 100644
index 00000000..67990e21
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/Workspace/Views/Views.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** 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 INCLUDED_VIEWS_H
+#define INCLUDED_VIEWS_H 1
+
+#pragma once
+
+#include <QtWidgets/qwidget.h>
+
+class CMainFrame;
+class CStudioApp;
+class CHotKeys;
+
+class CViews
+{
+public:
+ CViews();
+ virtual ~CViews();
+
+ void createViews(bool silent);
+
+ void registerGlobalKeyboardShortcuts(CHotKeys *inShortcutHandler, QWidget *actionParent);
+
+ CMainFrame *getMainFrame();
+
+ void recheckMainframeSizingMode();
+
+protected:
+ QScopedPointer<CMainFrame> m_mainFrame;
+};
+
+#endif // INCLUDED_VIEWS_H
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/DESCRIPTION.en_us.html b/src/Authoring/Qt3DStudio/fonts/titilliumweb/DESCRIPTION.en_us.html
new file mode 100644
index 00000000..99c6c3b9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/DESCRIPTION.en_us.html
@@ -0,0 +1,22 @@
+<p>Titillium is born inside the Accademia di Belle Arti di Urbino as a
+didactic project Course Type design of the Master of Visual Design Campi
+Visivi.</p>
+
+<p>The aim of the project is the creation of a collective fonts released under
+OFL. Each academic year, a dozen students work on the project, developing it
+further and solving problems. Any type designer interested in the amendment or
+revision of Titillium is invited to co-operate with us, or develop their own
+variants of the typeface according to the terms specified in the Open Font
+license. We also ask all graphic designers who use Titillium in their projects
+to <a href="mailto:segreteria@accademiadiurbino.it">email us some examples</a>
+of the typeface family in use, in order to prepare a case histories
+database.</p>
+
+<p>Three years after the birth of Titillium, the project is still evolving,
+and even we don’t know what it will become in the future.</p>
+
+<p>Special thanks go to:<br/>
+Prof. Luciano Perondi, design and curation<br/>
+Prof. Marcello Signorile, coordination<br/>
+Prof. Manuel Zanettin, web project supervision<br/>
+Diego Giusti, design of the first prototype</p> \ No newline at end of file
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/METADATA.pb b/src/Authoring/Qt3DStudio/fonts/titilliumweb/METADATA.pb
new file mode 100644
index 00000000..e4faa356
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/METADATA.pb
@@ -0,0 +1,107 @@
+name: "Titillium Web"
+designer: "Multiple Designers"
+license: "OFL"
+category: "SANS_SERIF"
+date_added: "2012-10-01"
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 200
+ filename: "TitilliumWeb-ExtraLight.ttf"
+ post_script_name: "TitilliumWeb-Thin"
+ full_name: "Titillium WebThin"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "italic"
+ weight: 200
+ filename: "TitilliumWeb-ExtraLightItalic.ttf"
+ post_script_name: "TitilliumWeb-ThinItalic"
+ full_name: "Titillium WebThin Italic"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 300
+ filename: "TitilliumWeb-Light.ttf"
+ post_script_name: "TitilliumWeb-Light"
+ full_name: "Titillium WebLight"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "italic"
+ weight: 300
+ filename: "TitilliumWeb-LightItalic.ttf"
+ post_script_name: "TitilliumWeb-LightItalic"
+ full_name: "Titillium WebLight Italic"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 400
+ filename: "TitilliumWeb-Regular.ttf"
+ post_script_name: "TitilliumWeb-Regular"
+ full_name: "Titillium Web"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "italic"
+ weight: 400
+ filename: "TitilliumWeb-Italic.ttf"
+ post_script_name: "TitilliumWeb-Italic"
+ full_name: "Titillium WebItalic"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 600
+ filename: "TitilliumWeb-SemiBold.ttf"
+ post_script_name: "TitilliumWeb-SemiBold"
+ full_name: "Titillium WebSemiBold"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "italic"
+ weight: 600
+ filename: "TitilliumWeb-SemiBoldItalic.ttf"
+ post_script_name: "TitilliumWeb-SemiBoldItalic"
+ full_name: "Titillium WebSemiBold Italic"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 700
+ filename: "TitilliumWeb-Bold.ttf"
+ post_script_name: "TitilliumWeb-Bold"
+ full_name: "Titillium WebBold"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "italic"
+ weight: 700
+ filename: "TitilliumWeb-BoldItalic.ttf"
+ post_script_name: "TitilliumWeb-BoldItalic"
+ full_name: "Titillium WebBold Italic"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+fonts {
+ name: "Titillium Web"
+ style: "normal"
+ weight: 900
+ filename: "TitilliumWeb-Black.ttf"
+ post_script_name: "TitilliumWeb-Black"
+ full_name: "Titillium WebBlack"
+ copyright: "Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino (titillium@campivisivi.net) and students of MA course of Visual design. Some rights reserved."
+}
+subsets: "menu"
+subsets: "latin"
+subsets: "latin-ext"
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/OFL.txt b/src/Authoring/Qt3DStudio/fonts/titilliumweb/OFL.txt
new file mode 100644
index 00000000..bbaa23a6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino and students of MA course of Visual design. Some rights reserved.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Black.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Black.ttf
new file mode 100644
index 00000000..fc5c4b50
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Black.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Bold.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Bold.ttf
new file mode 100644
index 00000000..0af0fe7d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Bold.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-BoldItalic.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-BoldItalic.ttf
new file mode 100644
index 00000000..77425eaa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-BoldItalic.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLight.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLight.ttf
new file mode 100644
index 00000000..2b506ef2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLight.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLightItalic.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLightItalic.ttf
new file mode 100644
index 00000000..c1be5ba1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-ExtraLightItalic.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Italic.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Italic.ttf
new file mode 100644
index 00000000..42f2c10f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Italic.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Light.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Light.ttf
new file mode 100644
index 00000000..ca679712
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Light.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-LightItalic.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-LightItalic.ttf
new file mode 100644
index 00000000..2ea724fe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-LightItalic.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Regular.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Regular.ttf
new file mode 100644
index 00000000..6da82193
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-Regular.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBold.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBold.ttf
new file mode 100644
index 00000000..dfdcdbea
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBold.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBoldItalic.ttf b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBoldItalic.ttf
new file mode 100644
index 00000000..b68a6693
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/fonts/titilliumweb/TitilliumWeb-SemiBoldItalic.ttf
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images.qrc b/src/Authoring/Qt3DStudio/images.qrc
new file mode 100644
index 00000000..f97f0c63
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images.qrc
@@ -0,0 +1,337 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/Action-MasterAction.png</file>
+ <file>images/Action-Trash-Disabled.png</file>
+ <file>images/Action-Trash-Disabled@2x.png</file>
+ <file>images/Action-Trash-Normal.png</file>
+ <file>images/Action-Trash-Normal@2x.png</file>
+ <file>images/add.png</file>
+ <file>images/add@2x.png</file>
+ <file>images/arrow.png</file>
+ <file>images/arrow@2x.png</file>
+ <file>images/arrow_down.png</file>
+ <file>images/arrow_down@2x.png</file>
+ <file>images/Asset-Light-Pick.png</file>
+ <file>images/Asset-Camera-Pick.png</file>
+ <file>images/Asset-Alias-Normal.png</file>
+ <file>images/Asset-Alias-Normal@2x.png</file>
+ <file>images/Asset-Camera-Normal.png</file>
+ <file>images/Asset-Camera-Normal@2x.png</file>
+ <file>images/Asset-Component-Normal.png</file>
+ <file>images/Asset-Component-Normal@2x.png</file>
+ <file>images/Asset-Cone-Normal.png</file>
+ <file>images/Asset-Cone-Normal@2x.png</file>
+ <file>images/Asset-Cube-Normal.png</file>
+ <file>images/Asset-Cube-Normal@2x.png</file>
+ <file>images/Asset-Cylinder-Normal.png</file>
+ <file>images/Asset-Cylinder-Normal@2x.png</file>
+ <file>images/Asset-Group-Normal.png</file>
+ <file>images/Asset-Group-Normal@2x.png</file>
+ <file>images/Asset-Layer-Normal.png</file>
+ <file>images/Asset-Layer-Normal@2x.png</file>
+ <file>images/Asset-Light-Normal.png</file>
+ <file>images/Asset-Light-Normal@2x.png</file>
+ <file>images/Asset-Rectangle-Normal.png</file>
+ <file>images/Asset-Rectangle-Normal@2x.png</file>
+ <file>images/Asset-Sphere-Normal.png</file>
+ <file>images/Asset-Sphere-Normal@2x.png</file>
+ <file>images/Asset-Text-Normal.png</file>
+ <file>images/Asset-Text-Normal@2x.png</file>
+ <file>images/checkbox-checked.png</file>
+ <file>images/checkbox-unchecked.png</file>
+ <file>images/Inspector-AnimateToggle-Active.png</file>
+ <file>images/Inspector-AnimateToggle-Active@2x.png</file>
+ <file>images/Inspector-AnimateToggle-Normal.png</file>
+ <file>images/Inspector-AnimateToggle-Normal@2x.png</file>
+ <file>images/Objects-Behavior-Normal.png</file>
+ <file>images/Objects-Effect-Normal.png</file>
+ <file>images/Objects-Folder-Normal.png</file>
+ <file>images/Objects-Image-Normal.png</file>
+ <file>images/Objects-Layer-Normal.png</file>
+ <file>images/Objects-Layer-Normal@2x.png</file>
+ <file>images/Objects-Material-Normal.png</file>
+ <file>images/Objects-Model-Normal.png</file>
+ <file>images/Objects-Text-Normal.png</file>
+ <file>images/Objects-Text-Normal@2x.png</file>
+ <file>images/Slide-Active.png</file>
+ <file>images/Slide-Active@2x.png</file>
+ <file>images/Slide-Master-Active.png</file>
+ <file>images/Slide-Normal.png</file>
+ <file>images/Objects-Group-Normal.png</file>
+ <file>images/Objects-Group-Normal@2x.png</file>
+ <file>images/Objects-Light-Normal.png</file>
+ <file>images/Objects-Light-Normal@2x.png</file>
+ <file>images/Objects-Property-Normal.png</file>
+ <file>images/Objects-Property-Normal@2x.png</file>
+ <file>images/filter-shy-down.png</file>
+ <file>images/filter-shy-down@2x.png</file>
+ <file>images/filter-toggle-eye-down.png</file>
+ <file>images/filter-toggle-eye-down@2x.png</file>
+ <file>images/filter-toggle-eye-up.png</file>
+ <file>images/filter-toggle-eye-up@2x.png</file>
+ <file>images/PlaybackHead.png</file>
+ <file>images/PlaybackHead@2x.png</file>
+ <file>images/Toggle-HideShow.png</file>
+ <file>images/Toggle-HideShow@2x.png</file>
+ <file>images/obsolete_placeholder.png</file>
+ <file>images/empty-pixel.png</file>
+ <file>images/Toggle-Lock.png</file>
+ <file>images/Toggle-Lock@2x.png</file>
+ <file>images/Toggle-Shy.png</file>
+ <file>images/Toggle-Shy@2x.png</file>
+ <file>images/Toggle-Empty.png</file>
+ <file>images/Objects-Scene-Normal.png</file>
+ <file>images/Objects-Scene-Normal@2x.png</file>
+ <file>images/Action-Action.png</file>
+ <file>images/Action-ChildAction.png</file>
+ <file>images/Objects-Camera-Normal.png</file>
+ <file>images/timebarhandle-disabled-left.png</file>
+ <file>images/timebarhandle-disabled-right.png</file>
+ <file>images/timebarhandle-left.png</file>
+ <file>images/timebarhandle-right.png</file>
+ <file>images/Toggle-HideShow-disabled.png</file>
+ <file>images/Toggle-HideShow-disabled@2x.png</file>
+ <file>images/Objects-Alias-Normal.png</file>
+ <file>images/Objects-Alias-Normal@2x.png</file>
+ <file>images/Objects-Behavior-Normal@2x.png</file>
+ <file>images/Objects-Camera-Normal@2x.png</file>
+ <file>images/Objects-Component-Normal.png</file>
+ <file>images/Objects-Component-Normal@2x.png</file>
+ <file>images/Objects-Effect-Normal@2x.png</file>
+ <file>images/Objects-Folder-Normal@2x.png</file>
+ <file>images/Objects-Image-Normal@2x.png</file>
+ <file>images/Objects-Material-Normal@2x.png</file>
+ <file>images/Objects-Model-Normal@2x.png</file>
+ <file>images/Keyframe-MasterDynamic-Disabled.png</file>
+ <file>images/Keyframe-MasterDynamic-Normal.png</file>
+ <file>images/Keyframe-MasterDynamic-Selected.png</file>
+ <file>images/Keyframe-Master-Disabled.png</file>
+ <file>images/Keyframe-Master-Normal.png</file>
+ <file>images/Keyframe-Master-Selected.png</file>
+ <file>images/Keyframe-PropertyDynamic-Disabled.png</file>
+ <file>images/Keyframe-PropertyDynamic-Normal.png</file>
+ <file>images/Keyframe-PropertyDynamic-Selected.png</file>
+ <file>images/Keyframe-Property-Disabled.png</file>
+ <file>images/Keyframe-Property-Normal.png</file>
+ <file>images/Keyframe-Property-Selected.png</file>
+ <file>images/keyframe-hidden-normal.png</file>
+ <file>images/Objects-Property-Disabled.png</file>
+ <file>images/Objects-Property-Disabled@2x.png</file>
+ <file>images/Action-ChildMasterAction.png</file>
+ <file>images/Action-ComponentAction.png</file>
+ <file>images/Action-ComponentMasterAction.png</file>
+ <file>images/breadcrumb_component_button.png</file>
+ <file>images/breadcrumb_component_colon_button.png</file>
+ <file>images/breadcrumb_component_grey_button.png</file>
+ <file>images/breadcrumb_component_scene.png</file>
+ <file>images/Insert-Left.png</file>
+ <file>images/Insert-Rearrange-Left.png</file>
+ <file>images/Insert-Rearrange-Right.png</file>
+ <file>images/Insert-Right.png</file>
+ <file>images/Objects-Alias-Disabled.png</file>
+ <file>images/Objects-Behavior-Disabled.png</file>
+ <file>images/Objects-Camera-Disabled.png</file>
+ <file>images/Objects-Component-Disabled.png</file>
+ <file>images/Objects-Effect-Disabled.png</file>
+ <file>images/Objects-Group-Disabled.png</file>
+ <file>images/Objects-Group-Disabled@2x.png</file>
+ <file>images/Objects-Image-Disabled.png</file>
+ <file>images/Objects-Layer-Disabled.png</file>
+ <file>images/Objects-Layer-Disabled@2x.png</file>
+ <file>images/Objects-Light-Disabled.png</file>
+ <file>images/Objects-Light-Disabled@2x.png</file>
+ <file>images/Objects-Material-Disabled.png</file>
+ <file>images/Objects-Model-Disabled.png</file>
+ <file>images/Objects-Scene-Disabled.png</file>
+ <file>images/Objects-Text-Disabled.png</file>
+ <file>images/Objects-Alias-Disabled@2x.png</file>
+ <file>images/Objects-Behavior-Disabled@2x.png</file>
+ <file>images/Objects-Camera-Disabled@2x.png</file>
+ <file>images/Objects-Component-Disabled@2x.png</file>
+ <file>images/Objects-Effect-Disabled@2x.png</file>
+ <file>images/Objects-Folder-Disabled.png</file>
+ <file>images/Objects-Folder-Disabled@2x.png</file>
+ <file>images/Objects-Image-Disabled@2x.png</file>
+ <file>images/Objects-Material-Disabled@2x.png</file>
+ <file>images/Objects-Model-Disabled@2x.png</file>
+ <file>images/Objects-Scene-Disabled@2x.png</file>
+ <file>images/Objects-Text-Disabled@2x.png</file>
+ <file>images/Slide-Master-Active@2x.png</file>
+ <file>images/Slide-Normal@2x.png</file>
+ <file>images/breadcrumb_component_button@2x.png</file>
+ <file>images/breadcrumb_component_colon_button@2x.png</file>
+ <file>images/breadcrumb_component_grey_button@2x.png</file>
+ <file>images/breadcrumb_component_scene@2x.png</file>
+ <file>images/checkbox-checked@2x.png</file>
+ <file>images/checkbox-unchecked@2x.png</file>
+ <file>images/anim_progress.png</file>
+ <file>images/anim_progress@2x.png</file>
+ <file>images/prefstab-00.png</file>
+ <file>images/prefstab-01.png</file>
+ <file>images/client_tools_hi_color-00.png</file>
+ <file>images/client_tools_hi_color-01.png</file>
+ <file>images/client_tools_hi_color-02.png</file>
+ <file>images/client_tools_hi_color-03.png</file>
+ <file>images/client_tools_hi_color-04.png</file>
+ <file>images/client_tools_hi_color-05.png</file>
+ <file>images/client_tools_hi_color-06.png</file>
+ <file>images/editcamera_tools_hi-00.png</file>
+ <file>images/editcamera_tools_hi-01.png</file>
+ <file>images/editcamera_tools_hi-02.png</file>
+ <file>images/editcamera_tools_hi-03.png</file>
+ <file>images/editcamera_tools_hi-04.png</file>
+ <file>images/editcamera_tools_hi-05.png</file>
+ <file>images/playback_tools_first.png</file>
+ <file>images/playback_tools_first@2x.png</file>
+ <file>images/playback_tools_last.png</file>
+ <file>images/playback_tools_last@2x.png</file>
+ <file>images/playback_tools_play.png</file>
+ <file>images/playback_tools_play@2x.png</file>
+ <file>images/playback_tools_stop.png</file>
+ <file>images/playback_tools_stop@2x.png</file>
+ <file>images/preview.png</file>
+ <file>images/preview@2x.png</file>
+ <file>images/preview-disabled.png</file>
+ <file>images/preview-disabled@2x.png</file>
+ <file>images/preview-variants.png</file>
+ <file>images/preview-variants@2x.png</file>
+ <file>images/preview-remote.png</file>
+ <file>images/preview-remote@2x.png</file>
+ <file>images/preview-remote-disabled.png</file>
+ <file>images/preview-remote-disabled@2x.png</file>
+ <file>images/Toolbar-00.png</file>
+ <file>images/Toolbar-01.png</file>
+ <file>images/Toolbar-02.png</file>
+ <file>images/Toolbar-03.png</file>
+ <file>images/Toolbar-04.png</file>
+ <file>images/Toolbar-05.png</file>
+ <file>images/Toolbar-06.png</file>
+ <file>images/Toolbar-07.png</file>
+ <file>images/About Icon.bmp</file>
+ <file>images/icon_256x256.png</file>
+ <file>images/icon_256x256@2x.png</file>
+ <file>images/separator.png</file>
+ <file>images/editcamera_tools_hi-00_disabled.png</file>
+ <file>images/editcamera_tools_hi-01_disabled.png</file>
+ <file>images/editcamera_tools_hi-02_disabled.png</file>
+ <file>images/editcamera_tools_hi-03_disabled.png</file>
+ <file>images/editcamera_tools_hi-04_disabled.png</file>
+ <file>images/editcamera_tools_hi-05_disabled.png</file>
+ <file>images/editcamera_tools_hi-00@2x.png</file>
+ <file>images/editcamera_tools_hi-00_disabled@2x.png</file>
+ <file>images/editcamera_tools_hi-01@2x.png</file>
+ <file>images/editcamera_tools_hi-01_disabled@2x.png</file>
+ <file>images/editcamera_tools_hi-02@2x.png</file>
+ <file>images/editcamera_tools_hi-02_disabled@2x.png</file>
+ <file>images/editcamera_tools_hi-03@2x.png</file>
+ <file>images/editcamera_tools_hi-03_disabled@2x.png</file>
+ <file>images/editcamera_tools_hi-04@2x.png</file>
+ <file>images/editcamera_tools_hi-04_disabled@2x.png</file>
+ <file>images/editcamera_tools_hi-05@2x.png</file>
+ <file>images/editcamera_tools_hi-05_disabled@2x.png</file>
+ <file>images/prefstab-01@2x.png</file>
+ <file>images/separator@2x.png</file>
+ <file>images/client_tools_hi_color-00@2x.png</file>
+ <file>images/client_tools_hi_color-01@2x.png</file>
+ <file>images/client_tools_hi_color-02@2x.png</file>
+ <file>images/client_tools_hi_color-03@2x.png</file>
+ <file>images/client_tools_hi_color-04@2x.png</file>
+ <file>images/client_tools_hi_color-05@2x.png</file>
+ <file>images/client_tools_hi_color-06@2x.png</file>
+ <file>images/prefstab-00@2x.png</file>
+ <file>images/Objects-edit-disabled.png</file>
+ <file>images/Objects-edit-disabled@2x.png</file>
+ <file>images/Objects-edit-normal.png</file>
+ <file>images/Objects-edit-normal@2x.png</file>
+ <file>images/Objects-DataInput-Active.png</file>
+ <file>images/Objects-DataInput-Active@2x.png</file>
+ <file>images/Objects-DataInput-Inactive.png</file>
+ <file>images/Objects-DataInput-Inactive@2x.png</file>
+ <file>images/Objects-DataInput-White.png</file>
+ <file>images/Objects-DataInput-White@2x.png</file>
+ <file>images/Asset-import-Normal.png</file>
+ <file>images/Asset-import-Normal@2x.png</file>
+ <file>images/arrow_up.png</file>
+ <file>images/arrow_up@2x.png</file>
+ <file>images/separator-vertical.png</file>
+ <file>images/separator-vertical@2x.png</file>
+ <file>images/toolbutton-arrow.png</file>
+ <file>images/add-disabled.png</file>
+ <file>images/add-disabled@2x.png</file>
+ <file>images/zoom_out.png</file>
+ <file>images/zoom_out@2x.png</file>
+ <file>images/zoom_in.png</file>
+ <file>images/zoom_in@2x.png</file>
+ <file>images/Assets-Model.png</file>
+ <file>images/Assets-Model@2x.png</file>
+ <file>images/presentation_edit.png</file>
+ <file>images/presentation_edit@2x.png</file>
+ <file>images/presentation_notUsed.png</file>
+ <file>images/presentation_notUsed@2x.png</file>
+ <file>images/presentation_used.png</file>
+ <file>images/presentation_used@2x.png</file>
+ <file>images/qml_notUsed.png</file>
+ <file>images/qml_notUsed@2x.png</file>
+ <file>images/qml_used.png</file>
+ <file>images/qml_used@2x.png</file>
+ <file>images/Toggle-HideShowControlled.png</file>
+ <file>images/Toggle-HideShowControlled@2x.png</file>
+ <file>images/welcomedialog/laptop.png</file>
+ <file>images/initial_notUsed.png</file>
+ <file>images/initial_notUsed@2x.png</file>
+ <file>images/initial_used.png</file>
+ <file>images/initial_used@2x.png</file>
+ <file>images/warning.png</file>
+ <file>images/warning2x.png</file>
+ <file>images/timeline_text_shown.png</file>
+ <file>images/timeline_text_shown@2x.png</file>
+ <file>images/timeline_text_hidden.png</file>
+ <file>images/timeline_text_hidden@2x.png</file>
+ <file>images/matdef-active.png</file>
+ <file>images/matdef-active@2x.png</file>
+ <file>images/matdef-disabled.png</file>
+ <file>images/matdef-disabled@2x.png</file>
+ <file>images/Action-Action@2x.png</file>
+ <file>images/Action-ChildAction@2x.png</file>
+ <file>images/Action-ChildMasterAction@2x.png</file>
+ <file>images/Action-ComponentAction@2x.png</file>
+ <file>images/Action-ComponentMasterAction@2x.png</file>
+ <file>images/Action-MasterAction@2x.png</file>
+ <file>images/Insert-Left@2x.png</file>
+ <file>images/Insert-Right@2x.png</file>
+ <file>images/Toggle-Empty@2x.png</file>
+ <file>images/keyframe-hidden-normal@2x.png</file>
+ <file>images/Keyframe-MasterDynamic-Selected@2x.png</file>
+ <file>images/Keyframe-MasterDynamic-Normal@2x.png</file>
+ <file>images/Keyframe-MasterDynamic-Disabled@2x.png</file>
+ <file>images/Keyframe-Master-Selected@2x.png</file>
+ <file>images/Keyframe-Master-Normal@2x.png</file>
+ <file>images/Keyframe-Master-Disabled@2x.png</file>
+ <file>images/Keyframe-PropertyDynamic-Selected@2x.png</file>
+ <file>images/Keyframe-PropertyDynamic-Normal@2x.png</file>
+ <file>images/Keyframe-PropertyDynamic-Disabled@2x.png</file>
+ <file>images/Keyframe-Property-Selected@2x.png</file>
+ <file>images/Keyframe-Property-Normal@2x.png</file>
+ <file>images/Keyframe-Property-Disabled@2x.png</file>
+ <file>images/filter.png</file>
+ <file>images/filter@2x.png</file>
+ <file>images/filter-disabled.png</file>
+ <file>images/filter-disabled@2x.png</file>
+ <file>images/filter-colored.png</file>
+ <file>images/filter-colored@2x.png</file>
+ </qresource>
+ <qresource prefix="/startup">
+ <file alias="open_dialog.png">images/open_dialog.png</file>
+ </qresource>
+ <qresource prefix="/cursors">
+ <file alias="edit_camera_rot.png">images/edit_camera_rot.png</file>
+ <file alias="edit_camera_pan.png">images/edit_camera_pan.png</file>
+ <file alias="edit_camera_zoom.png">images/edit_camera_zoom.png</file>
+ <file alias="group_move.png">images/group_move.png</file>
+ <file alias="group_rotate.png">images/group_rotate.png</file>
+ <file alias="group_scale.png">images/group_scale.png</file>
+ <file alias="item_move.png">images/item_move.png</file>
+ <file alias="item_rotate.png">images/item_rotate.png</file>
+ <file alias="item_scale.png">images/item_scale.png</file>
+ </qresource>
+</RCC>
diff --git a/src/Authoring/Qt3DStudio/images/3D-studio.ico b/src/Authoring/Qt3DStudio/images/3D-studio.ico
new file mode 100644
index 00000000..88fb4ba0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/3D-studio.ico
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/About Icon.bmp b/src/Authoring/Qt3DStudio/images/About Icon.bmp
new file mode 100644
index 00000000..97764f42
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/About Icon.bmp
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Action.png b/src/Authoring/Qt3DStudio/images/Action-Action.png
new file mode 100644
index 00000000..4b8d9c8e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Action.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Action@2x.png b/src/Authoring/Qt3DStudio/images/Action-Action@2x.png
new file mode 100644
index 00000000..affa6c01
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Action@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ChildAction.png b/src/Authoring/Qt3DStudio/images/Action-ChildAction.png
new file mode 100644
index 00000000..6589b104
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ChildAction.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ChildAction@2x.png b/src/Authoring/Qt3DStudio/images/Action-ChildAction@2x.png
new file mode 100644
index 00000000..ba066860
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ChildAction@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction.png b/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction.png
new file mode 100644
index 00000000..39159191
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction@2x.png b/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction@2x.png
new file mode 100644
index 00000000..f0c8a1da
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ChildMasterAction@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ComponentAction.png b/src/Authoring/Qt3DStudio/images/Action-ComponentAction.png
new file mode 100644
index 00000000..d7287f46
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ComponentAction.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ComponentAction@2x.png b/src/Authoring/Qt3DStudio/images/Action-ComponentAction@2x.png
new file mode 100644
index 00000000..26a2c36c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ComponentAction@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction.png b/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction.png
new file mode 100644
index 00000000..bb34a3da
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction@2x.png b/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction@2x.png
new file mode 100644
index 00000000..fa40fb4b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-ComponentMasterAction@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-MasterAction.png b/src/Authoring/Qt3DStudio/images/Action-MasterAction.png
new file mode 100644
index 00000000..74f06661
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-MasterAction.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-MasterAction@2x.png b/src/Authoring/Qt3DStudio/images/Action-MasterAction@2x.png
new file mode 100644
index 00000000..c3b8e2e5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-MasterAction@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled.png b/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled.png
new file mode 100644
index 00000000..c175290d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled@2x.png
new file mode 100644
index 00000000..2793f0c2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Trash-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Trash-Normal.png b/src/Authoring/Qt3DStudio/images/Action-Trash-Normal.png
new file mode 100644
index 00000000..2b6ef815
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Trash-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Action-Trash-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Action-Trash-Normal@2x.png
new file mode 100644
index 00000000..8a8fa0c1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Action-Trash-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal.png
new file mode 100644
index 00000000..4a40aecf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal@2x.png
new file mode 100644
index 00000000..6b14d31f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Alias-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal.png
new file mode 100644
index 00000000..faa642c2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal@2x.png
new file mode 100644
index 00000000..d69675e8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Camera-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Camera-Pick.png b/src/Authoring/Qt3DStudio/images/Asset-Camera-Pick.png
new file mode 100644
index 00000000..935650c4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Camera-Pick.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Component-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Component-Normal.png
new file mode 100644
index 00000000..9b1fc237
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Component-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Component-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Component-Normal@2x.png
new file mode 100644
index 00000000..45b10cb5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Component-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal.png
new file mode 100644
index 00000000..5b42abe9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal@2x.png
new file mode 100644
index 00000000..4edac2fa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cone-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal.png
new file mode 100644
index 00000000..c858a011
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal@2x.png
new file mode 100644
index 00000000..07bfc514
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cube-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal.png
new file mode 100644
index 00000000..99b8b418
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal@2x.png
new file mode 100644
index 00000000..887855e9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Cylinder-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Group-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Group-Normal.png
new file mode 100644
index 00000000..4575da6e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Group-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Group-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Group-Normal@2x.png
new file mode 100644
index 00000000..162c008b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Group-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal.png
new file mode 100644
index 00000000..e11e3d26
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal@2x.png
new file mode 100644
index 00000000..d479fca7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Layer-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Light-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Light-Normal.png
new file mode 100644
index 00000000..47bc8d89
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Light-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Light-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Light-Normal@2x.png
new file mode 100644
index 00000000..d36e0bf8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Light-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Light-Pick.png b/src/Authoring/Qt3DStudio/images/Asset-Light-Pick.png
new file mode 100644
index 00000000..c21790e1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Light-Pick.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal.png
new file mode 100644
index 00000000..dd904770
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal@2x.png
new file mode 100644
index 00000000..1ad5bc31
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Rectangle-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal.png
new file mode 100644
index 00000000..c53885ba
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal@2x.png
new file mode 100644
index 00000000..e14312f1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Sphere-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Text-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-Text-Normal.png
new file mode 100644
index 00000000..0ae00949
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Text-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-Text-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-Text-Normal@2x.png
new file mode 100644
index 00000000..734900e8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-Text-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-import-Normal.png b/src/Authoring/Qt3DStudio/images/Asset-import-Normal.png
new file mode 100644
index 00000000..a5337aec
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-import-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Asset-import-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Asset-import-Normal@2x.png
new file mode 100644
index 00000000..f161890a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Asset-import-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Assets-Model.png b/src/Authoring/Qt3DStudio/images/Assets-Model.png
new file mode 100644
index 00000000..c7185436
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Assets-Model.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Assets-Model@2x.png b/src/Authoring/Qt3DStudio/images/Assets-Model@2x.png
new file mode 100644
index 00000000..1d54f012
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Assets-Model@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Left.png b/src/Authoring/Qt3DStudio/images/Insert-Left.png
new file mode 100644
index 00000000..155127e9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Left.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Left@2x.png b/src/Authoring/Qt3DStudio/images/Insert-Left@2x.png
new file mode 100644
index 00000000..ad0696c6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Left@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Left.png b/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Left.png
new file mode 100644
index 00000000..7a0d2703
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Left.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Right.png b/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Right.png
new file mode 100644
index 00000000..58bbc0c8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Rearrange-Right.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Right.png b/src/Authoring/Qt3DStudio/images/Insert-Right.png
new file mode 100644
index 00000000..affd937c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Right.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Insert-Right@2x.png b/src/Authoring/Qt3DStudio/images/Insert-Right@2x.png
new file mode 100644
index 00000000..d7a20850
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Insert-Right@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active.png b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active.png
new file mode 100644
index 00000000..1ed0e731
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active@2x.png b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active@2x.png
new file mode 100644
index 00000000..ef83fea8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Active@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal.png b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal.png
new file mode 100644
index 00000000..ff00f71c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal@2x.png
new file mode 100644
index 00000000..dc1cee05
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Inspector-AnimateToggle-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled.png
new file mode 100644
index 00000000..41c7bbe3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled@2x.png
new file mode 100644
index 00000000..a1291e92
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal.png
new file mode 100644
index 00000000..af88c894
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal@2x.png
new file mode 100644
index 00000000..503942c8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected.png
new file mode 100644
index 00000000..7e27743c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected@2x.png
new file mode 100644
index 00000000..e86bacf5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Master-Selected@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled.png
new file mode 100644
index 00000000..d6abe9f1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled@2x.png
new file mode 100644
index 00000000..0b90181d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal.png
new file mode 100644
index 00000000..6d6dab76
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal@2x.png
new file mode 100644
index 00000000..4a1f83fc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected.png
new file mode 100644
index 00000000..6e00aeba
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected@2x.png
new file mode 100644
index 00000000..c402defc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-MasterDynamic-Selected@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled.png
new file mode 100644
index 00000000..2bba3f4b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled@2x.png
new file mode 100644
index 00000000..957fb36e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal.png
new file mode 100644
index 00000000..aaebda90
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal@2x.png
new file mode 100644
index 00000000..06919fa3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected.png
new file mode 100644
index 00000000..456a4b90
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected@2x.png
new file mode 100644
index 00000000..b2008a29
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-Property-Selected@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled.png
new file mode 100644
index 00000000..84263b0d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled@2x.png
new file mode 100644
index 00000000..229fddf2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal.png
new file mode 100644
index 00000000..733e0718
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal@2x.png
new file mode 100644
index 00000000..aa24ccac
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected.png
new file mode 100644
index 00000000..ef1cf1bd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected@2x.png b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected@2x.png
new file mode 100644
index 00000000..ad155725
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Keyframe-PropertyDynamic-Selected@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled.png
new file mode 100644
index 00000000..85e6dd94
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled@2x.png
new file mode 100644
index 00000000..6f9dffc3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Alias-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal.png
new file mode 100644
index 00000000..c97c38f2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal@2x.png
new file mode 100644
index 00000000..620608d8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Alias-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled.png
new file mode 100644
index 00000000..a7c71f93
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled@2x.png
new file mode 100644
index 00000000..5ded4633
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal.png
new file mode 100644
index 00000000..0aa09e8f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal@2x.png
new file mode 100644
index 00000000..aa65ef2d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Behavior-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled.png
new file mode 100644
index 00000000..5d87e29a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled@2x.png
new file mode 100644
index 00000000..47efdc5c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Camera-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal.png
new file mode 100644
index 00000000..69f6e385
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal@2x.png
new file mode 100644
index 00000000..2c8bc946
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Camera-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled.png
new file mode 100644
index 00000000..f417a7a2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled@2x.png
new file mode 100644
index 00000000..bfb5d2d2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Component-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Component-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Component-Normal.png
new file mode 100644
index 00000000..c95f7b4d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Component-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Component-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Component-Normal@2x.png
new file mode 100644
index 00000000..e852f0a7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Component-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active.png
new file mode 100644
index 00000000..cef46028
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active@2x.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active@2x.png
new file mode 100644
index 00000000..94330a8e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Active@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive.png
new file mode 100644
index 00000000..6bed14ee
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive@2x.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive@2x.png
new file mode 100644
index 00000000..b04b43b1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-Inactive@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-White.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-White.png
new file mode 100644
index 00000000..f97b5a80
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-White.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-DataInput-White@2x.png b/src/Authoring/Qt3DStudio/images/Objects-DataInput-White@2x.png
new file mode 100644
index 00000000..21905429
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-DataInput-White@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled.png
new file mode 100644
index 00000000..0b0bde6e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled@2x.png
new file mode 100644
index 00000000..34eb98d8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Effect-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal.png
new file mode 100644
index 00000000..d54948a4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal@2x.png
new file mode 100644
index 00000000..c2edac40
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Effect-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled.png
new file mode 100644
index 00000000..67585cd3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled@2x.png
new file mode 100644
index 00000000..fc1fadc8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Folder-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal.png
new file mode 100644
index 00000000..2cc95c46
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal@2x.png
new file mode 100644
index 00000000..44c2e3b6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Folder-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled.png
new file mode 100644
index 00000000..0e4e6eaf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled@2x.png
new file mode 100644
index 00000000..2feb6c6b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Group-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Group-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Group-Normal.png
new file mode 100644
index 00000000..3c22bb61
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Group-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Group-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Group-Normal@2x.png
new file mode 100644
index 00000000..286be9b8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Group-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled.png
new file mode 100644
index 00000000..34654dae
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled@2x.png
new file mode 100644
index 00000000..2f1dfd5e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Image-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Image-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Image-Normal.png
new file mode 100644
index 00000000..3d657b8a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Image-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Image-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Image-Normal@2x.png
new file mode 100644
index 00000000..c99df5d2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Image-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled.png
new file mode 100644
index 00000000..c52e0340
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled@2x.png
new file mode 100644
index 00000000..6fa6f7aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Layer-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal.png
new file mode 100644
index 00000000..d06bb2cd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal@2x.png
new file mode 100644
index 00000000..532764ab
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Layer-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled.png
new file mode 100644
index 00000000..9fea9d3f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled@2x.png
new file mode 100644
index 00000000..d476c9b3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Light-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Light-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Light-Normal.png
new file mode 100644
index 00000000..68fec179
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Light-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Light-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Light-Normal@2x.png
new file mode 100644
index 00000000..4b627f1d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Light-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled.png
new file mode 100644
index 00000000..ec77957f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled@2x.png
new file mode 100644
index 00000000..f9b216d0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Material-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Material-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Material-Normal.png
new file mode 100644
index 00000000..225bb4b5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Material-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Material-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Material-Normal@2x.png
new file mode 100644
index 00000000..d51fd360
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Material-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled.png
new file mode 100644
index 00000000..ead6ede5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled@2x.png
new file mode 100644
index 00000000..8c0f635a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Model-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Model-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Model-Normal.png
new file mode 100644
index 00000000..9e066414
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Model-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Model-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Model-Normal@2x.png
new file mode 100644
index 00000000..075663bf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Model-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled.png
new file mode 100644
index 00000000..84c55833
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled@2x.png
new file mode 100644
index 00000000..15428fe9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Property-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Property-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Property-Normal.png
new file mode 100644
index 00000000..a75d0561
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Property-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Property-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Property-Normal@2x.png
new file mode 100644
index 00000000..e1cf503a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Property-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled.png
new file mode 100644
index 00000000..a4aba973
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled@2x.png
new file mode 100644
index 00000000..2d21542a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Scene-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal.png
new file mode 100644
index 00000000..56253242
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal@2x.png
new file mode 100644
index 00000000..c963c77b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Scene-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled.png b/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled.png
new file mode 100644
index 00000000..f8ba1b9a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled@2x.png
new file mode 100644
index 00000000..22cec550
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Text-Disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Text-Normal.png b/src/Authoring/Qt3DStudio/images/Objects-Text-Normal.png
new file mode 100644
index 00000000..0ae00949
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Text-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-Text-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-Text-Normal@2x.png
new file mode 100644
index 00000000..734900e8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-Text-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-edit-disabled.png b/src/Authoring/Qt3DStudio/images/Objects-edit-disabled.png
new file mode 100644
index 00000000..b0f64166
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-edit-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-edit-disabled@2x.png b/src/Authoring/Qt3DStudio/images/Objects-edit-disabled@2x.png
new file mode 100644
index 00000000..3f970328
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-edit-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-edit-normal.png b/src/Authoring/Qt3DStudio/images/Objects-edit-normal.png
new file mode 100644
index 00000000..e83fb4d5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-edit-normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-edit-normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-edit-normal@2x.png
new file mode 100644
index 00000000..e47be32b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-edit-normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal.png b/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal.png
new file mode 100644
index 00000000..956dd915
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal@2x.png b/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal@2x.png
new file mode 100644
index 00000000..3cc2c328
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Objects-manageInputs-normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/PlaybackHead.png b/src/Authoring/Qt3DStudio/images/PlaybackHead.png
new file mode 100644
index 00000000..ce001ad2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/PlaybackHead.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/PlaybackHead@2x.png b/src/Authoring/Qt3DStudio/images/PlaybackHead@2x.png
new file mode 100644
index 00000000..f1a579a7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/PlaybackHead@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Active.png b/src/Authoring/Qt3DStudio/images/Slide-Active.png
new file mode 100644
index 00000000..8ee8a8bd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Active.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Active@2x.png b/src/Authoring/Qt3DStudio/images/Slide-Active@2x.png
new file mode 100644
index 00000000..8c2031e1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Active@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Master-Active.png b/src/Authoring/Qt3DStudio/images/Slide-Master-Active.png
new file mode 100644
index 00000000..0672c3c0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Master-Active.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Master-Active@2x.png b/src/Authoring/Qt3DStudio/images/Slide-Master-Active@2x.png
new file mode 100644
index 00000000..43baf38f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Master-Active@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Normal.png b/src/Authoring/Qt3DStudio/images/Slide-Normal.png
new file mode 100644
index 00000000..bb44401c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Slide-Normal@2x.png b/src/Authoring/Qt3DStudio/images/Slide-Normal@2x.png
new file mode 100644
index 00000000..5f6bf90e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Slide-Normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Empty.png b/src/Authoring/Qt3DStudio/images/Toggle-Empty.png
new file mode 100644
index 00000000..3f6660c9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Empty.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Empty@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-Empty@2x.png
new file mode 100644
index 00000000..94c695c6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Empty@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled.png
new file mode 100644
index 00000000..fa28ed9c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled@2x.png
new file mode 100644
index 00000000..e75f821f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShow-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShow.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShow.png
new file mode 100644
index 00000000..bfa3c734
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShow.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShow@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShow@2x.png
new file mode 100644
index 00000000..5425b285
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShow@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled.png
new file mode 100644
index 00000000..e407e952
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled@2x.png
new file mode 100644
index 00000000..1d348efd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-HideShowControlled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Lock.png b/src/Authoring/Qt3DStudio/images/Toggle-Lock.png
new file mode 100644
index 00000000..627fa7f7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Lock.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Lock@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-Lock@2x.png
new file mode 100644
index 00000000..d1a6dfc4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Lock@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Shy.png b/src/Authoring/Qt3DStudio/images/Toggle-Shy.png
new file mode 100644
index 00000000..2b990ad3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Shy.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toggle-Shy@2x.png b/src/Authoring/Qt3DStudio/images/Toggle-Shy@2x.png
new file mode 100644
index 00000000..1c9fa9f4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toggle-Shy@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-00.png b/src/Authoring/Qt3DStudio/images/Toolbar-00.png
new file mode 100644
index 00000000..79bf70ff
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-00.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-00@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-00@2x.png
new file mode 100644
index 00000000..a4944029
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-00@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-01.png b/src/Authoring/Qt3DStudio/images/Toolbar-01.png
new file mode 100644
index 00000000..f4d66d13
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-01.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-01@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-01@2x.png
new file mode 100644
index 00000000..f11ee199
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-01@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-02.png b/src/Authoring/Qt3DStudio/images/Toolbar-02.png
new file mode 100644
index 00000000..2c93b72b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-02.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-02@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-02@2x.png
new file mode 100644
index 00000000..231c5075
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-02@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-03.png b/src/Authoring/Qt3DStudio/images/Toolbar-03.png
new file mode 100644
index 00000000..16e7e2bf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-03.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-03@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-03@2x.png
new file mode 100644
index 00000000..9c66d91c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-03@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-04.png b/src/Authoring/Qt3DStudio/images/Toolbar-04.png
new file mode 100644
index 00000000..c1028d6c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-04.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-04@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-04@2x.png
new file mode 100644
index 00000000..e39844aa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-04@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-05.png b/src/Authoring/Qt3DStudio/images/Toolbar-05.png
new file mode 100644
index 00000000..f7af0ab8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-05.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-05@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-05@2x.png
new file mode 100644
index 00000000..98d01960
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-05@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-06.png b/src/Authoring/Qt3DStudio/images/Toolbar-06.png
new file mode 100644
index 00000000..67564da5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-06.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-06@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-06@2x.png
new file mode 100644
index 00000000..14ef1d2a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-06@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-07.png b/src/Authoring/Qt3DStudio/images/Toolbar-07.png
new file mode 100644
index 00000000..bb5e7904
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-07.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/Toolbar-07@2x.png b/src/Authoring/Qt3DStudio/images/Toolbar-07@2x.png
new file mode 100644
index 00000000..d9254340
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/Toolbar-07@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/add-disabled.png b/src/Authoring/Qt3DStudio/images/add-disabled.png
new file mode 100644
index 00000000..f08f6721
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/add-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/add-disabled@2x.png b/src/Authoring/Qt3DStudio/images/add-disabled@2x.png
new file mode 100644
index 00000000..8ea5f501
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/add-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/add.png b/src/Authoring/Qt3DStudio/images/add.png
new file mode 100644
index 00000000..efd65386
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/add.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/add@2x.png b/src/Authoring/Qt3DStudio/images/add@2x.png
new file mode 100644
index 00000000..98fd8d80
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/add@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/anim_progress.png b/src/Authoring/Qt3DStudio/images/anim_progress.png
new file mode 100644
index 00000000..1bd3a396
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/anim_progress.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/anim_progress@2x.png b/src/Authoring/Qt3DStudio/images/anim_progress@2x.png
new file mode 100644
index 00000000..909b415f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/anim_progress@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow.png b/src/Authoring/Qt3DStudio/images/arrow.png
new file mode 100644
index 00000000..40ebda88
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow@2x.png b/src/Authoring/Qt3DStudio/images/arrow@2x.png
new file mode 100644
index 00000000..1a21ee06
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow_down.png b/src/Authoring/Qt3DStudio/images/arrow_down.png
new file mode 100644
index 00000000..238f8b6f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow_down.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow_down@2x.png b/src/Authoring/Qt3DStudio/images/arrow_down@2x.png
new file mode 100644
index 00000000..831189b6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow_down@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow_up.png b/src/Authoring/Qt3DStudio/images/arrow_up.png
new file mode 100644
index 00000000..284caf9e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow_up.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/arrow_up@2x.png b/src/Authoring/Qt3DStudio/images/arrow_up@2x.png
new file mode 100644
index 00000000..303fa926
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/arrow_up@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_button.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_button.png
new file mode 100644
index 00000000..7b4e4e4c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_button.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_button@2x.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_button@2x.png
new file mode 100644
index 00000000..abdba2d0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_button@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button.png
new file mode 100644
index 00000000..bf360165
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button@2x.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button@2x.png
new file mode 100644
index 00000000..cc253edd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_colon_button@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button.png
new file mode 100644
index 00000000..abd7e146
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button@2x.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button@2x.png
new file mode 100644
index 00000000..7d33555c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_grey_button@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene.png
new file mode 100644
index 00000000..4645f6da
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene@2x.png b/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene@2x.png
new file mode 100644
index 00000000..8a2d2c04
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/breadcrumb_component_scene@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/checkbox-checked.png b/src/Authoring/Qt3DStudio/images/checkbox-checked.png
new file mode 100644
index 00000000..5acdf1b1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/checkbox-checked.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/checkbox-checked@2x.png b/src/Authoring/Qt3DStudio/images/checkbox-checked@2x.png
new file mode 100644
index 00000000..a5b97c8e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/checkbox-checked@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/checkbox-unchecked.png b/src/Authoring/Qt3DStudio/images/checkbox-unchecked.png
new file mode 100644
index 00000000..9c2bcd6b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/checkbox-unchecked.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/checkbox-unchecked@2x.png b/src/Authoring/Qt3DStudio/images/checkbox-unchecked@2x.png
new file mode 100644
index 00000000..8d244d79
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/checkbox-unchecked@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00.png
new file mode 100644
index 00000000..f466a1c3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00@2x.png
new file mode 100644
index 00000000..0b68e6b3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-00@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01.png
new file mode 100644
index 00000000..aad1b000
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01@2x.png
new file mode 100644
index 00000000..e9589689
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-01@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02.png
new file mode 100644
index 00000000..871a5e7d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02@2x.png
new file mode 100644
index 00000000..235d6804
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-02@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03.png
new file mode 100644
index 00000000..68a7848a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03@2x.png
new file mode 100644
index 00000000..bc861678
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-03@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04.png
new file mode 100644
index 00000000..0a2a7bc9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04@2x.png
new file mode 100644
index 00000000..bdf10c1f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-04@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05.png
new file mode 100644
index 00000000..de315cab
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05@2x.png
new file mode 100644
index 00000000..89ba3fba
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-05@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06.png
new file mode 100644
index 00000000..eeb93638
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06@2x.png b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06@2x.png
new file mode 100644
index 00000000..5fe71abb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/client_tools_hi_color-06@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/edit_camera_pan.png b/src/Authoring/Qt3DStudio/images/edit_camera_pan.png
new file mode 100644
index 00000000..9248a875
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/edit_camera_pan.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/edit_camera_rot.png b/src/Authoring/Qt3DStudio/images/edit_camera_rot.png
new file mode 100644
index 00000000..55c6ddac
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/edit_camera_rot.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/edit_camera_zoom.png b/src/Authoring/Qt3DStudio/images/edit_camera_zoom.png
new file mode 100644
index 00000000..46f69c43
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/edit_camera_zoom.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00.png
new file mode 100644
index 00000000..ad07bf8f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00@2x.png
new file mode 100644
index 00000000..d8c5fc46
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled.png
new file mode 100644
index 00000000..f8499e9d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled@2x.png
new file mode 100644
index 00000000..96564a1c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-00_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01.png
new file mode 100644
index 00000000..10b16c6d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01@2x.png
new file mode 100644
index 00000000..b872a674
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled.png
new file mode 100644
index 00000000..ce5d85d2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled@2x.png
new file mode 100644
index 00000000..a4a93e53
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-01_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02.png
new file mode 100644
index 00000000..77b5a93e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02@2x.png
new file mode 100644
index 00000000..13b2ce61
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled.png
new file mode 100644
index 00000000..a21cea23
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled@2x.png
new file mode 100644
index 00000000..bba1b104
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-02_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03.png
new file mode 100644
index 00000000..c8929b76
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03@2x.png
new file mode 100644
index 00000000..26292608
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled.png
new file mode 100644
index 00000000..db8944b2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled@2x.png
new file mode 100644
index 00000000..15904fee
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-03_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04.png
new file mode 100644
index 00000000..22da1b6d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04@2x.png
new file mode 100644
index 00000000..9110519b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled.png
new file mode 100644
index 00000000..1d9e0ac0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled@2x.png
new file mode 100644
index 00000000..0a3f5527
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-04_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05.png
new file mode 100644
index 00000000..147f8e29
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05@2x.png
new file mode 100644
index 00000000..844a61e4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled.png
new file mode 100644
index 00000000..ee55b391
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled@2x.png b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled@2x.png
new file mode 100644
index 00000000..072be7ed
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/editcamera_tools_hi-05_disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/empty-pixel.png b/src/Authoring/Qt3DStudio/images/empty-pixel.png
new file mode 100644
index 00000000..0ae92115
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/empty-pixel.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-colored.png b/src/Authoring/Qt3DStudio/images/filter-colored.png
new file mode 100644
index 00000000..2a06e736
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-colored.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-colored@2x.png b/src/Authoring/Qt3DStudio/images/filter-colored@2x.png
new file mode 100644
index 00000000..20289b55
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-colored@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-disabled.png b/src/Authoring/Qt3DStudio/images/filter-disabled.png
new file mode 100644
index 00000000..019184cd
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-disabled@2x.png b/src/Authoring/Qt3DStudio/images/filter-disabled@2x.png
new file mode 100644
index 00000000..00a5040a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-shy-down.png b/src/Authoring/Qt3DStudio/images/filter-shy-down.png
new file mode 100644
index 00000000..2b990ad3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-shy-down.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-shy-down@2x.png b/src/Authoring/Qt3DStudio/images/filter-shy-down@2x.png
new file mode 100644
index 00000000..1c9fa9f4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-shy-down@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down.png b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down.png
new file mode 100644
index 00000000..fa28ed9c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down@2x.png b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down@2x.png
new file mode 100644
index 00000000..e75f821f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-down@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up.png b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up.png
new file mode 100644
index 00000000..bfa3c734
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up@2x.png b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up@2x.png
new file mode 100644
index 00000000..5425b285
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter-toggle-eye-up@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter.png b/src/Authoring/Qt3DStudio/images/filter.png
new file mode 100644
index 00000000..e1c04006
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/filter@2x.png b/src/Authoring/Qt3DStudio/images/filter@2x.png
new file mode 100644
index 00000000..f01cbab7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/filter@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/group_move.png b/src/Authoring/Qt3DStudio/images/group_move.png
new file mode 100644
index 00000000..d7d06770
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/group_move.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/group_rotate.png b/src/Authoring/Qt3DStudio/images/group_rotate.png
new file mode 100644
index 00000000..23a3eb3e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/group_rotate.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/group_scale.png b/src/Authoring/Qt3DStudio/images/group_scale.png
new file mode 100644
index 00000000..64024dfe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/group_scale.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/icon_256x256.png b/src/Authoring/Qt3DStudio/images/icon_256x256.png
new file mode 100644
index 00000000..bda35e5e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/icon_256x256.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/icon_256x256@2x.png b/src/Authoring/Qt3DStudio/images/icon_256x256@2x.png
new file mode 100644
index 00000000..909b95e2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/icon_256x256@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/initial_notUsed.png b/src/Authoring/Qt3DStudio/images/initial_notUsed.png
new file mode 100644
index 00000000..07f46e7a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/initial_notUsed.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/initial_notUsed@2x.png b/src/Authoring/Qt3DStudio/images/initial_notUsed@2x.png
new file mode 100644
index 00000000..83269db2
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/initial_notUsed@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/initial_used.png b/src/Authoring/Qt3DStudio/images/initial_used.png
new file mode 100644
index 00000000..457825f1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/initial_used.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/initial_used@2x.png b/src/Authoring/Qt3DStudio/images/initial_used@2x.png
new file mode 100644
index 00000000..bc563df0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/initial_used@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/item_move.png b/src/Authoring/Qt3DStudio/images/item_move.png
new file mode 100644
index 00000000..d7b923bf
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/item_move.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/item_rotate.png b/src/Authoring/Qt3DStudio/images/item_rotate.png
new file mode 100644
index 00000000..514c567c
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/item_rotate.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/item_scale.png b/src/Authoring/Qt3DStudio/images/item_scale.png
new file mode 100644
index 00000000..6ef8acdb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/item_scale.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal.png b/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal.png
new file mode 100644
index 00000000..ac59b049
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal@2x.png b/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal@2x.png
new file mode 100644
index 00000000..040d281d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/keyframe-hidden-normal@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/matdef-active.png b/src/Authoring/Qt3DStudio/images/matdef-active.png
new file mode 100644
index 00000000..89861be9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/matdef-active.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/matdef-active@2x.png b/src/Authoring/Qt3DStudio/images/matdef-active@2x.png
new file mode 100644
index 00000000..5098de4e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/matdef-active@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/matdef-disabled.png b/src/Authoring/Qt3DStudio/images/matdef-disabled.png
new file mode 100644
index 00000000..09bf4ad8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/matdef-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/matdef-disabled@2x.png b/src/Authoring/Qt3DStudio/images/matdef-disabled@2x.png
new file mode 100644
index 00000000..eeb66286
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/matdef-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/obsolete_placeholder.png b/src/Authoring/Qt3DStudio/images/obsolete_placeholder.png
new file mode 100644
index 00000000..4837e5f0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/obsolete_placeholder.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/open_dialog.png b/src/Authoring/Qt3DStudio/images/open_dialog.png
new file mode 100644
index 00000000..532a810a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/open_dialog.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/open_dialog@2x.png b/src/Authoring/Qt3DStudio/images/open_dialog@2x.png
new file mode 100644
index 00000000..49a874da
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/open_dialog@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_first.png b/src/Authoring/Qt3DStudio/images/playback_tools_first.png
new file mode 100644
index 00000000..d7cae98d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_first.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_first@2x.png b/src/Authoring/Qt3DStudio/images/playback_tools_first@2x.png
new file mode 100644
index 00000000..88d6cf17
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_first@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_last.png b/src/Authoring/Qt3DStudio/images/playback_tools_last.png
new file mode 100644
index 00000000..3819e1e3
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_last.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_last@2x.png b/src/Authoring/Qt3DStudio/images/playback_tools_last@2x.png
new file mode 100644
index 00000000..0f74d37e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_last@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_play.png b/src/Authoring/Qt3DStudio/images/playback_tools_play.png
new file mode 100644
index 00000000..471b2573
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_play.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_play@2x.png b/src/Authoring/Qt3DStudio/images/playback_tools_play@2x.png
new file mode 100644
index 00000000..02370d25
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_play@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_stop.png b/src/Authoring/Qt3DStudio/images/playback_tools_stop.png
new file mode 100644
index 00000000..bf4d7f59
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_stop.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/playback_tools_stop@2x.png b/src/Authoring/Qt3DStudio/images/playback_tools_stop@2x.png
new file mode 100644
index 00000000..8230c6e8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/playback_tools_stop@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/prefstab-00.png b/src/Authoring/Qt3DStudio/images/prefstab-00.png
new file mode 100644
index 00000000..398a578b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/prefstab-00.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/prefstab-00@2x.png b/src/Authoring/Qt3DStudio/images/prefstab-00@2x.png
new file mode 100644
index 00000000..3c3fdfb1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/prefstab-00@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/prefstab-01.png b/src/Authoring/Qt3DStudio/images/prefstab-01.png
new file mode 100644
index 00000000..35c99cea
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/prefstab-01.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/prefstab-01@2x.png b/src/Authoring/Qt3DStudio/images/prefstab-01@2x.png
new file mode 100644
index 00000000..4c35e2b0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/prefstab-01@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_edit.png b/src/Authoring/Qt3DStudio/images/presentation_edit.png
new file mode 100644
index 00000000..35886718
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_edit.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_edit@2x.png b/src/Authoring/Qt3DStudio/images/presentation_edit@2x.png
new file mode 100644
index 00000000..1f36cdc0
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_edit@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_notUsed.png b/src/Authoring/Qt3DStudio/images/presentation_notUsed.png
new file mode 100644
index 00000000..1103019e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_notUsed.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_notUsed@2x.png b/src/Authoring/Qt3DStudio/images/presentation_notUsed@2x.png
new file mode 100644
index 00000000..d6cb613a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_notUsed@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_used.png b/src/Authoring/Qt3DStudio/images/presentation_used.png
new file mode 100644
index 00000000..2ecfac43
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_used.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/presentation_used@2x.png b/src/Authoring/Qt3DStudio/images/presentation_used@2x.png
new file mode 100644
index 00000000..e5fa2893
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/presentation_used@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-disabled.png b/src/Authoring/Qt3DStudio/images/preview-disabled.png
new file mode 100644
index 00000000..c4effffc
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-disabled@2x.png b/src/Authoring/Qt3DStudio/images/preview-disabled@2x.png
new file mode 100644
index 00000000..7713f19f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-remote-disabled.png b/src/Authoring/Qt3DStudio/images/preview-remote-disabled.png
new file mode 100644
index 00000000..0da8dac9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-remote-disabled.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-remote-disabled@2x.png b/src/Authoring/Qt3DStudio/images/preview-remote-disabled@2x.png
new file mode 100644
index 00000000..dba26e55
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-remote-disabled@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-remote.png b/src/Authoring/Qt3DStudio/images/preview-remote.png
new file mode 100644
index 00000000..70e10906
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-remote.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-remote@2x.png b/src/Authoring/Qt3DStudio/images/preview-remote@2x.png
new file mode 100644
index 00000000..21f48873
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-remote@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-variants.png b/src/Authoring/Qt3DStudio/images/preview-variants.png
new file mode 100644
index 00000000..62c3e2ff
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-variants.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview-variants@2x.png b/src/Authoring/Qt3DStudio/images/preview-variants@2x.png
new file mode 100644
index 00000000..ae5a9534
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview-variants@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview.png b/src/Authoring/Qt3DStudio/images/preview.png
new file mode 100644
index 00000000..9c647a4e
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/preview@2x.png b/src/Authoring/Qt3DStudio/images/preview@2x.png
new file mode 100644
index 00000000..d2a02f2a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/preview@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/qml_notUsed.png b/src/Authoring/Qt3DStudio/images/qml_notUsed.png
new file mode 100644
index 00000000..ee2509f8
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/qml_notUsed.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/qml_notUsed@2x.png b/src/Authoring/Qt3DStudio/images/qml_notUsed@2x.png
new file mode 100644
index 00000000..b3ae4f45
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/qml_notUsed@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/qml_used.png b/src/Authoring/Qt3DStudio/images/qml_used.png
new file mode 100644
index 00000000..a5c1449d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/qml_used.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/qml_used@2x.png b/src/Authoring/Qt3DStudio/images/qml_used@2x.png
new file mode 100644
index 00000000..ab1c20c7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/qml_used@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/separator-vertical.png b/src/Authoring/Qt3DStudio/images/separator-vertical.png
new file mode 100644
index 00000000..f67129cb
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/separator-vertical.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/separator-vertical@2x.png b/src/Authoring/Qt3DStudio/images/separator-vertical@2x.png
new file mode 100644
index 00000000..555623b1
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/separator-vertical@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/separator.png b/src/Authoring/Qt3DStudio/images/separator.png
new file mode 100644
index 00000000..c86aac17
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/separator.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/separator@2x.png b/src/Authoring/Qt3DStudio/images/separator@2x.png
new file mode 100644
index 00000000..052003a4
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/separator@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/studio.icns b/src/Authoring/Qt3DStudio/images/studio.icns
new file mode 100644
index 00000000..e9e2433b
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/studio.icns
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-left.png b/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-left.png
new file mode 100644
index 00000000..a5c0e661
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-left.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-right.png b/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-right.png
new file mode 100644
index 00000000..a23ba5fe
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timebarhandle-disabled-right.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timebarhandle-left.png b/src/Authoring/Qt3DStudio/images/timebarhandle-left.png
new file mode 100644
index 00000000..598b6151
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timebarhandle-left.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timebarhandle-right.png b/src/Authoring/Qt3DStudio/images/timebarhandle-right.png
new file mode 100644
index 00000000..149d834a
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timebarhandle-right.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timeline_text_hidden.png b/src/Authoring/Qt3DStudio/images/timeline_text_hidden.png
new file mode 100644
index 00000000..68b62fd9
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timeline_text_hidden.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timeline_text_hidden@2x.png b/src/Authoring/Qt3DStudio/images/timeline_text_hidden@2x.png
new file mode 100644
index 00000000..b2794e17
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timeline_text_hidden@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timeline_text_shown.png b/src/Authoring/Qt3DStudio/images/timeline_text_shown.png
new file mode 100644
index 00000000..6f3bf557
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timeline_text_shown.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/timeline_text_shown@2x.png b/src/Authoring/Qt3DStudio/images/timeline_text_shown@2x.png
new file mode 100644
index 00000000..53d4b212
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/timeline_text_shown@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/toolbutton-arrow.png b/src/Authoring/Qt3DStudio/images/toolbutton-arrow.png
new file mode 100644
index 00000000..d4b228b6
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/toolbutton-arrow.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/warning.png b/src/Authoring/Qt3DStudio/images/warning.png
new file mode 100644
index 00000000..938dd992
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/warning.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/warning2x.png b/src/Authoring/Qt3DStudio/images/warning2x.png
new file mode 100644
index 00000000..3c809ae7
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/warning2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/welcomedialog/laptop.png b/src/Authoring/Qt3DStudio/images/welcomedialog/laptop.png
new file mode 100644
index 00000000..0bdeef44
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/welcomedialog/laptop.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/zoom_in.png b/src/Authoring/Qt3DStudio/images/zoom_in.png
new file mode 100644
index 00000000..efd65386
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/zoom_in.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/zoom_in@2x.png b/src/Authoring/Qt3DStudio/images/zoom_in@2x.png
new file mode 100644
index 00000000..98fd8d80
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/zoom_in@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/zoom_out.png b/src/Authoring/Qt3DStudio/images/zoom_out.png
new file mode 100644
index 00000000..d05f7c81
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/zoom_out.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/images/zoom_out@2x.png b/src/Authoring/Qt3DStudio/images/zoom_out@2x.png
new file mode 100644
index 00000000..0d2e6bfa
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/images/zoom_out@2x.png
Binary files differ
diff --git a/src/Authoring/Qt3DStudio/qml.qrc b/src/Authoring/Qt3DStudio/qml.qrc
new file mode 100644
index 00000000..09d5451f
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/qml.qrc
@@ -0,0 +1,47 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Palettes/Action/ActionView.qml</file>
+ <file>Palettes/BasicObjects/BasicObjectsView.qml</file>
+ <file>Palettes/Slide/SlideView.qml</file>
+ <file>Palettes/Inspector/InspectorControlView.qml</file>
+ <file>Palettes/Inspector/ObjectBrowser.qml</file>
+ <file>Palettes/Action/HandlerEmitSignal.qml</file>
+ <file>Palettes/Action/HandlerGenericText.qml</file>
+ <file>Palettes/Action/HandlerFireEvent.qml</file>
+ <file>Palettes/Action/HandlerGenericCheckbox.qml</file>
+ <file>Palettes/Action/HandlerGoToSlide.qml</file>
+ <file>Palettes/Action/EventsBrowser.qml</file>
+ <file>Palettes/Action/HandlerPropertyXYZ.qml</file>
+ <file>Palettes/Action/HandlerPropertySlider.qml</file>
+ <file>Palettes/Action/HandlerPropertyCombo.qml</file>
+ <file>Palettes/Action/HandlerProperty.qml</file>
+ <file>Palettes/Action/HandlerPropertyBaseXY.qml</file>
+ <file>Palettes/Action/HandlerPropertyBaseXYZ.qml</file>
+ <file>Palettes/Action/HandlerPropertyBaseSlider.qml</file>
+ <file>Palettes/Action/HandlerGenericColor.qml</file>
+ <file>Palettes/Inspector/HandlerFilesChooser.qml</file>
+ <file>Palettes/Inspector/ImageChooser.qml</file>
+ <file>Palettes/Inspector/MeshChooser.qml</file>
+ <file>Palettes/Inspector/HandlerGenericChooser.qml</file>
+ <file>Palettes/Inspector/ChooserDelegate.qml</file>
+ <file>Palettes/Inspector/TextureChooser.qml</file>
+ <file>Palettes/Project/ProjectView.qml</file>
+ <file>Palettes/Action/HandlerMultilineText.qml</file>
+ <file>Palettes/Action/HandlerGenericBaseColor.qml</file>
+ <file>Palettes/Inspector/FileChooser.qml</file>
+ <file>Palettes/controls/StyledLabel.qml</file>
+ <file>Palettes/controls/StyledTextField.qml</file>
+ <file>Palettes/controls/FloatTextField.qml</file>
+ <file>Palettes/controls/StyledComboBox.qml</file>
+ <file>Palettes/controls/BrowserCombo.qml</file>
+ <file>Palettes/controls/StyledMenuItem.qml</file>
+ <file>Palettes/controls/StyledTooltip.qml</file>
+ <file>Palettes/controls/StyledToolButton.qml</file>
+ <file>Palettes/controls/StyledMenuSeparator.qml</file>
+ <file>Palettes/controls/StyledToggleButton.qml</file>
+ <file>Palettes/Action/HandlerBaseMultilineText.qml</file>
+ <file>Palettes/Action/HandlerGenericFloat.qml</file>
+ <file>Palettes/Inspector/MaterialDropDown.qml</file>
+ <file>Application/FilterVariantsDlg.qml</file>
+ </qresource>
+</RCC>
diff --git a/src/Authoring/Qt3DStudio/remotedeploymentsender.cpp b/src/Authoring/Qt3DStudio/remotedeploymentsender.cpp
new file mode 100644
index 00000000..5c66c682
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/remotedeploymentsender.cpp
@@ -0,0 +1,260 @@
+/****************************************************************************
+**
+** 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$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please 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 "remotedeploymentsender.h"
+#include "StudioApp.h"
+#include "Core.h"
+
+#include <QtCore/qpair.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdatastream.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qsettings.h>
+#include <QtWidgets/qinputdialog.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtWidgets/qdialog.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qgridlayout.h>
+#include <QtWidgets/qdialogbuttonbox.h>
+#include <QtCore/qcoreapplication.h>
+
+class ConnectionDialog : public QDialog
+{
+ Q_DECLARE_TR_FUNCTIONS(ConnectionDialog)
+
+public:
+ static QPair<QString, int> getInfo(QWidget *parent);
+
+private:
+ ConnectionDialog(QWidget *parent);
+ QLineEdit *m_hostLineEdit = nullptr;
+ QLineEdit *m_portLineEdit = nullptr;
+};
+
+ConnectionDialog::ConnectionDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ QSettings settings;
+ QString previousIPAddress = settings.value(QStringLiteral("lastRemoteDeploymentIP")).toString();
+ QString previousPort = settings.value(QStringLiteral("lastRemoteDeploymentPort"),
+ QStringLiteral("36000")).toString();
+
+ m_hostLineEdit = new QLineEdit(this);
+ m_hostLineEdit->setText(previousIPAddress);
+ QLabel *hostLabel = new QLabel(tr("Address:"));
+ hostLabel->setBuddy(m_hostLineEdit);
+
+ m_portLineEdit = new QLineEdit(this);
+ m_portLineEdit->setText(previousPort);
+ QLabel *portLabel = new QLabel(tr("Port:"));
+ portLabel->setBuddy(m_portLineEdit);
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(
+ QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
+
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ QGridLayout *mainLayout = new QGridLayout(this);
+ mainLayout->addWidget(hostLabel, 0, 0);
+ mainLayout->addWidget(m_hostLineEdit, 0, 1);
+ mainLayout->addWidget(portLabel, 1, 0);
+ mainLayout->addWidget(m_portLineEdit, 1, 1);
+ mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
+
+ setWindowTitle(tr("Connect to Device"));
+ m_hostLineEdit->setFocus();
+}
+
+QPair<QString, int> ConnectionDialog::getInfo(QWidget *parent)
+{
+ ConnectionDialog dialog(parent);
+ if (!dialog.exec())
+ return QPair<QString, int>();
+
+ QSettings settings;
+ settings.setValue(QStringLiteral("lastRemoteDeploymentIP"), dialog.m_hostLineEdit->text());
+ settings.setValue(QStringLiteral("lastRemoteDeploymentPort"), dialog.m_portLineEdit->text());
+
+ return qMakePair(dialog.m_hostLineEdit->text(),
+ dialog.m_portLineEdit->text().toInt());
+}
+
+RemoteDeploymentSender::RemoteDeploymentSender(QWidget *parent)
+ : QObject(parent)
+ , m_tcpSocket(nullptr)
+ , m_mainWindow(parent)
+ , m_connectionError(nullptr)
+{
+}
+
+RemoteDeploymentSender::~RemoteDeploymentSender()
+{
+ delete m_tcpSocket;
+ delete m_connectionError;
+}
+
+QPair<QString, int> RemoteDeploymentSender::initConnection()
+{
+ if (isConnected())
+ return QPair<QString, int>();
+
+ delete m_tcpSocket;
+ m_tcpSocket = new QTcpSocket(this);
+ m_lastUpdate = QDateTime();
+
+ QObject::connect(m_tcpSocket, &QTcpSocket::connected, this,
+ &RemoteDeploymentSender::checkConnection);
+ QObject::connect(m_tcpSocket, &QTcpSocket::disconnected, this,
+ &RemoteDeploymentSender::checkConnection);
+ QObject::connect(m_tcpSocket,
+ static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>
+ (&QAbstractSocket::error),
+ this, &RemoteDeploymentSender::connectionError);
+
+ return ConnectionDialog::getInfo(m_mainWindow);
+ }
+
+void RemoteDeploymentSender::connect(const QPair<QString, int> &info)
+{
+ m_tcpSocket->connectToHost(info.first, info.second);
+ if (!m_tcpSocket->waitForConnected(2000)) {
+ m_tcpSocket->abort();
+ connectionError();
+ }
+}
+
+void RemoteDeploymentSender::disconnect()
+{
+ Q_ASSERT(m_tcpSocket);
+ m_tcpSocket->disconnectFromHost();
+}
+
+bool RemoteDeploymentSender::isConnected() const
+{
+ return m_tcpSocket && m_tcpSocket->state()
+ == QAbstractSocket::ConnectedState;
+}
+
+void RemoteDeploymentSender::checkConnection()
+{
+ Q_EMIT connectionChanged(isConnected());
+}
+
+void RemoteDeploymentSender::connectionError()
+{
+ Q_EMIT connectionChanged(isConnected());
+ if (m_tcpSocket) {
+ delete m_connectionError;
+ m_connectionError = new QMessageBox(QMessageBox::Warning, tr("Connect to Device"),
+ tr("Device connection error: ")
+ + m_tcpSocket->errorString(),
+ QMessageBox::Ok, m_mainWindow, Qt::Dialog
+ | Qt::MSWindowsFixedSizeDialogHint
+ | Qt::WindowStaysOnTopHint);
+ m_connectionError->open();
+ }
+}
+
+void RemoteDeploymentSender::streamProject(const QString &projectFile)
+{
+ Q_ASSERT(isConnected());
+ if (!isConnected())
+ return;
+
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_5_8);
+
+ const QFileInfo fileInfo(projectFile);
+ if (!fileInfo.exists()) {
+ qWarning() << "failed to find file " << projectFile;
+ return;
+ }
+
+ // If we have a new project file, then reset the time and clear the deployed file list
+ if (projectFile != m_projectFile) {
+ m_lastUpdate = QDateTime();
+ m_lastDeployed.clear();
+ }
+
+ QString projectPath = g_StudioApp.GetCore()->getProjectFile().getProjectPath();
+
+ const QDir projectDirectory(projectPath);
+
+ // The file to be loaded
+ const QString relativePath = projectDirectory.relativeFilePath(fileInfo.filePath());
+
+ int fileCount = 0;
+ QDirIterator it(projectPath, QDir::Files, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ const QString filePath = it.next();
+ QFile file(filePath);
+
+ QFileInfo info(file);
+ QDateTime lastModified = info.lastModified();
+
+#ifdef Q_OS_DARWIN
+ // Resolution on macOS is not guaranteed below second granularity
+ lastModified.addSecs(1);
+#endif
+ if (!m_lastUpdate.isNull() && lastModified < m_lastUpdate
+ && m_lastDeployed.contains(filePath)) {
+ continue;
+ }
+
+ m_lastDeployed.append(filePath);
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "could not open file " << filePath;
+ return;
+ }
+
+ fileCount++;
+ const QString relativePath = projectDirectory.relativeFilePath(filePath);
+ const QByteArray payload = file.readAll();
+ out << relativePath;
+ out << payload;
+ }
+
+ QByteArray metaBlock;
+ QDataStream metaOut(&metaBlock, QIODevice::WriteOnly);
+ metaOut.setVersion(QDataStream::Qt_5_8);
+ metaOut << block.size();
+ metaOut << fileCount;
+ metaOut << relativePath;
+
+ // Record the current time to compare against on next update
+ m_lastUpdate = QDateTime::currentDateTime();
+ m_projectFile = projectFile;
+
+ m_tcpSocket->write(metaBlock);
+ m_tcpSocket->write(block);
+}
diff --git a/src/Authoring/Qt3DStudio/remotedeploymentsender.h b/src/Authoring/Qt3DStudio/remotedeploymentsender.h
new file mode 100644
index 00000000..d996e795
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/remotedeploymentsender.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$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please 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 REMOTEDEPLOYMENTSENDER_H
+#define REMOTEDEPLOYMENTSENDER_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qdatetime.h>
+#include <QtWidgets/qwidget.h>
+#include <QtNetwork/qtcpsocket.h>
+
+QT_FORWARD_DECLARE_CLASS(QMessageBox)
+
+class RemoteDeploymentSender : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RemoteDeploymentSender(QWidget *parent);
+ ~RemoteDeploymentSender();
+
+ QPair<QString, int> initConnection();
+ void connect(const QPair<QString, int> &info);
+ void disconnect();
+ bool isConnected() const;
+
+ void streamProject(const QString &);
+
+public Q_SLOTS:
+ void checkConnection();
+ void connectionError();
+
+Q_SIGNALS:
+ void connectionChanged(bool) const;
+
+private:
+ QTcpSocket *m_tcpSocket;
+ QWidget *m_mainWindow;
+ QMessageBox *m_connectionError;
+ QDateTime m_lastUpdate;
+ QString m_projectFile;
+ QStringList m_lastDeployed;
+};
+
+#endif // REMOTEDEPLOYMENTSENDER_H
diff --git a/src/Authoring/Qt3DStudio/style.qss b/src/Authoring/Qt3DStudio/style.qss
new file mode 100644
index 00000000..298cec7d
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/style.qss
@@ -0,0 +1,465 @@
+/* General coloring and font size */
+QWidget {
+ color: #ffffff;
+ background: #2e2f30;
+ font-size: 12px;
+ border: 0px;
+}
+
+/* DockWidget */
+QMainWindow::separator {
+ background: #727476;
+ width: 2px; /* when vertical */
+ height: 2px; /* when horizontal */
+}
+
+QDockWidget::title {
+ border: 1px solid #727476;
+ background: #727476;
+ padding: 3px;
+}
+
+QDockWidget::close-button,
+QDockWidget::float-button {
+ border: 1px solid transparent;
+ background: transparent;
+}
+
+/* Tabs */
+QTabBar::tab:selected {
+ background: #2e2f30;
+ padding: 5px;
+}
+
+QTabBar::tab:!selected {
+ background: #262829;
+ color: #727476;
+ padding: 5px;
+}
+
+QTabWidget::pane {
+ border: 1px solid #404244;
+}
+
+/* Scrollbar */
+QScrollBar:horizontal {
+ height: 6px;
+}
+
+QScrollBar:vertical {
+ width: 6px;
+}
+
+QScrollBar::add-page:horizontal,
+QScrollBar::sub-page:horizontal,
+QScrollBar::add-page:vertical,
+QScrollBar::sub-page:vertical {
+ background: #2e2f30;
+}
+
+QScrollBar::handle:horizontal {
+ height: 6px;
+ border: 3px solid #999A9B;
+ border-radius: 3px;
+}
+
+QScrollBar::add-line:horizontal,
+QScrollBar::sub-line:horizontal {
+ width: 0px;
+}
+
+QScrollBar::handle:vertical {
+ width: 6px;
+ border: 3px solid #999A9B;
+ border-radius: 3px;
+}
+
+QScrollBar::add-line:vertical,
+QScrollBar::sub-line:vertical {
+ height: 0px;
+}
+
+/* Menubar */
+QMenuBar {
+ background: #404244;
+ border-top: 1px solid #262829;
+ border-bottom: 1px solid #262829;
+}
+
+QMenuBar::item {
+ background: transparent;
+}
+
+QMenuBar::item:selected {
+ background: #46a2da;
+ border: 1px solid #262829;
+}
+
+/* Menu */
+QMenu::item:selected:enabled {
+ background: #46a2da;
+}
+
+QMenu::item:disabled {
+ color: #727476;
+}
+
+QMenu::separator {
+ height: 1px;
+ background: #727476;
+}
+
+/* Toolbar */
+QToolBar {
+ background: #404244;
+}
+
+QToolBar::handle:horizontal {
+ image: url(:/images/separator.png);
+}
+
+QToolBar::handle:vertical {
+ image: url(:/images/separator-vertical.png);
+}
+
+QToolBar::separator:horizontal {
+ background: #727476;
+ width: 1px;
+ margin: 3px;
+}
+
+QToolBar::separator:vertical {
+ background: #727476;
+ height: 1px;
+ margin: 3px;
+}
+
+QToolButton {
+ min-width: 22px;
+ max-width: 22px;
+ min-height: 22px;
+ max-height: 22px;
+ background: #404244;
+}
+
+QToolButton:checked, QToolButton:hover {
+ background: #262829;
+}
+
+QToolButton:pressed:!checked {
+ background: #666666;
+}
+
+QToolButton#actionFilterVariants:checked {
+ background-color: #2e2f30;
+ border-color: #727476;
+ border-width: 1px;
+ border-top-style: solid;
+ border-right-style: solid;
+ border-left-style: solid;
+}
+
+QComboBox#cameraSelector {
+ /* We have a specific style for camera selector combobox, as it's in the toolbar and needs to
+ blend in */
+ background: #404244;
+ border: 0px;
+ border-radius: 0;
+ padding: 3px;
+}
+
+/* ToolTip */
+QToolTip {
+ background: #404244;
+ color: #ffffff;
+ font-size: 12px;
+ border: 1px solid #727476;
+ border-radius: 2;
+}
+
+QLabel#variantsToolTip {
+ background: #222222;
+ border: 1px solid #444444;
+}
+
+/* Dialog widgets */
+QComboBox,
+QLineEdit,
+QSpinBox,
+QDoubleSpinBox,
+QDateTimeEdit {
+ background: #404244;
+ border: 1px solid #262829;
+ border-radius: 2;
+ padding: 3px;
+}
+
+QComboBox::drop-down {
+ background: #404244;
+}
+
+QComboBox::down-arrow,
+QSpinBox::down-arrow,
+QDoubleSpinBox::down-arrow,
+QDateTimeEdit::down-arrow {
+ image: url(:/images/arrow_down.png);
+}
+
+QComboBox::down-arrow:on {
+ image: url(:/images/arrow_up.png);
+}
+
+QSpinBox::up-arrow,
+QDoubleSpinBox::up-arrow,
+QDateTimeEdit::up-arrow {
+ image: url(:/images/arrow_up.png);
+}
+
+QSpinBox::up-button,
+QSpinBox::down-button,
+QDoubleSpinBox::up-button,
+QDoubleSpinBox::down-button,
+QDateTimeEdit::up-button,
+QDateTimeEdit::down-button {
+ border: 0px;
+}
+
+QGroupBox {
+ padding: 14px 0px 0px 0px;
+ border: 1px solid #262829;
+}
+
+QGroupBox::title {
+ subcontrol-origin: padding;
+ margin-top: 2px;
+ margin-left: 10px;
+}
+
+QPushButton:!pressed {
+ background: #404244;
+ border: 1px solid #262829;
+ border-radius: 2;
+ padding: 3px 10px 3px 10px;
+ min-width: 75;
+}
+
+QPushButton:pressed {
+ background: #262829;
+}
+
+QHeaderView::section {
+ background: transparent;
+ min-height: 26px;
+}
+
+QHeaderView::section:hover {
+ background: #46a2da;
+}
+
+QCheckBox::indicator:unchecked {
+ image: url(:/images/checkbox-unchecked.png);
+}
+
+QCheckBox::indicator:checked {
+ image: url(:/images/checkbox-checked.png);
+}
+
+/* Reference material list */
+MaterialRefView {
+ border: 1px solid #727476;
+ background: #2e2f30;
+}
+
+MaterialRefView::item:hover {
+ background-color: #23516D;
+}
+
+/* Tutorial Dialog */
+QDialog#StudioTutorialWidget,
+QDialog#StudioTutorialWidget QWidget {
+ background-color: transparent;
+ border: 0px;
+}
+
+/* This dummy label is a hack to fix the background quadrupling issue */
+QLabel#dummyLabel {
+ font-size: 1px;
+ color: "#273049"; /* color to match background in case text size already doesn't hide it */
+}
+
+QLabel#studioTutorialName {
+ font-size: 42px;
+ font-family: "Titillium Web";
+ font-weight: 300;
+ color: "#00cd45";
+}
+
+QLabel#studioTutorialWelcome {
+ font-size: 42px;
+ font-family: "Titillium Web";
+ font-weight: 300;
+ color: #ffffff;
+}
+
+QPushButton#studioTutorialOpen,
+QPushButton#studioTutorialNew,
+QPushButton#studioTutorialQuickStart {
+ font-size: 28px;
+ font-family: "Titillium Web";
+ font-weight: 300;
+ color: #ffffff;
+ padding: 0px 60px 0px 60px;
+}
+
+QPushButton:!pressed#studioTutorialOpen,
+QPushButton:!pressed#studioTutorialNew,
+QPushButton:!pressed#studioTutorialQuickStart {
+ background: "#00cd45";
+ border: 3px solid "#00cd45";
+ border-radius: 10px;
+}
+
+QPushButton:pressed#studioTutorialOpen,
+QPushButton:pressed#studioTutorialNew,
+QPushButton:pressed#studioTutorialQuickStart {
+ background: transparent;
+ border: 3px solid "#00cd45";
+ border-radius: 10px;
+}
+
+/* Startup Dialog */
+QDialog#StartupDlg,
+QDialog#StartupDlg QWidget {
+ color: #ffffff;
+ background-color: transparent;
+ border: 0px;
+ font-size: 16px;
+}
+
+/* About Dialog */
+QDialog#AboutDlg,
+QDialog#AboutDlg QWidget {
+ background-color: transparent;
+ border: 0px;
+}
+
+QDialog#AboutDlg QPushButton:!pressed {
+ background: transparent;
+ border: 1px solid #41cd52;
+}
+
+QDialog#AboutDlg QPushButton:pressed {
+ background: #41cd52;
+ border: 1px solid #41cd52;
+}
+
+/* TimelineToolbar */
+QPushButton#timelineButton {
+ border: 0px;
+}
+
+QPushButton:pressed#timelineButton {
+ background: #46a2da;
+}
+
+QPushButton:hover:!pressed#timelineButton {
+ background: #262829;
+}
+
+/* Loading progress dialog */
+QDialog#ProgressDlg QWidget {
+ background-color: #202020;
+}
+
+QDialog#ProgressDlg QWidget#backgroundWidget {
+ border: 1px solid #808080;
+}
+
+QDialog#ProgressDlg QWidget#progressActionText {
+ font-size: 16px;
+}
+
+/* Data Input Dialogs */
+QDialog#DataInputListDlg QTableView QTableCornerButton::section,
+QDialog#DataInputListDlg QHeaderView::section {
+ font-weight: bold;
+}
+
+QDialog#DataInputListDlg QTableView {
+ selection-background-color: #46a2da;
+ gridline-color: #2e2f30;
+}
+
+QDialog#DataInputListDlg QListView {
+ border: 1px solid #808080;
+ selection-background-color: #46a2da;
+}
+
+QDialog#DataInputListDlg QListView::item:selected {
+ background-color: #46a2da;
+}
+
+QDialog#DataInputListDlg QHeaderView::section:hover {
+ background: transparent;
+}
+
+QDialog#DataInputListDlg QComboBox QAbstractItemView::item:selected
+{
+ background-color: #46a2da;
+}
+
+QDialog#DataInputListDlg QComboBox QAbstractItemView::item
+{
+ margin-top: 5px;
+}
+
+QDialog#DataInputListDlg QPushButton#DataInputListButton {
+ background-color: transparent;
+ border: 0px;
+ min-width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+ padding: 0px;
+}
+
+QDialog#DataInputListDlg QPushButton#DataInputListButton:pressed {
+ background-color: #262829;
+}
+
+QDialog#DataInputDlg QPushButton:!enabled,
+QDialog#DataInputDlg QComboBox QAbstractItemView::item:disabled {
+ background: transparent;
+ color: #727476;
+}
+
+QDialog#DataInputDlg QTableView {
+ selection-background-color: #46a2da;
+ gridline-color: transparent;
+ border: 1px solid #808080;
+ background-color: transparent;
+}
+
+QDialog#DataInputDlg QTableView::item:selected {
+ background-color: #46a2da;
+}
+
+/* QSlider horizontal */
+QSlider {
+ background: transparent;
+}
+
+QSlider::groove:horizontal {
+ background: #dddddd;
+ height: 1px;
+}
+
+QSlider::handle:horizontal {
+ background: #999A9B;
+ width: 12px;
+ margin: -5px 0;
+ border-radius: 4px;
+}
+
+QSlider::handle:horizontal:hover {
+ background: #cccccc;
+}
diff --git a/src/Authoring/Qt3DStudio/version.h b/src/Authoring/Qt3DStudio/version.h
new file mode 100644
index 00000000..118d95b5
--- /dev/null
+++ b/src/Authoring/Qt3DStudio/version.h
@@ -0,0 +1,35 @@
+/****************************************************************************
+**
+** 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_VERSION
+
+#ifdef WIN32
+#include "..\Build\VersionNumber.h"
+#endif
+
+#endif