aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmldesigner/components')
-rw-r--r--src/plugins/qmldesigner/components/colortool/colortool.cpp243
-rw-r--r--src/plugins/qmldesigner/components/colortool/colortool.h89
-rw-r--r--src/plugins/qmldesigner/components/colortool/colortool.pri3
-rw-r--r--src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp10
-rw-r--r--src/plugins/qmldesigner/components/componentcore/componentcore_constants.h1
-rw-r--r--src/plugins/qmldesigner/components/componentcore/crumblebar.cpp2
-rw-r--r--src/plugins/qmldesigner/components/componentcore/crumblebar.h4
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp7
-rw-r--r--src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp11
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp160
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h1
-rw-r--r--src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp1
-rw-r--r--src/plugins/qmldesigner/components/componentcore/zoomaction.cpp3
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp114
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h63
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui135
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp333
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/backendmodel.h74
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp446
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h93
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri26
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc5
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp357
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h89
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp222
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionview.h100
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp348
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h102
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui273
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/delegates.cpp361
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/delegates.h104
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp677
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h104
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/stylesheet.css123
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp187
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/animationcurve.h69
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp64
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditor.h53
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditor.pri46
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditormodel.cpp70
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditormodel.h72
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h125
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.cpp101
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.h63
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.cpp267
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.h127
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp226
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h98
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp277
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h65
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp225
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.h89
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp523
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h157
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp104
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h56
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp251
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h98
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/playhead.cpp187
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/playhead.h72
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp112
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.h85
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp221
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/selector.h86
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/shortcut.cpp81
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/shortcut.h61
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.cpp146
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.h63
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp156
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treemodel.h75
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp127
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/treeview.h61
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp107
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/utils.h52
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/keyframe.cpp88
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/keyframe.h65
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/treeitem.cpp227
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/treeitem.h137
-rw-r--r--src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp2
-rw-r--r--src/plugins/qmldesigner/components/formeditor/dragtool.cpp1
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp5
-rw-r--r--src/plugins/qmldesigner/components/formeditor/numberseriesaction.cpp2
-rw-r--r--src/plugins/qmldesigner/components/importmanager/importswidget.cpp2
-rw-r--r--src/plugins/qmldesigner/components/integration/componentaction.cpp2
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.cpp52
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.h4
-rw-r--r--src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp2
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp59
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatortreeview.h1
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatorview.cpp16
-rw-r--r--src/plugins/qmldesigner/components/pathtool/controlpoint.cpp174
-rw-r--r--src/plugins/qmldesigner/components/pathtool/controlpoint.h93
-rw-r--r--src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp367
-rw-r--r--src/plugins/qmldesigner/components/pathtool/cubicsegment.h126
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathitem.cpp971
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathitem.h140
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp299
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h100
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.cpp315
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.h91
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.pri13
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp95
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtoolview.h47
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp13
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h4
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp178
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientmodel.h15
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.cpp161
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.h62
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.cpp61
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.h48
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.cpp206
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.h92
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.cpp113
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.h60
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditor.pri18
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp29
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp119
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h7
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp119
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h3
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp284
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h2
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp31
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h9
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp6
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.cpp113
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.h75
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.cpp146
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.h77
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.cpp185
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.h71
-rw-r--r--src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp252
-rw-r--r--src/plugins/qmldesigner/components/sourcetool/sourcetool.h90
-rw-r--r--src/plugins/qmldesigner/components/sourcetool/sourcetool.pri3
-rw-r--r--src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp1
-rw-r--r--src/plugins/qmldesigner/components/stateseditor/stateseditorview.h1
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititem.cpp98
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititem.h58
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp125
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititemwidget.h60
-rw-r--r--src/plugins/qmldesigner/components/texttool/texttool.cpp270
-rw-r--r--src/plugins/qmldesigner/components/texttool/texttool.h89
-rw-r--r--src/plugins/qmldesigner/components/texttool/texttool.pri7
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/canvas.cpp359
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/canvas.h110
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.cpp124
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.h84
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/easingcurve.cpp502
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/easingcurve.h157
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.cpp298
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.h85
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/add_timeline.pngbin0 -> 164 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/add_timeline@2x.pngbin0 -> 162 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/animation.pngbin0 -> 200 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/animation@2x.pngbin0 -> 235 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame.pngbin0 -> 153 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame@2x.pngbin0 -> 231 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/curve_editor.pngbin0 -> 190 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/curve_editor@2x.pngbin0 -> 239 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/curve_picker.pngbin0 -> 163 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/curve_picker@2x.pngbin0 -> 295 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame.pngbin0 -> 147 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame@2x.pngbin0 -> 231 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes.pngbin0 -> 162 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes@2x.pngbin0 -> 281 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe.pngbin0 -> 237 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe@2x.pngbin0 -> 236 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe-16px.pngbin0 -> 190 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe.pngbin0 -> 185 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe@2x.pngbin0 -> 185 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active.pngbin0 -> 268 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active@2x.pngbin0 -> 489 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive.pngbin0 -> 241 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive@2x.pngbin0 -> 414 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected.pngbin0 -> 273 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected@2x.pngbin0 -> 493 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active.pngbin0 -> 327 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active@2x.pngbin0 -> 550 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive.pngbin0 -> 371 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive@2x.pngbin0 -> 580 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected.pngbin0 -> 328 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected@2x.pngbin0 -> 557 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active.pngbin0 -> 204 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active@2x.pngbin0 -> 299 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive.pngbin0 -> 201 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive@2x.pngbin0 -> 292 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected.pngbin0 -> 204 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected@2x.pngbin0 -> 299 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active.pngbin0 -> 336 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active@2x.pngbin0 -> 542 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive.pngbin0 -> 286 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive@2x.pngbin0 -> 457 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected.pngbin0 -> 407 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected@2x.pngbin0 -> 706 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes.pngbin0 -> 186 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes@2x.pngbin0 -> 192 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/loop_playback.pngbin0 -> 171 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/loop_playback@2x.pngbin0 -> 291 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe.pngbin0 -> 133 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe@2x.pngbin0 -> 150 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/pause_playback.pngbin0 -> 150 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/pause_playback@2x.pngbin0 -> 152 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/playhead.pngbin0 -> 289 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/playhead@2x.pngbin0 -> 478 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe.pngbin0 -> 132 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe@2x.pngbin0 -> 152 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline.pngbin0 -> 190 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline@2x.pngbin0 -> 179 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/start_playback.pngbin0 -> 143 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/start_playback@2x.pngbin0 -> 169 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/timeline-16px.pngbin0 -> 389 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame.pngbin0 -> 135 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame@2x.pngbin0 -> 193 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame.pngbin0 -> 132 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame@2x.pngbin0 -> 191 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left.pngbin0 -> 186 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left@2x.pngbin0 -> 309 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right.pngbin0 -> 177 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right@2x.pngbin0 -> 282 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/zoom_big.pngbin0 -> 202 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/zoom_big@2x.pngbin0 -> 357 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/zoom_small.pngbin0 -> 191 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/images/zoom_small@2x.pngbin0 -> 370 bytes
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/preseteditor.cpp560
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/preseteditor.h152
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp55
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.h54
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.ui74
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/splineeditor.cpp274
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/splineeditor.h96
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timeline.metainfo35
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timeline.qrc77
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp67
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h73
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineactions.cpp322
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineactions.h52
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp262
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.h64
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui327
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h77
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinecontext.cpp45
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinecontext.h43
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.cpp211
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.h123
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri81
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp186
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineform.h59
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineform.ui254
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp162
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h87
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp720
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h181
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineicons.h114
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp293
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineitem.h82
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp154
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h83
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp173
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h54
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.cpp78
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.h46
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp641
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h128
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp1062
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h192
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp179
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h86
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.cpp272
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.h74
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.ui84
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp484
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.h87
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp460
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h108
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.cpp164
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.h71
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp150
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h81
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineutils.cpp48
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineutils.h128
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp610
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineview.h100
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp443
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h106
285 files changed, 29426 insertions, 547 deletions
diff --git a/src/plugins/qmldesigner/components/colortool/colortool.cpp b/src/plugins/qmldesigner/components/colortool/colortool.cpp
new file mode 100644
index 0000000000..32a5742f9f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/colortool/colortool.cpp
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "colortool.h"
+
+#include "formeditorscene.h"
+#include "formeditorview.h"
+#include "formeditorwidget.h"
+#include "itemutilfunctions.h"
+#include "formeditoritem.h"
+
+#include "resizehandleitem.h"
+
+#include "nodemetainfo.h"
+#include "qmlitemnode.h"
+#include <qmldesignerplugin.h>
+#include <abstractaction.h>
+#include <designeractionmanager.h>
+
+#include <QApplication>
+#include <QGraphicsSceneMouseEvent>
+#include <QAction>
+#include <QDebug>
+#include <QPair>
+#include <QUrl>
+
+namespace QmlDesigner {
+
+class ColorToolAction : public AbstractAction
+{
+public:
+ ColorToolAction() : AbstractAction(QCoreApplication::translate("ColorToolAction","Edit Color")) {}
+
+ QByteArray category() const override
+ {
+ return QByteArray();
+ }
+
+ QByteArray menuId() const override
+ {
+ return "ColorTool";
+ }
+
+ int priority() const override
+ {
+ return CustomActionsPriority;
+ }
+
+ Type type() const override
+ {
+ return FormEditorAction;
+ }
+
+protected:
+ bool isVisible(const SelectionContext &selectionContext) const override
+ {
+ if (selectionContext.singleNodeIsSelected())
+ return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("color");
+
+ return false;
+ }
+
+ bool isEnabled(const SelectionContext &selectionContext) const override
+ {
+ return isVisible(selectionContext);
+ }
+};
+
+ColorTool::ColorTool()
+{
+ auto colorToolAction = new ColorToolAction;
+ QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(colorToolAction);
+ connect(colorToolAction->action(), &QAction::triggered, [=]() {
+ view()->changeCurrentToolTo(this);
+ });
+}
+
+ColorTool::~ColorTool() = default;
+
+void ColorTool::clear()
+{
+ if (m_colorDialog)
+ m_colorDialog.data()->deleteLater();
+
+ AbstractFormEditorTool::clear();
+}
+
+void ColorTool::mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mousePressEvent(itemList, event);
+}
+
+void ColorTool::mouseMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * /*event*/)
+{
+}
+
+void ColorTool::hoverMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * /*event*/)
+{
+}
+
+void ColorTool::keyPressEvent(QKeyEvent * /*keyEvent*/)
+{
+}
+
+void ColorTool::keyReleaseEvent(QKeyEvent * /*keyEvent*/)
+{
+}
+
+void ColorTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+}
+
+void ColorTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+}
+
+void ColorTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mouseReleaseEvent(itemList, event);
+}
+
+
+void ColorTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mouseDoubleClickEvent(itemList, event);
+}
+
+void ColorTool::itemsAboutToRemoved(const QList<FormEditorItem*> &removedItemList)
+{
+ if (m_colorDialog.isNull())
+ return;
+
+ if (removedItemList.contains(m_formEditorItem))
+ view()->changeToSelectionTool();
+}
+
+void ColorTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList)
+{
+ if (m_colorDialog.data()
+ && m_oldColor.isValid())
+ m_formEditorItem->qmlItemNode().setVariantProperty("color", m_oldColor);
+
+ if (!itemList.isEmpty()
+ && itemList.constFirst()->qmlItemNode().modelNode().metaInfo().hasProperty("color")) {
+ m_formEditorItem = itemList.constFirst();
+ m_oldColor = m_formEditorItem->qmlItemNode().modelValue("color").value<QColor>();
+
+ if (m_colorDialog.isNull()) {
+ m_colorDialog = new QColorDialog(view()->formEditorWidget()->parentWidget());
+ m_colorDialog.data()->setCurrentColor(m_oldColor);
+
+ connect(m_colorDialog.data(), &QDialog::accepted, this, &ColorTool::colorDialogAccepted);
+ connect(m_colorDialog.data(), &QDialog::rejected, this, &ColorTool::colorDialogRejected);
+ connect(m_colorDialog.data(), &QColorDialog::currentColorChanged, this, &ColorTool::currentColorChanged);
+
+ m_colorDialog.data()->exec();
+ }
+ } else {
+ view()->changeToSelectionTool();
+ }
+}
+
+void ColorTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+void ColorTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/)
+{
+}
+
+void ColorTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > & /*propertyList*/)
+{
+}
+
+void ColorTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+int ColorTool::wantHandleItem(const ModelNode &modelNode) const
+{
+ if (modelNode.metaInfo().hasProperty("color"))
+ return 10;
+
+ return 0;
+}
+
+QString ColorTool::name() const
+{
+ return tr("Color Tool");
+}
+
+void ColorTool::colorDialogAccepted()
+{
+ view()->changeToSelectionTool();
+}
+
+void ColorTool::colorDialogRejected()
+{
+ if (m_formEditorItem) {
+ if (m_oldColor.isValid())
+ m_formEditorItem->qmlItemNode().setVariantProperty("color", m_oldColor);
+ else
+ m_formEditorItem->qmlItemNode().removeProperty("color");
+
+ }
+
+ view()->changeToSelectionTool();
+}
+
+void ColorTool::currentColorChanged(const QColor &color)
+{
+ if (m_formEditorItem) {
+ m_formEditorItem->qmlItemNode().setVariantProperty("color", color);
+ }
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/colortool/colortool.h b/src/plugins/qmldesigner/components/colortool/colortool.h
new file mode 100644
index 0000000000..5e9e54c633
--- /dev/null
+++ b/src/plugins/qmldesigner/components/colortool/colortool.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "abstractcustomtool.h"
+#include "selectionindicator.h"
+
+#include <QHash>
+#include <QPointer>
+#include <QColorDialog>
+
+namespace QmlDesigner {
+
+class ColorTool : public QObject, public AbstractCustomTool
+{
+ Q_OBJECT
+public:
+ ColorTool();
+ ~ColorTool() override;
+
+ void mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+ void dragLeaveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+ void dragMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+
+ void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override;
+
+ void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ void instancesCompleted(const QList<FormEditorItem*> &itemList) override;
+ void instancesParentChanged(const QList<FormEditorItem *> &itemList) override;
+ void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override;
+
+ void clear() override;
+
+ void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ int wantHandleItem(const ModelNode &modelNode) const override;
+
+ QString name() const override;
+
+private:
+ void colorDialogAccepted();
+ void colorDialogRejected();
+ void currentColorChanged(const QColor &color);
+
+private:
+ QPointer<QColorDialog> m_colorDialog;
+ FormEditorItem *m_formEditorItem = nullptr;
+ QColor m_oldColor;
+};
+
+}
diff --git a/src/plugins/qmldesigner/components/colortool/colortool.pri b/src/plugins/qmldesigner/components/colortool/colortool.pri
new file mode 100644
index 0000000000..602af91da5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/colortool/colortool.pri
@@ -0,0 +1,3 @@
+HEADERS += $$PWD/colortool.h
+
+SOURCES += $$PWD/colortool.cpp
diff --git a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
index fa77acccb9..c2a17cd7b1 100644
--- a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
@@ -35,10 +35,10 @@ namespace QmlDesigner {
static QString styleConfigFileName(const QString &qmlFileName)
{
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(Utils::FileName::fromString(qmlFileName));
+ ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(Utils::FilePath::fromString(qmlFileName));
if (currentProject)
- foreach (const Utils::FileName &fileName, currentProject->files(ProjectExplorer::Project::SourceFiles))
+ foreach (const Utils::FilePath &fileName, currentProject->files(ProjectExplorer::Project::SourceFiles))
if (fileName.endsWith("qtquickcontrols2.conf"))
return fileName.toString();
@@ -90,14 +90,14 @@ QWidget *ChangeStyleWidgetAction::createWidget(QWidget *parent)
});
connect(comboBox,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
+ QOverload<const QString &>::of(&QComboBox::activated),
this,
[this](const QString &style) {
if (style.isEmpty())
return;
- const Utils::FileName configFileName = Utils::FileName::fromString(styleConfigFileName(qmlFileName));
+ const Utils::FilePath configFileName = Utils::FilePath::fromString(styleConfigFileName(qmlFileName));
if (configFileName.exists()) {
QSettings infiFile(configFileName.toString(), QSettings::IniFormat);
@@ -125,7 +125,7 @@ void ChangeStyleAction::currentContextChanged(const SelectionContext &selectionC
const QString confFileName = styleConfigFileName(fileName);
- if (Utils::FileName::fromString(confFileName).exists()) {
+ if (Utils::FilePath::fromString(confFileName).exists()) {
QSettings infiFile(confFileName, QSettings::IniFormat);
m_action->handleModelUpdate(infiFile.value("Controls/Style", "Default").toString());
} else {
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
index 028d3a21b4..0535a8f52e 100644
--- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
+++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
@@ -165,6 +165,7 @@ const int priorityGenericToolBar = 50;
const int priorityLast = 60;
const char addImagesDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Image Files");
+const char addFontsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Font Files");
} //ComponentCoreConstants
diff --git a/src/plugins/qmldesigner/components/componentcore/crumblebar.cpp b/src/plugins/qmldesigner/components/componentcore/crumblebar.cpp
index 112923c5f7..733b29ef5a 100644
--- a/src/plugins/qmldesigner/components/componentcore/crumblebar.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/crumblebar.cpp
@@ -78,7 +78,7 @@ CrumbleBar::~CrumbleBar()
delete m_crumblePath;
}
-void CrumbleBar::pushFile(const Utils::FileName &fileName)
+void CrumbleBar::pushFile(const Utils::FilePath &fileName)
{
if (!m_isInternalCalled) {
crumblePath()->clear();
diff --git a/src/plugins/qmldesigner/components/componentcore/crumblebar.h b/src/plugins/qmldesigner/components/componentcore/crumblebar.h
index d29d8f594e..cd19e1faa2 100644
--- a/src/plugins/qmldesigner/components/componentcore/crumblebar.h
+++ b/src/plugins/qmldesigner/components/componentcore/crumblebar.h
@@ -39,7 +39,7 @@ public:
explicit CrumbleBar(QObject *parent = nullptr);
~CrumbleBar() override;
- void pushFile(const Utils::FileName &fileName);
+ void pushFile(const Utils::FilePath &fileName);
void pushInFileComponent(const ModelNode &modelNode);
void nextFileIsCalledInternally();
@@ -58,7 +58,7 @@ private:
class CrumbleBarInfo {
public:
- Utils::FileName fileName;
+ Utils::FilePath fileName;
QString displayName;
ModelNode modelNode;
};
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
index 6edbacec36..37e1fdfd53 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
@@ -1026,6 +1026,13 @@ void DesignerActionManager::createDefaultAddResourceHandler()
registerAddResourceHandler(AddResourceHandler(ComponentCoreConstants::addImagesDisplayString,
"*.svg",
ModelNodeOperations::addImageToProject));
+
+ registerAddResourceHandler(AddResourceHandler(ComponentCoreConstants::addFontsDisplayString,
+ "*.ttf",
+ ModelNodeOperations::addFontToProject));
+ registerAddResourceHandler(AddResourceHandler(ComponentCoreConstants::addFontsDisplayString,
+ "*.otf",
+ ModelNodeOperations::addFontToProject));
}
void DesignerActionManager::addDesignerAction(ActionInterface *newAction)
diff --git a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp
index 5ec3ea557b..ba520a1ca3 100644
--- a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp
@@ -185,17 +185,16 @@ void LayoutInGridLayout::doIt()
if (qmlItemNode.hasInstanceParentItem()) {
ModelNode layoutNode;
- {
- RewriterTransaction transaction(m_selectionContext.view(), QByteArrayLiteral("LayoutInGridLayout1"));
+
+ m_selectionContext.view()->executeInTransaction("LayoutInGridLayout1",[this, &layoutNode, layoutType](){
QTC_ASSERT(m_selectionContext.view()->model()->hasNodeMetaInfo(layoutType), return);
NodeMetaInfo metaInfo = m_selectionContext.view()->model()->metaInfo(layoutType);
layoutNode = m_selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion());
reparentTo(layoutNode, m_parentNode);
- }
+ });
- {
- RewriterTransaction transaction(m_selectionContext.view(), QByteArrayLiteral("LayoutInGridLayout2"));
+ m_selectionContext.view()->executeInTransaction("LayoutInGridLayout2", [this, layoutNode](){
fillEmptyCells();
@@ -208,7 +207,7 @@ void LayoutInGridLayout::doIt()
reparentToNodeAndRemovePositionForModelNodes(layoutNode, sortedSelectedNodes);
setSizeAsPreferredSize(sortedSelectedNodes);
setSpanning(layoutNode);
- }
+ });
}
}
}
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
index f5b701e44d..9f880c453b 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
@@ -240,9 +240,7 @@ void changeOrder(const SelectionContext &selectionState, OderAction orderAction)
if (!modelNode.parentProperty().isNodeListProperty())
return;
- try {
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|raise"));
-
+ selectionState.view()->executeInTransaction("DesignerActionManager|raise",[orderAction, selectionState, modelNode](){
ModelNode modelNode = selectionState.currentSingleSelectedNode();
NodeListProperty parentProperty = modelNode.parentProperty().toNodeListProperty();
const int index = parentProperty.indexOf(modelNode);
@@ -255,11 +253,7 @@ void changeOrder(const SelectionContext &selectionState, OderAction orderAction)
if (index > 0)
parentProperty.slide(index, index - 1);
}
-
- transaction.commit();
- } catch (const RewritingException &e) { //better save then sorry
- e.showException();
- }
+ });
}
void raise(const SelectionContext &selectionState)
@@ -328,16 +322,13 @@ void resetSize(const SelectionContext &selectionState)
if (!selectionState.view())
return;
- try {
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|resetSize"));
+ selectionState.view()->executeInTransaction("DesignerActionManager|resetSize",[selectionState](){
foreach (ModelNode node, selectionState.selectedModelNodes()) {
QmlItemNode itemNode(node);
itemNode.removeProperty("width");
itemNode.removeProperty("height");
}
- } catch (const RewritingException &e) { //better save then sorry
- e.showException();
- }
+ });
}
void resetPosition(const SelectionContext &selectionState)
@@ -345,17 +336,13 @@ void resetPosition(const SelectionContext &selectionState)
if (!selectionState.view())
return;
- try {
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|resetPosition"));
+ selectionState.view()->executeInTransaction("DesignerActionManager|resetPosition",[selectionState](){
foreach (ModelNode node, selectionState.selectedModelNodes()) {
QmlItemNode itemNode(node);
itemNode.removeProperty("x");
itemNode.removeProperty("y");
}
- transaction.commit();
- } catch (const RewritingException &e) { //better save then sorry
- e.showException();
- }
+ });
}
void goIntoComponentOperation(const SelectionContext &selectionState)
@@ -372,11 +359,12 @@ void resetZ(const SelectionContext &selectionState)
if (!selectionState.view())
return;
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|resetZ"));
- foreach (ModelNode node, selectionState.selectedModelNodes()) {
- QmlItemNode itemNode(node);
- itemNode.removeProperty("z");
- }
+ selectionState.view()->executeInTransaction("DesignerActionManager|resetZ",[selectionState](){
+ foreach (ModelNode node, selectionState.selectedModelNodes()) {
+ QmlItemNode itemNode(node);
+ itemNode.removeProperty("z");
+ }
+ });
}
static inline void backupPropertyAndRemove(const ModelNode &node, const PropertyName &propertyName)
@@ -404,9 +392,7 @@ void anchorsFill(const SelectionContext &selectionState)
if (!selectionState.view())
return;
- try {
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|anchorsFill"));
-
+ selectionState.view()->executeInTransaction("DesignerActionManager|anchorsFill",[selectionState](){
ModelNode modelNode = selectionState.currentSingleSelectedNode();
QmlItemNode node = modelNode;
@@ -417,11 +403,7 @@ void anchorsFill(const SelectionContext &selectionState)
backupPropertyAndRemove(modelNode, "width");
backupPropertyAndRemove(modelNode, "height");
}
-
- transaction.commit();
- } catch (const RewritingException &e) { //better save then sorry
- e.showException();
- }
+ });
}
void anchorsReset(const SelectionContext &selectionState)
@@ -429,19 +411,19 @@ void anchorsReset(const SelectionContext &selectionState)
if (!selectionState.view())
return;
- RewriterTransaction transaction(selectionState.view(), QByteArrayLiteral("DesignerActionManager|anchorsReset"));
-
- ModelNode modelNode = selectionState.currentSingleSelectedNode();
+ selectionState.view()->executeInTransaction("DesignerActionManager|anchorsReset",[selectionState](){
+ ModelNode modelNode = selectionState.currentSingleSelectedNode();
- QmlItemNode node = modelNode;
- if (node.isValid()) {
- node.anchors().removeAnchors();
- node.anchors().removeMargins();
- restoreProperty(node, "x");
- restoreProperty(node, "y");
- restoreProperty(node, "width");
- restoreProperty(node, "height");
- }
+ QmlItemNode node = modelNode;
+ if (node.isValid()) {
+ node.anchors().removeAnchors();
+ node.anchors().removeMargins();
+ restoreProperty(node, "x");
+ restoreProperty(node, "y");
+ restoreProperty(node, "width");
+ restoreProperty(node, "height");
+ }
+ });
}
using LessThan = std::function<bool (const ModelNode &, const ModelNode&)>;
@@ -481,7 +463,7 @@ bool compareByGrid(const ModelNode &node1, const ModelNode &node2)
static void layoutHelperFunction(const SelectionContext &selectionContext,
const TypeName &layoutType,
- LessThan lessThan)
+ const LessThan &lessThan)
{
if (!selectionContext.view()
|| !selectionContext.hasSingleSelectedModelNode()
@@ -492,10 +474,8 @@ static void layoutHelperFunction(const SelectionContext &selectionContext,
const QmlItemNode qmlItemNode = QmlItemNode(selectionContext.firstSelectedModelNode());
if (qmlItemNode.hasInstanceParentItem()) {
-
ModelNode layoutNode;
- {
- RewriterTransaction transaction(selectionContext.view(), QByteArrayLiteral("DesignerActionManager|layoutHelperFunction1"));
+ selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction1",[=, &layoutNode](){
QmlItemNode parentNode = qmlItemNode.instanceParentItem();
@@ -504,10 +484,9 @@ static void layoutHelperFunction(const SelectionContext &selectionContext,
layoutNode = selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion());
reparentTo(layoutNode, parentNode);
- }
+ });
- {
- RewriterTransaction transaction(selectionContext.view(), QByteArrayLiteral("DesignerActionManager|layoutHelperFunction2"));
+ selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction2",[=](){
QList<ModelNode> sortedSelectedNodes = selectionContext.selectedModelNodes();
Utils::sort(sortedSelectedNodes, lessThan);
@@ -516,7 +495,7 @@ static void layoutHelperFunction(const SelectionContext &selectionContext,
LayoutInGridLayout::reparentToNodeAndRemovePositionForModelNodes(layoutNode, sortedSelectedNodes);
if (layoutType.contains("Layout"))
LayoutInGridLayout::setSizeAsPreferredSize(sortedSelectedNodes);
- }
+ });
}
}
}
@@ -662,21 +641,14 @@ void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState
if (!qmlObjectNode.isRootModelNode()) {
isModelNodeRoot = false;
- try {
- RewriterTransaction transaction =
- qmlObjectNode.view()->beginRewriterTransaction(QByteArrayLiteral("NavigatorTreeModel:exportItem"));
-
- QmlObjectNode qmlObjectNode(modelNode);
+ qmlObjectNode.view()->executeInTransaction("NavigatorTreeModel:exportItem", [&qmlObjectNode](){
qmlObjectNode.ensureAliasExport();
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
}
QString itemId = modelNode.id();
- const Utils::FileName currentDesignDocument = QmlDesignerPlugin::instance()->documentManager().currentDesignDocument()->fileName();
+ const Utils::FilePath currentDesignDocument = QmlDesignerPlugin::instance()->documentManager().currentDesignDocument()->fileName();
const QString fileName = currentDesignDocument.toString();
const QString typeName = currentDesignDocument.toFileInfo().baseName();
@@ -708,14 +680,10 @@ void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState
if (dialog->signal().isEmpty())
return;
- try {
- RewriterTransaction transaction =
- qmlObjectNode.view()->beginRewriterTransaction(QByteArrayLiteral("NavigatorTreeModel:exportItem"));
+ qmlObjectNode.view()->executeInTransaction("NavigatorTreeModel:exportItem", [=](){
addSignal(typeName, itemId, dialog->signal(), isModelNodeRoot);
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
addSignal(typeName, itemId, dialog->signal(), isModelNodeRoot);
@@ -751,9 +719,7 @@ void removeLayout(const SelectionContext &selectionContext)
if (!parent.isValid())
return;
- {
- RewriterTransaction transaction(selectionContext.view(), QByteArrayLiteral("DesignerActionManager|removeLayout"));
-
+ selectionContext.view()->executeInTransaction("DesignerActionManager|removeLayout", [selectionContext, &layoutItem, parent](){
foreach (const ModelNode &modelNode, selectionContext.currentSingleSelectedNode().directSubModelNodes()) {
if (QmlItemNode::isValidQmlItemNode(modelNode)) {
@@ -772,7 +738,7 @@ void removeLayout(const SelectionContext &selectionContext)
parent.modelNode().defaultNodeListProperty().reparentHere(modelNode);
}
layoutItem.destroy();
- }
+ });
}
void removePositioner(const SelectionContext &selectionContext)
@@ -826,9 +792,7 @@ void addItemToStackedContainer(const SelectionContext &selectionContext)
}
}
- try {
- RewriterTransaction transaction =
- view->beginRewriterTransaction(QByteArrayLiteral("DesignerActionManager:addItemToStackedContainer"));
+ view->executeInTransaction("DesignerActionManager:addItemToStackedContainer", [=](){
NodeMetaInfo itemMetaInfo = view->model()->metaInfo("QtQuick.Item", -1, -1);
QTC_ASSERT(itemMetaInfo.isValid(), return);
@@ -853,11 +817,7 @@ void addItemToStackedContainer(const SelectionContext &selectionContext)
}
}
-
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
}
PropertyName getIndexPropertyName(const ModelNode &modelNode)
@@ -969,9 +929,8 @@ void addTabBarToStackedContainer(const SelectionContext &selectionContext)
const PropertyName indexPropertyName = getIndexPropertyName(container);
QTC_ASSERT(container.metaInfo().hasProperty(indexPropertyName), return);
- try {
- RewriterTransaction transaction =
- view->beginRewriterTransaction(QByteArrayLiteral("DesignerActionManager:addItemToStackedContainer"));
+ view->executeInTransaction("DesignerActionManager:addItemToStackedContainer",
+ [view, container, containerItemNode, tabBarMetaInfo, tabButtonMetaInfo, indexPropertyName](){
ModelNode tabBarNode =
view->createModelNode("QtQuick.Controls.TabBar",
@@ -1003,13 +962,42 @@ void addTabBarToStackedContainer(const SelectionContext &selectionContext)
container.removeProperty(indexPropertyName);
const QString expression = id + "." + QString::fromLatin1(indexPropertyName);
container.bindingProperty(indexPropertyName).setExpression(expression);
+ });
+
+}
+
+bool addFontToProject(const QStringList &fileNames, const QString &defaultDirectory)
+{
+ QString directory = AddImagesDialog::getDirectory(fileNames, defaultDirectory);
+
+ if (directory.isEmpty())
+ return true;
+
+ bool allSuccessful = true;
+ for (const QString &fileName : fileNames) {
+ const QString targetFile = directory + "/" + QFileInfo(fileName).fileName();
+ const bool success = QFile::copy(fileName, targetFile);
+
+ auto document = QmlDesignerPlugin::instance()->currentDesignDocument();
+
+ QTC_ASSERT(document, return false);
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
+ if (success) {
+ ProjectExplorer::Node *node = ProjectExplorer::ProjectTree::nodeForFile(document->fileName());
+ if (node) {
+ ProjectExplorer::FolderNode *containingFolder = node->parentFolderNode();
+ if (containingFolder)
+ containingFolder->addFiles(QStringList(targetFile));
+ }
+ } else {
+ allSuccessful = false;
+ }
}
+
+ return allSuccessful;
}
+
bool addImageToProject(const QStringList &fileNames, const QString &defaultDirectory)
{
QString directory = AddImagesDialog::getDirectory(fileNames, defaultDirectory);
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
index e3396a303e..52dfaf6f1d 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
@@ -73,6 +73,7 @@ void increaseIndexOfStackedContainer(const SelectionContext &selectionContext);
void decreaseIndexOfStackedContainer(const SelectionContext &selectionContext);
void addTabBarToStackedContainer(const SelectionContext &selectionContext);
bool addImageToProject(const QStringList &fileNames, const QString &directory);
+bool addFontToProject(const QStringList &fileNames, const QString &directory);
} // namespace ModelNodeOperationso
} //QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp b/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp
index e82b2a22dd..434f73c0f3 100644
--- a/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp
@@ -29,7 +29,6 @@
namespace QmlDesigner {
-
SelectionContext::SelectionContext() = default;
SelectionContext::SelectionContext(AbstractView *view) :
diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
index 9a1529414a..40ad483785 100644
--- a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
@@ -30,7 +30,6 @@
namespace QmlDesigner {
-
ZoomAction::ZoomAction(QObject *parent)
: QWidgetAction(parent),
m_zoomLevel(1.0),
@@ -105,7 +104,7 @@ QWidget *ZoomAction::createWidget(QWidget *parent)
comboBox->setCurrentIndex(m_currentComboBoxIndex);
blockSignals(false);
});
- connect(comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this, comboBox](int index) {
m_currentComboBoxIndex = index;
diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp
new file mode 100644
index 0000000000..9de928ae90
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "addnewbackenddialog.h"
+#include "ui_addnewbackenddialog.h"
+
+#include <QPushButton>
+
+namespace QmlDesigner {
+
+AddNewBackendDialog::AddNewBackendDialog(QWidget *parent) :
+ QDialog(parent),
+ m_ui(new Ui::AddNewBackendDialog)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ m_ui->setupUi(this);
+
+ connect(m_ui->comboBox, &QComboBox::currentTextChanged, this, &AddNewBackendDialog::invalidate);
+
+ connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, [this]() {
+ m_applied = true;
+ close();
+ });
+
+ connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &AddNewBackendDialog::close);
+}
+
+AddNewBackendDialog::~AddNewBackendDialog()
+{
+ delete m_ui;
+}
+
+void AddNewBackendDialog::setupPossibleTypes(const QList<CppTypeData> &types)
+{
+ QSignalBlocker blocker(this);
+ m_typeData = types;
+ for (const CppTypeData &typeData : types)
+ m_ui->comboBox->addItem(typeData.typeName);
+
+ m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_ui->comboBox->count() > 0);
+ invalidate();
+}
+
+QString AddNewBackendDialog::importString() const
+{
+ if (m_ui->comboBox->currentIndex() < 0)
+ return QString();
+
+ CppTypeData typeData = m_typeData.at(m_ui->comboBox->currentIndex());
+
+ return typeData.importUrl + " " + typeData.versionString;
+}
+
+QString AddNewBackendDialog::type() const
+{
+ if (m_ui->comboBox->currentIndex() < 0)
+ return QString();
+
+ return m_typeData.at(m_ui->comboBox->currentIndex()).typeName;
+}
+
+bool AddNewBackendDialog::applied() const
+{
+ return m_applied;
+}
+
+bool AddNewBackendDialog::localDefinition() const
+{
+ return m_ui->checkBox->isChecked();
+}
+
+bool AddNewBackendDialog::isSingleton() const
+{
+ return m_isSingleton;
+}
+
+void AddNewBackendDialog::invalidate()
+{
+ if (m_ui->comboBox->currentIndex() < 0)
+ return;
+
+ CppTypeData typeData = m_typeData.at(m_ui->comboBox->currentIndex());
+ m_ui->importLabel->setText(importString());
+
+ m_ui->checkBox->setChecked(false);
+ if (typeData.isSingleton)
+ m_ui->checkBox->setEnabled(false);
+
+ m_isSingleton = typeData.isSingleton;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h
new file mode 100644
index 0000000000..4cfa46d66e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDialog>
+
+#include <rewriterview.h>
+
+namespace QmlDesigner {
+
+namespace Ui {
+class AddNewBackendDialog;
+}
+
+class AddNewBackendDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AddNewBackendDialog(QWidget *parent = nullptr);
+ ~AddNewBackendDialog() override;
+ void setupPossibleTypes(const QList<CppTypeData> &types);
+ QString importString() const;
+ QString type() const;
+ bool applied() const;
+ bool localDefinition() const;
+ bool isSingleton() const;
+
+private:
+ void invalidate();
+
+ Ui::AddNewBackendDialog *m_ui;
+ QList<CppTypeData> m_typeData;
+
+ bool m_applied = false;
+ bool m_isSingleton = false;
+};
+
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui
new file mode 100644
index 0000000000..1da43f7526
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::AddNewBackendDialog</class>
+ <widget class="QDialog" name="QmlDesigner::AddNewBackendDialog">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>412</width>
+ <height>202</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Add New C++ Backend</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>13</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="1">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QFrame" name="frame">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="comboBox"/>
+ </item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>169</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Define object locally</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QCheckBox" name="checkBox">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Required import</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="importLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>160</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_4">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Choose a type that is registered using qmlRegisterType or qmlRegisterSingletonType. The type will be available as a property in the current .qml file.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp
new file mode 100644
index 0000000000..4a004ca575
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp
@@ -0,0 +1,333 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include <utils/algorithm.h>
+
+#include "backendmodel.h"
+
+#include "bindingproperty.h"
+#include "connectionview.h"
+#include "exception.h"
+#include "nodemetainfo.h"
+#include "nodeproperty.h"
+#include "rewriterview.h"
+#include "rewritertransaction.h"
+
+#include "addnewbackenddialog.h"
+
+#include <coreplugin/icore.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+BackendModel::BackendModel(ConnectionView *parent) :
+ QStandardItemModel(parent)
+ ,m_connectionView(parent)
+{
+ connect(this, &QStandardItemModel::dataChanged, this, &BackendModel::handleDataChanged);
+}
+
+ConnectionView *QmlDesigner::Internal::BackendModel::connectionView() const
+{
+ return m_connectionView;
+}
+
+void BackendModel::resetModel()
+{
+ if (!m_connectionView->model())
+ return;
+
+ RewriterView *rewriterView = m_connectionView->model()->rewriterView();
+
+ m_lock = true;
+
+ beginResetModel();
+ clear();
+
+ setHorizontalHeaderLabels(QStringList({ tr("Type"), tr("Name"), tr("Singleton"), tr("Local") }));
+
+ ModelNode rootNode = connectionView()->rootModelNode();
+
+ static const PropertyTypeList simpleTypes = {"int", "real", "color", "string"};
+
+ if (rewriterView)
+ for (const CppTypeData &cppTypeData : rewriterView->getCppTypes())
+ if (cppTypeData.isSingleton) {
+ NodeMetaInfo metaInfo = m_connectionView->model()->metaInfo(cppTypeData.typeName.toUtf8());
+ if (metaInfo.isValid() && !metaInfo.isSubclassOf("QtQuick.Item")) {
+ auto type = new QStandardItem(cppTypeData.typeName);
+ type->setData(cppTypeData.typeName, Qt::UserRole + 1);
+ type->setData(true, Qt::UserRole + 2);
+ type->setEditable(false);
+
+ auto name = new QStandardItem(cppTypeData.typeName);
+ name->setEditable(false);
+
+ QStandardItem *singletonItem = new QStandardItem("");
+ singletonItem->setCheckState(Qt::Checked);
+
+ singletonItem->setCheckable(true);
+ singletonItem->setEnabled(false);
+
+ QStandardItem *inlineItem = new QStandardItem("");
+
+ inlineItem->setCheckState(Qt::Unchecked);
+
+ inlineItem->setCheckable(true);
+ inlineItem->setEnabled(false);
+
+ appendRow({ type, name, singletonItem, inlineItem });
+ }
+ }
+
+ if (rootNode.isValid())
+ foreach (const AbstractProperty &property ,rootNode.properties())
+ if (property.isDynamic() && !simpleTypes.contains(property.dynamicTypeName())) {
+
+ NodeMetaInfo metaInfo = m_connectionView->model()->metaInfo(property.dynamicTypeName());
+ if (metaInfo.isValid() && !metaInfo.isSubclassOf("QtQuick.Item")) {
+ QStandardItem *type = new QStandardItem(QString::fromUtf8(property.dynamicTypeName()));
+ type->setEditable(false);
+
+ type->setData(QString::fromUtf8(property.name()), Qt::UserRole + 1);
+ type->setData(false, Qt::UserRole + 2);
+ QStandardItem *name = new QStandardItem(QString::fromUtf8(property.name()));
+
+ QStandardItem *singletonItem = new QStandardItem("");
+ singletonItem->setCheckState(Qt::Unchecked);
+
+ singletonItem->setCheckable(true);
+ singletonItem->setEnabled(false);
+
+ QStandardItem *inlineItem = new QStandardItem("");
+
+ inlineItem->setCheckState(property.isNodeProperty() ? Qt::Checked : Qt::Unchecked);
+
+ inlineItem->setCheckable(true);
+ inlineItem->setEnabled(false);
+
+ appendRow({ type, name, singletonItem, inlineItem });
+ }
+ }
+
+ m_lock = false;
+
+ endResetModel();
+}
+
+QStringList BackendModel::possibleCppTypes() const
+{
+ RewriterView *rewriterView = m_connectionView->model()->rewriterView();
+
+ QStringList list;
+
+ if (rewriterView)
+ foreach (const CppTypeData &cppTypeData, rewriterView->getCppTypes())
+ list.append(cppTypeData.typeName);
+
+ return list;
+}
+
+CppTypeData BackendModel::cppTypeDataForType(const QString &typeName) const
+{
+ RewriterView *rewriterView = m_connectionView->model()->rewriterView();
+
+ if (!rewriterView)
+ return CppTypeData();
+
+ return Utils::findOr(rewriterView->getCppTypes(), CppTypeData(), [&typeName](const CppTypeData &data) {
+ return typeName == data.typeName;
+ });
+}
+
+void BackendModel::deletePropertyByRow(int rowNumber)
+{
+ Model *model = m_connectionView->model();
+ if (!model)
+ return;
+
+ /* singleton case remove the import */
+ if (data(index(rowNumber, 0), Qt::UserRole + 1).toBool()) {
+ const QString typeName = data(index(rowNumber, 0), Qt::UserRole + 1).toString();
+ CppTypeData cppTypeData = cppTypeDataForType(typeName);
+
+ if (cppTypeData.isSingleton) {
+
+ Import import = Import::createLibraryImport(cppTypeData.importUrl, cppTypeData.versionString);
+
+ try {
+ if (model->hasImport(import))
+ model->changeImports({}, {import});
+ } catch (const Exception &e) {
+ e.showException();
+ }
+ }
+ } else {
+ const QString propertyName = data(index(rowNumber, 0), Qt::UserRole + 1).toString();
+
+ ModelNode modelNode = connectionView()->rootModelNode();
+
+ try {
+ modelNode.removeProperty(propertyName.toUtf8());
+ } catch (const Exception &e) {
+ e.showException();
+ }
+ }
+
+ resetModel();
+}
+
+void BackendModel::addNewBackend()
+{
+ Model *model = m_connectionView->model();
+ if (!model)
+ return;
+
+ AddNewBackendDialog dialog(Core::ICore::mainWindow());
+
+ RewriterView *rewriterView = model->rewriterView();
+
+ QStringList availableTypes;
+
+ if (rewriterView)
+ dialog.setupPossibleTypes(Utils::filtered(rewriterView->getCppTypes(), [model](const CppTypeData &cppTypeData) {
+ return !cppTypeData.isSingleton || !model->metaInfo(cppTypeData.typeName.toUtf8()).isValid();
+ /* Only show singletons if the import is missing */
+ }));
+
+ dialog.exec();
+
+ if (dialog.applied()) {
+ QStringList importSplit = dialog.importString().split(" ");
+ if (importSplit.count() != 2) {
+ qWarning() << Q_FUNC_INFO << "invalid import" << importSplit;
+ QTC_ASSERT(false, return);
+ }
+
+ QString typeName = dialog.type();
+
+ Import import = Import::createLibraryImport(importSplit.constFirst(), importSplit.constLast());
+
+ /* We cannot add an import and add a node from that import in a single transaction.
+ * We need the import to have the meta info available.
+ */
+
+ if (!model->hasImport(import))
+ model->changeImports({import}, {});
+
+ QString propertyName = m_connectionView->generateNewId(typeName);
+
+ NodeMetaInfo metaInfo = model->metaInfo(typeName.toUtf8());
+
+ QTC_ASSERT(metaInfo.isValid(), return);
+
+ /* Add a property for non singleton types. For singletons just adding the import is enough. */
+ if (!dialog.isSingleton()) {
+ m_connectionView->executeInTransaction("BackendModel::addNewBackend", [=, &dialog](){
+ int minorVersion = metaInfo.minorVersion();
+ int majorVersion = metaInfo.majorVersion();
+
+ if (dialog.localDefinition()) {
+ ModelNode newNode = m_connectionView->createModelNode(metaInfo.typeName(), majorVersion, minorVersion);
+
+ m_connectionView->rootModelNode().nodeProperty(propertyName.toUtf8()).setDynamicTypeNameAndsetModelNode(
+ typeName.toUtf8(), newNode);
+ } else {
+ m_connectionView->rootModelNode().bindingProperty(
+ propertyName.toUtf8()).setDynamicTypeNameAndExpression(typeName.toUtf8(), "null");
+ }
+ });
+ }
+ }
+ resetModel();
+}
+
+void BackendModel::updatePropertyName(int rowNumber)
+{
+ const PropertyName newName = data(index(rowNumber, 1)).toString().toUtf8();
+ const PropertyName oldName = data(index(rowNumber, 0), Qt::UserRole + 1).toString().toUtf8();
+
+ m_connectionView->executeInTransaction("BackendModel::updatePropertyName", [this, newName, oldName](){
+
+ ModelNode rootModelNode = m_connectionView->rootModelNode();
+ if (rootModelNode.property(oldName).isNodeProperty()) {
+
+ const TypeName typeName = rootModelNode.nodeProperty(oldName).dynamicTypeName();
+ const ModelNode targetModelNode = rootModelNode.nodeProperty(oldName).modelNode();
+ const TypeName fullTypeName = targetModelNode.type();
+ const int majorVersion = targetModelNode.majorVersion();
+ const int minorVersion = targetModelNode.minorVersion();
+
+ rootModelNode.removeProperty(oldName);
+ ModelNode newNode = m_connectionView->createModelNode(fullTypeName, majorVersion, minorVersion);
+ m_connectionView->rootModelNode().nodeProperty(newName).setDynamicTypeNameAndsetModelNode(typeName, newNode);
+
+ } else if (rootModelNode.property(oldName).isBindingProperty()) {
+ const QString expression = rootModelNode.bindingProperty(oldName).expression();
+ const TypeName typeName = rootModelNode.bindingProperty(oldName).dynamicTypeName();
+
+ rootModelNode.removeProperty(oldName);
+ rootModelNode.bindingProperty(newName).setDynamicTypeNameAndExpression(typeName, expression);
+ } else {
+ qWarning() << Q_FUNC_INFO << oldName << newName << "failed...";
+ QTC_ASSERT(false, return);
+ }
+ });
+}
+
+void BackendModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (m_lock)
+ return;
+
+ if (topLeft != bottomRight) {
+ qWarning() << "BackendModel::handleDataChanged multi edit?";
+ return;
+ }
+
+ m_lock = true;
+
+ int currentColumn = topLeft.column();
+ int currentRow = topLeft.row();
+
+ switch (currentColumn) {
+ case 0: {
+ //updating user data
+ } break;
+ case 1: {
+ updatePropertyName(currentRow);
+ } break;
+
+ default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn;
+ }
+
+ m_lock = false;
+}
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h
new file mode 100644
index 0000000000..8abbbe77fa
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+#include <QStandardItemModel>
+
+#include "rewriterview.h"
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+class ConnectionView;
+
+class BackendModel : public QStandardItemModel
+{
+ Q_OBJECT
+public:
+ enum ColumnRoles {
+ TypeNameColumn = 0,
+ PropertyNameColumn = 1,
+ IsSingletonColumn = 2,
+ IsLocalColumn = 3,
+ };
+
+ BackendModel(ConnectionView *parent);
+
+ ConnectionView *connectionView() const;
+
+ void resetModel();
+
+ QStringList possibleCppTypes() const;
+ CppTypeData cppTypeDataForType(const QString &typeName) const;
+
+ void deletePropertyByRow(int rowNumber);
+
+ void addNewBackend();
+
+protected:
+ void updatePropertyName(int rowNumber);
+
+private:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
+
+private:
+ ConnectionView *m_connectionView;
+ bool m_lock = false;
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp
new file mode 100644
index 0000000000..2cff12b044
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp
@@ -0,0 +1,446 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "bindingmodel.h"
+
+#include "connectionview.h"
+
+#include <nodemetainfo.h>
+#include <nodeproperty.h>
+#include <bindingproperty.h>
+#include <variantproperty.h>
+#include <rewritingexception.h>
+#include <rewritertransaction.h>
+
+#include <QMessageBox>
+#include <QTimer>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+BindingModel::BindingModel(ConnectionView *parent)
+ : QStandardItemModel(parent)
+ , m_connectionView(parent)
+{
+ connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged);
+}
+
+void BindingModel::resetModel()
+{
+ beginResetModel();
+ clear();
+ setHorizontalHeaderLabels(QStringList({ tr("Item"), tr("Property"), tr("Source Item"),
+ tr("Source Property") }));
+
+ foreach (const ModelNode modelNode, m_selectedModelNodes)
+ addModelNode(modelNode);
+
+ endResetModel();
+}
+
+void BindingModel::bindingChanged(const BindingProperty &bindingProperty)
+{
+ m_handleDataChanged = false;
+
+ QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
+ if (!selectedNodes.contains(bindingProperty.parentModelNode()))
+ return;
+ if (!m_lock) {
+ int rowNumber = findRowForBinding(bindingProperty);
+
+ if (rowNumber == -1) {
+ addBindingProperty(bindingProperty);
+ } else {
+ updateBindingProperty(rowNumber);
+ }
+ }
+
+ m_handleDataChanged = true;
+}
+
+void BindingModel::bindingRemoved(const BindingProperty &bindingProperty)
+{
+ m_handleDataChanged = false;
+
+ QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
+ if (!selectedNodes.contains(bindingProperty.parentModelNode()))
+ return;
+ if (!m_lock) {
+ int rowNumber = findRowForBinding(bindingProperty);
+ removeRow(rowNumber);
+ }
+
+ m_handleDataChanged = true;
+}
+
+void BindingModel::selectionChanged(const QList<ModelNode> &selectedNodes)
+{
+ m_handleDataChanged = false;
+ m_selectedModelNodes = selectedNodes;
+ resetModel();
+ m_handleDataChanged = true;
+}
+
+ConnectionView *BindingModel::connectionView() const
+{
+ return m_connectionView;
+}
+
+BindingProperty BindingModel::bindingPropertyForRow(int rowNumber) const
+{
+
+ const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
+ const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
+
+ ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
+
+ if (modelNode.isValid())
+ return modelNode.bindingProperty(targetPropertyName.toLatin1());
+
+ return BindingProperty();
+}
+
+QStringList BindingModel::possibleTargetProperties(const BindingProperty &bindingProperty) const
+{
+ const ModelNode modelNode = bindingProperty.parentModelNode();
+
+ if (!modelNode.isValid()) {
+ qWarning() << " BindingModel::possibleTargetPropertiesForRow invalid model node";
+ return QStringList();
+ }
+
+ NodeMetaInfo metaInfo = modelNode.metaInfo();
+
+ if (metaInfo.isValid()) {
+ QStringList possibleProperties;
+ foreach (const PropertyName &propertyName, metaInfo.propertyNames()) {
+ if (metaInfo.propertyIsWritable(propertyName))
+ possibleProperties << QString::fromUtf8(propertyName);
+ }
+
+ return possibleProperties;
+ }
+
+ return QStringList();
+}
+
+QStringList BindingModel::possibleSourceProperties(const BindingProperty &bindingProperty) const
+{
+ const QString expression = bindingProperty.expression();
+ const QStringList stringlist = expression.split(QLatin1String("."));
+
+ TypeName typeName;
+
+ if (bindingProperty.parentModelNode().metaInfo().isValid()) {
+ typeName = bindingProperty.parentModelNode().metaInfo().propertyTypeName(bindingProperty.name());
+ } else {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for target node";
+ }
+
+ const QString &id = stringlist.constFirst();
+
+ ModelNode modelNode = getNodeByIdOrParent(id, bindingProperty.parentModelNode());
+
+ if (!modelNode.isValid()) {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow invalid model node";
+ return QStringList();
+ }
+
+ NodeMetaInfo metaInfo = modelNode.metaInfo();
+
+ QStringList possibleProperties;
+
+ foreach (VariantProperty variantProperty, modelNode.variantProperties()) {
+ if (variantProperty.isDynamic())
+ possibleProperties << QString::fromUtf8(variantProperty.name());
+ }
+
+ foreach (BindingProperty bindingProperty, modelNode.bindingProperties()) {
+ if (bindingProperty.isDynamic())
+ possibleProperties << QString::fromUtf8((bindingProperty.name()));
+ }
+
+ if (metaInfo.isValid()) {
+ foreach (const PropertyName &propertyName, metaInfo.propertyNames()) {
+ if (metaInfo.propertyTypeName(propertyName) == typeName) //### todo proper check
+ possibleProperties << QString::fromUtf8(propertyName);
+ }
+ } else {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for source node";
+ }
+
+ return possibleProperties;
+}
+
+void BindingModel::deleteBindindByRow(int rowNumber)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ if (bindingProperty.isValid()) {
+ bindingProperty.parentModelNode().removeProperty(bindingProperty.name());
+ }
+
+ resetModel();
+}
+
+static PropertyName unusedProperty(const ModelNode &modelNode)
+{
+ PropertyName propertyName = "none";
+ if (modelNode.metaInfo().isValid()) {
+ foreach (const PropertyName &propertyName, modelNode.metaInfo().propertyNames()) {
+ if (modelNode.metaInfo().propertyIsWritable(propertyName) && !modelNode.hasProperty(propertyName))
+ return propertyName;
+ }
+ }
+
+ return propertyName;
+}
+
+void BindingModel::addBindingForCurrentNode()
+{
+ if (connectionView()->selectedModelNodes().count() == 1) {
+ const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
+ if (modelNode.isValid()) {
+ try {
+ modelNode.bindingProperty(unusedProperty(modelNode)).setExpression(QLatin1String("none.none"));
+ } catch (RewritingException &e) {
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &BindingModel::handleException);
+ }
+ }
+ } else {
+ qWarning() << " BindingModel::addBindingForCurrentNode not one node selected";
+ }
+}
+
+void BindingModel::addBindingProperty(const BindingProperty &property)
+{
+ QStandardItem *idItem;
+ QStandardItem *targetPropertyNameItem;
+ QStandardItem *sourceIdItem;
+ QStandardItem *sourcePropertyNameItem;
+
+ QString idLabel = property.parentModelNode().id();
+ if (idLabel.isEmpty())
+ idLabel = property.parentModelNode().simplifiedTypeName();
+ idItem = new QStandardItem(idLabel);
+ updateCustomData(idItem, property);
+ targetPropertyNameItem = new QStandardItem(QString::fromUtf8(property.name()));
+ QList<QStandardItem*> items;
+
+ items.append(idItem);
+ items.append(targetPropertyNameItem);
+
+ QString sourceNodeName;
+ QString sourcePropertyName;
+ getExpressionStrings(property, &sourceNodeName, &sourcePropertyName);
+
+ sourceIdItem = new QStandardItem(sourceNodeName);
+ sourcePropertyNameItem = new QStandardItem(sourcePropertyName);
+
+ items.append(sourceIdItem);
+ items.append(sourcePropertyNameItem);
+ appendRow(items);
+}
+
+void BindingModel::updateBindingProperty(int rowNumber)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ if (bindingProperty.isValid()) {
+ QString targetPropertyName = QString::fromUtf8(bindingProperty.name());
+ updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName);
+ QString sourceNodeName;
+ QString sourcePropertyName;
+ getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName);
+ updateDisplayRole(rowNumber, SourceModelNodeRow, sourceNodeName);
+ updateDisplayRole(rowNumber, SourcePropertyNameRow, sourcePropertyName);
+ }
+}
+
+void BindingModel::addModelNode(const ModelNode &modelNode)
+{
+ foreach (const BindingProperty &bindingProperty, modelNode.bindingProperties()) {
+ addBindingProperty(bindingProperty);
+ }
+}
+
+void BindingModel::updateExpression(int row)
+{
+ const QString sourceNode = data(index(row, SourceModelNodeRow)).toString().trimmed();
+ const QString sourceProperty = data(index(row, SourcePropertyNameRow)).toString().trimmed();
+
+ QString expression;
+ if (sourceProperty.isEmpty()) {
+ expression = sourceNode;
+ } else {
+ expression = sourceNode + QLatin1String(".") + sourceProperty;
+ }
+
+ connectionView()->executeInTransaction("BindingModel::updateExpression", [this, row, expression](){
+ BindingProperty bindingProperty = bindingPropertyForRow(row);
+ bindingProperty.setExpression(expression.trimmed());
+ });
+}
+
+void BindingModel::updatePropertyName(int rowNumber)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ const PropertyName newName = data(index(rowNumber, TargetPropertyNameRow)).toString().toUtf8();
+ const QString expression = bindingProperty.expression();
+ const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName();
+ ModelNode targetNode = bindingProperty.parentModelNode();
+
+ if (!newName.isEmpty()) {
+ RewriterTransaction transaction =
+ connectionView()->beginRewriterTransaction(QByteArrayLiteral("BindingModel::updatePropertyName"));
+ try {
+ if (bindingProperty.isDynamic()) {
+ targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, expression);
+ } else {
+ targetNode.bindingProperty(newName).setExpression(expression);
+ }
+ targetNode.removeProperty(bindingProperty.name());
+ transaction.commit(); //committing in the try block
+ } catch (Exception &e) { //better save then sorry
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &BindingModel::handleException);
+ }
+
+ QStandardItem* idItem = item(rowNumber, 0);
+ BindingProperty newBindingProperty = targetNode.bindingProperty(newName);
+ updateCustomData(idItem, newBindingProperty);
+
+ } else {
+ qWarning() << "BindingModel::updatePropertyName invalid property name";
+ }
+}
+
+ModelNode BindingModel::getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const
+{
+ ModelNode modelNode;
+
+ if (id != QLatin1String("parent")) {
+ modelNode = connectionView()->modelNodeForId(id);
+ } else {
+ if (targetNode.hasParentProperty()) {
+ modelNode = targetNode.parentProperty().parentModelNode();
+ }
+ }
+ return modelNode;
+}
+
+void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty)
+{
+ item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1);
+ item->setData(bindingProperty.name(), Qt::UserRole + 2);
+}
+
+int BindingModel::findRowForBinding(const BindingProperty &bindingProperty)
+{
+ for (int i=0; i < rowCount(); i++) {
+ if (compareBindingProperties(bindingPropertyForRow(i), bindingProperty))
+ return i;
+ }
+ //not found
+ return -1;
+}
+
+bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty)
+{
+ //### todo we assume no expressions yet
+
+ const QString expression = bindingProperty.expression();
+
+ if (true) {
+ const QStringList stringList = expression.split(QLatin1String("."));
+
+ *sourceNode = stringList.constFirst();
+
+ QString propertyName;
+
+ for (int i=1; i < stringList.count(); i++) {
+ propertyName += stringList.at(i);
+ if (i != stringList.count() - 1)
+ propertyName += QLatin1String(".");
+ }
+ *sourceProperty = propertyName;
+ }
+ return true;
+}
+
+void BindingModel::updateDisplayRole(int row, int columns, const QString &string)
+{
+ QModelIndex modelIndex = index(row, columns);
+ if (data(modelIndex).toString() != string)
+ setData(modelIndex, string);
+}
+
+void BindingModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (!m_handleDataChanged)
+ return;
+
+ if (topLeft != bottomRight) {
+ qWarning() << "BindingModel::handleDataChanged multi edit?";
+ return;
+ }
+
+ m_lock = true;
+
+ int currentColumn = topLeft.column();
+ int currentRow = topLeft.row();
+
+ switch (currentColumn) {
+ case TargetModelNodeRow: {
+ //updating user data
+ } break;
+ case TargetPropertyNameRow: {
+ updatePropertyName(currentRow);
+ } break;
+ case SourceModelNodeRow: {
+ updateExpression(currentRow);
+ } break;
+ case SourcePropertyNameRow: {
+ updateExpression(currentRow);
+ } break;
+
+ default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn;
+ }
+
+ m_lock = false;
+}
+
+void BindingModel::handleException()
+{
+ QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
+ resetModel();
+}
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h
new file mode 100644
index 0000000000..480ba254ad
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <modelnode.h>
+#include <bindingproperty.h>
+#include <variantproperty.h>
+
+#include <QStandardItemModel>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+class ConnectionView;
+
+class BindingModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+public:
+ enum ColumnRoles {
+ TargetModelNodeRow = 0,
+ TargetPropertyNameRow = 1,
+ SourceModelNodeRow = 2,
+ SourcePropertyNameRow = 3
+ };
+ BindingModel(ConnectionView *parent = nullptr);
+ void bindingChanged(const BindingProperty &bindingProperty);
+ void bindingRemoved(const BindingProperty &bindingProperty);
+ void selectionChanged(const QList<ModelNode> &selectedNodes);
+
+ ConnectionView *connectionView() const;
+ BindingProperty bindingPropertyForRow(int rowNumber) const;
+ QStringList possibleTargetProperties(const BindingProperty &bindingProperty) const;
+ QStringList possibleSourceProperties(const BindingProperty &bindingProperty) const;
+ void deleteBindindByRow(int rowNumber);
+ void addBindingForCurrentNode();
+ void resetModel();
+
+protected:
+ void addBindingProperty(const BindingProperty &property);
+ void updateBindingProperty(int rowNumber);
+ void addModelNode(const ModelNode &modelNode);
+ void updateExpression(int row);
+ void updatePropertyName(int rowNumber);
+ ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const;
+ void updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty);
+ int findRowForBinding(const BindingProperty &bindingProperty);
+
+ bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty);
+
+ void updateDisplayRole(int row, int columns, const QString &string);
+
+private:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
+ void handleException();
+
+private:
+ QList<ModelNode> m_selectedModelNodes;
+ ConnectionView *m_connectionView;
+ bool m_lock = false;
+ bool m_handleDataChanged = false;
+ QString m_exceptionError;
+
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri
new file mode 100644
index 0000000000..5dfa3160ab
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri
@@ -0,0 +1,26 @@
+VPATH += $$PWD
+INCLUDEPATH += $$PWD
+
+HEADERS += delegates.h \
+ connectionview.h \
+ connectionviewwidget.h \
+ connectionmodel.h \
+ bindingmodel.h \
+ dynamicpropertiesmodel.h \
+ backendmodel.h \
+ $$PWD/addnewbackenddialog.h
+
+SOURCES += delegates.cpp \
+ connectionview.cpp \
+ connectionviewwidget.cpp \
+ connectionmodel.cpp \
+ bindingmodel.cpp \
+ dynamicpropertiesmodel.cpp \
+ backendmodel.cpp \
+ $$PWD/addnewbackenddialog.cpp
+
+FORMS += \
+ connectionviewwidget.ui \
+ $$PWD/addnewbackenddialog.ui
+
+RESOURCES += connectioneditor.qrc
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc
new file mode 100644
index 0000000000..a313b6648d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/connectionview">
+ <file>stylesheet.css</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp
new file mode 100644
index 0000000000..80caf51ce0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp
@@ -0,0 +1,357 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "connectionmodel.h"
+#include "connectionview.h"
+
+#include <bindingproperty.h>
+#include <variantproperty.h>
+#include <signalhandlerproperty.h>
+#include <rewritertransaction.h>
+#include <nodeabstractproperty.h>
+#include <exception.h>
+#include <nodemetainfo.h>
+
+#include <QStandardItemModel>
+#include <QMessageBox>
+#include <QTableView>
+#include <QTimer>
+
+namespace {
+
+QStringList propertyNameListToStringList(const QmlDesigner::PropertyNameList &propertyNameList)
+{
+ QStringList stringList;
+ foreach (QmlDesigner::PropertyName propertyName, propertyNameList) {
+ stringList << QString::fromUtf8(propertyName);
+ }
+ return stringList;
+}
+
+bool isConnection(const QmlDesigner::ModelNode &modelNode)
+{
+ return (modelNode.type() == "Connections"
+ || modelNode.type() == "QtQuick.Connections"
+ || modelNode.type() == "Qt.Connections");
+
+}
+
+} //namespace
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+ConnectionModel::ConnectionModel(ConnectionView *parent)
+ : QStandardItemModel(parent)
+ , m_connectionView(parent)
+{
+ connect(this, &QStandardItemModel::dataChanged, this, &ConnectionModel::handleDataChanged);
+}
+
+void ConnectionModel::resetModel()
+{
+ beginResetModel();
+ clear();
+ setHorizontalHeaderLabels(QStringList({ tr("Target"), tr("Signal Handler"), tr("Action") }));
+
+ if (connectionView()->isAttached()) {
+ foreach (const ModelNode modelNode, connectionView()->allModelNodes())
+ addModelNode(modelNode);
+ }
+
+ const int columnWidthTarget = connectionView()->connectionTableView()->columnWidth(0);
+ connectionView()->connectionTableView()->setColumnWidth(0, columnWidthTarget - 80);
+
+ endResetModel();
+}
+
+SignalHandlerProperty ConnectionModel::signalHandlerPropertyForRow(int rowNumber) const
+{
+ const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
+ const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
+
+ ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
+
+ if (modelNode.isValid())
+ return modelNode.signalHandlerProperty(targetPropertyName.toUtf8());
+
+ return SignalHandlerProperty();
+}
+
+void ConnectionModel::addModelNode(const ModelNode &modelNode)
+{
+ if (isConnection(modelNode))
+ addConnection(modelNode);
+}
+
+void ConnectionModel::addConnection(const ModelNode &modelNode)
+{
+ foreach (const AbstractProperty &property, modelNode.properties()) {
+ if (property.isSignalHandlerProperty() && property.name() != "target") {
+ addSignalHandler(property.toSignalHandlerProperty());
+ }
+ }
+}
+
+void ConnectionModel::addSignalHandler(const SignalHandlerProperty &signalHandlerProperty)
+{
+ QStandardItem *targetItem;
+ QStandardItem *signalItem;
+ QStandardItem *actionItem;
+
+ QString idLabel;
+
+ ModelNode connectionsModelNode = signalHandlerProperty.parentModelNode();
+
+ if (connectionsModelNode.bindingProperty("target").isValid()) {
+ idLabel =connectionsModelNode.bindingProperty("target").expression();
+ }
+
+ targetItem = new QStandardItem(idLabel);
+ updateCustomData(targetItem, signalHandlerProperty);
+ const QString propertyName = QString::fromUtf8(signalHandlerProperty.name());
+ const QString source = signalHandlerProperty.source();
+
+ signalItem = new QStandardItem(propertyName);
+ QList<QStandardItem*> items;
+
+ items.append(targetItem);
+ items.append(signalItem);
+
+ actionItem = new QStandardItem(source);
+
+ items.append(actionItem);
+
+ appendRow(items);
+}
+
+void ConnectionModel::removeModelNode(const ModelNode &modelNode)
+{
+ if (isConnection(modelNode))
+ removeConnection(modelNode);
+}
+
+void ConnectionModel::removeConnection(const ModelNode & /*modelNode*/)
+{
+ Q_ASSERT_X(false, "not implemented", Q_FUNC_INFO);
+}
+
+void ConnectionModel::updateSource(int row)
+{
+ SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(row);
+
+ const QString sourceString = data(index(row, SourceRow)).toString();
+
+ RewriterTransaction transaction =
+ connectionView()->beginRewriterTransaction(QByteArrayLiteral("ConnectionModel::updateSource"));
+
+ try {
+ signalHandlerProperty.setSource(sourceString);
+ transaction.commit();
+ }
+ catch (Exception &e) {
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &ConnectionModel::handleException);
+ }
+
+}
+
+void ConnectionModel::updateSignalName(int rowNumber)
+{
+ SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(rowNumber);
+ ModelNode connectionNode = signalHandlerProperty.parentModelNode();
+
+ const PropertyName newName = data(index(rowNumber, TargetPropertyNameRow)).toString().toUtf8();
+ if (!newName.isEmpty()) {
+ connectionView()->executeInTransaction("ConnectionModel::updateSignalName", [=, &connectionNode](){
+
+ const QString source = signalHandlerProperty.source();
+
+ connectionNode.signalHandlerProperty(newName).setSource(source);
+ connectionNode.removeProperty(signalHandlerProperty.name());
+ });
+
+ QStandardItem* idItem = item(rowNumber, 0);
+ SignalHandlerProperty newSignalHandlerProperty = connectionNode.signalHandlerProperty(newName);
+ updateCustomData(idItem, newSignalHandlerProperty);
+ } else {
+ qWarning() << "BindingModel::updatePropertyName invalid property name";
+ }
+}
+
+void ConnectionModel::updateTargetNode(int rowNumber)
+{
+ SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(rowNumber);
+ const QString newTarget = data(index(rowNumber, TargetModelNodeRow)).toString();
+ ModelNode connectionNode = signalHandlerProperty.parentModelNode();
+
+ if (!newTarget.isEmpty()) {
+ connectionView()->executeInTransaction("ConnectionModel::updateTargetNode", [= ,&connectionNode](){
+ connectionNode.bindingProperty("target").setExpression(newTarget);
+ });
+
+ QStandardItem* idItem = item(rowNumber, 0);
+ updateCustomData(idItem, signalHandlerProperty);
+
+ } else {
+ qWarning() << "BindingModel::updatePropertyName invalid target id";
+ }
+}
+
+void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty)
+{
+ item->setData(signalHandlerProperty.parentModelNode().internalId(), Qt::UserRole + 1);
+ item->setData(signalHandlerProperty.name(), Qt::UserRole + 2);
+}
+
+ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const
+{
+ BindingProperty bindingProperty = connection.bindingProperty("target");
+
+ if (bindingProperty.isValid()) {
+ if (bindingProperty.expression() == QLatin1String("parent"))
+ return connection.parentProperty().parentModelNode();
+ return connectionView()->modelNodeForId(bindingProperty.expression());
+ }
+
+ return ModelNode();
+}
+
+void ConnectionModel::addConnection()
+{
+ ModelNode rootModelNode = connectionView()->rootModelNode();
+
+ if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) {
+
+ NodeMetaInfo nodeMetaInfo = connectionView()->model()->metaInfo("QtQuick.Connections");
+
+ if (nodeMetaInfo.isValid()) {
+ connectionView()->executeInTransaction("ConnectionModel::addConnection", [=](){
+ ModelNode newNode = connectionView()->createModelNode("QtQuick.Connections",
+ nodeMetaInfo.majorVersion(),
+ nodeMetaInfo.minorVersion());
+
+ rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode);
+ newNode.signalHandlerProperty("onClicked").setSource(QLatin1String("print(\"clicked\")"));
+
+ if (connectionView()->selectedModelNodes().count() == 1
+ && !connectionView()->selectedModelNodes().constFirst().id().isEmpty()) {
+ const ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst();
+ newNode.bindingProperty("target").setExpression(selectedNode.id());
+ } else {
+ newNode.bindingProperty("target").setExpression(QLatin1String("parent"));
+ }
+ });
+ }
+ }
+}
+
+void ConnectionModel::bindingPropertyChanged(const BindingProperty &bindingProperty)
+{
+ if (isConnection(bindingProperty.parentModelNode()))
+ resetModel();
+}
+
+void ConnectionModel::variantPropertyChanged(const VariantProperty &variantProperty)
+{
+ if (isConnection(variantProperty.parentModelNode()))
+ resetModel();
+}
+
+void ConnectionModel::deleteConnectionByRow(int currentRow)
+{
+ signalHandlerPropertyForRow(currentRow).parentModelNode().destroy();
+}
+
+void ConnectionModel::handleException()
+{
+ QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
+ resetModel();
+}
+
+void ConnectionModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (topLeft != bottomRight) {
+ qWarning() << "ConnectionModel::handleDataChanged multi edit?";
+ return;
+ }
+
+ m_lock = true;
+
+ int currentColumn = topLeft.column();
+ int currentRow = topLeft.row();
+
+ switch (currentColumn) {
+ case TargetModelNodeRow: {
+ updateTargetNode(currentRow);
+ } break;
+ case TargetPropertyNameRow: {
+ updateSignalName(currentRow);
+ } break;
+ case SourceRow: {
+ updateSource(currentRow);
+ } break;
+
+ default: qWarning() << "ConnectionModel::handleDataChanged column" << currentColumn;
+ }
+
+ m_lock = false;
+}
+
+ConnectionView *ConnectionModel::connectionView() const
+{
+ return m_connectionView;
+}
+
+QStringList ConnectionModel::getSignalsForRow(int row) const
+{
+ QStringList stringList;
+ SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(row);
+
+ if (signalHandlerProperty.isValid()) {
+ stringList.append(getPossibleSignalsForConnection(signalHandlerProperty.parentModelNode()));
+ }
+
+ return stringList;
+}
+
+QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &connection) const
+{
+ QStringList stringList;
+
+ if (connection.isValid()) {
+ ModelNode targetNode = getTargetNodeForConnection(connection);
+ if (targetNode.isValid() && targetNode.metaInfo().isValid()) {
+ stringList.append(propertyNameListToStringList(targetNode.metaInfo().signalNames()));
+ }
+ }
+
+ return stringList;
+}
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h
new file mode 100644
index 0000000000..2c66b2ef25
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QStandardItemModel>
+
+namespace QmlDesigner {
+
+class ModelNode;
+class BindingProperty;
+class SignalHandlerProperty;
+class VariantProperty;
+
+namespace Internal {
+
+class ConnectionView;
+
+class ConnectionModel : public QStandardItemModel
+{
+ Q_OBJECT
+public:
+ enum ColumnRoles {
+ TargetModelNodeRow = 0,
+ TargetPropertyNameRow = 1,
+ SourceRow = 2
+ };
+ ConnectionModel(ConnectionView *parent = nullptr);
+ void resetModel();
+ SignalHandlerProperty signalHandlerPropertyForRow(int rowNumber) const;
+ ConnectionView *connectionView() const;
+
+ QStringList getSignalsForRow(int row) const;
+ ModelNode getTargetNodeForConnection(const ModelNode &connection) const;
+
+ void addConnection();
+
+ void bindingPropertyChanged(const BindingProperty &bindingProperty);
+ void variantPropertyChanged(const VariantProperty &variantProperty);
+
+ void deleteConnectionByRow(int currentRow);
+
+protected:
+ void addModelNode(const ModelNode &modelNode);
+ void addConnection(const ModelNode &modelNode);
+ void addSignalHandler(const SignalHandlerProperty &bindingProperty);
+ void removeModelNode(const ModelNode &modelNode);
+ void removeConnection(const ModelNode &modelNode);
+ void updateSource(int row);
+ void updateSignalName(int rowNumber);
+ void updateTargetNode(int rowNumber);
+ void updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty);
+ QStringList getPossibleSignalsForConnection(const ModelNode &connection) const;
+
+private:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
+ void handleException();
+
+private:
+ ConnectionView *m_connectionView;
+ bool m_lock = false;
+ QString m_exceptionError;
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp
new file mode 100644
index 0000000000..9aef01259e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp
@@ -0,0 +1,222 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "connectionview.h"
+#include "connectionviewwidget.h"
+
+#include "backendmodel.h"
+#include "bindingmodel.h"
+#include "connectionmodel.h"
+#include "dynamicpropertiesmodel.h"
+
+#include <bindingproperty.h>
+#include <nodeabstractproperty.h>
+#include <variantproperty.h>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+ConnectionView::ConnectionView(QObject *parent) : AbstractView(parent),
+ m_connectionViewWidget(new ConnectionViewWidget()),
+ m_connectionModel(new ConnectionModel(this)),
+ m_bindingModel(new BindingModel(this)),
+ m_dynamicPropertiesModel(new DynamicPropertiesModel(this)),
+ m_backendModel(new BackendModel(this))
+{
+ connectionViewWidget()->setBindingModel(m_bindingModel);
+ connectionViewWidget()->setConnectionModel(m_connectionModel);
+ connectionViewWidget()->setDynamicPropertiesModel(m_dynamicPropertiesModel);
+ connectionViewWidget()->setBackendModel(m_backendModel);
+}
+
+ConnectionView::~ConnectionView() = default;
+
+void ConnectionView::modelAttached(Model *model)
+{
+ AbstractView::modelAttached(model);
+ bindingModel()->selectionChanged(QList<ModelNode>());
+ dynamicPropertiesModel()->selectionChanged(QList<ModelNode>());
+ connectionModel()->resetModel();
+ connectionViewWidget()->resetItemViews();
+ backendModel()->resetModel();
+}
+
+void ConnectionView::modelAboutToBeDetached(Model *model)
+{
+ AbstractView::modelAboutToBeDetached(model);
+ bindingModel()->selectionChanged(QList<ModelNode>());
+ dynamicPropertiesModel()->selectionChanged(QList<ModelNode>());
+ connectionModel()->resetModel();
+ connectionViewWidget()->resetItemViews();
+}
+
+void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/)
+{
+//bindings
+ connectionModel()->resetModel();
+}
+
+void ConnectionView::nodeRemoved(const ModelNode & /*removedNode*/,
+ const NodeAbstractProperty & /*parentProperty*/,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ connectionModel()->resetModel();
+}
+
+void ConnectionView::nodeReparented(const ModelNode & /*node*/, const NodeAbstractProperty & /*newPropertyParent*/,
+ const NodeAbstractProperty & /*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ connectionModel()->resetModel();
+}
+
+void ConnectionView::nodeIdChanged(const ModelNode & /*node*/, const QString & /*newId*/, const QString & /*oldId*/)
+{
+ connectionModel()->resetModel();
+ bindingModel()->resetModel();
+ dynamicPropertiesModel()->resetModel();
+}
+
+void ConnectionView::propertiesAboutToBeRemoved(const QList<AbstractProperty> & propertyList)
+{
+ foreach (const AbstractProperty &property, propertyList) {
+ if (property.isBindingProperty()) {
+ bindingModel()->bindingRemoved(property.toBindingProperty());
+ dynamicPropertiesModel()->bindingRemoved(property.toBindingProperty());
+ } else if (property.isVariantProperty()) {
+ //### dynamicPropertiesModel->bindingRemoved(property.toVariantProperty());
+ }
+ }
+}
+
+void ConnectionView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ foreach (const VariantProperty &variantProperty, propertyList) {
+ if (variantProperty.isDynamic())
+ dynamicPropertiesModel()->variantPropertyChanged(variantProperty);
+ if (variantProperty.isDynamic() && variantProperty.parentModelNode().isRootNode())
+ backendModel()->resetModel();
+
+ connectionModel()->variantPropertyChanged(variantProperty);
+ }
+
+}
+
+void ConnectionView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ foreach (const BindingProperty &bindingProperty, propertyList) {
+ bindingModel()->bindingChanged(bindingProperty);
+ if (bindingProperty.isDynamic())
+ dynamicPropertiesModel()->bindingPropertyChanged(bindingProperty);
+ if (bindingProperty.isDynamic() && bindingProperty.parentModelNode().isRootNode())
+ backendModel()->resetModel();
+
+ connectionModel()->bindingPropertyChanged(bindingProperty);
+ }
+}
+
+void ConnectionView::selectedNodesChanged(const QList<ModelNode> & selectedNodeList,
+ const QList<ModelNode> & /*lastSelectedNodeList*/)
+{
+ bindingModel()->selectionChanged(selectedNodeList);
+ dynamicPropertiesModel()->selectionChanged(selectedNodeList);
+ connectionViewWidget()->bindingTableViewSelectionChanged(QModelIndex(), QModelIndex());
+ connectionViewWidget()->dynamicPropertiesTableViewSelectionChanged(QModelIndex(), QModelIndex());
+
+ if (connectionViewWidget()->currentTab() == ConnectionViewWidget::BindingTab
+ || connectionViewWidget()->currentTab() == ConnectionViewWidget::DynamicPropertiesTab)
+ emit connectionViewWidget()->setEnabledAddButton(selectedNodeList.count() == 1);
+}
+
+void ConnectionView::importsChanged(const QList<Import> & /*addedImports*/, const QList<Import> & /*removedImports*/)
+{
+ backendModel()->resetModel();
+}
+
+WidgetInfo ConnectionView::widgetInfo()
+{
+ return createWidgetInfo(m_connectionViewWidget.data(),
+ new WidgetInfo::ToolBarWidgetDefaultFactory<ConnectionViewWidget>(connectionViewWidget()),
+ QLatin1String("ConnectionView"),
+ WidgetInfo::LeftPane,
+ 0,
+ tr("Connection View"));
+}
+
+bool ConnectionView::hasWidget() const
+{
+ return true;
+}
+
+QTableView *ConnectionView::connectionTableView() const
+{
+ return connectionViewWidget()->connectionTableView();
+}
+
+QTableView *ConnectionView::bindingTableView() const
+{
+ return connectionViewWidget()->bindingTableView();
+}
+
+QTableView *ConnectionView::dynamicPropertiesTableView() const
+{
+ return connectionViewWidget()->dynamicPropertiesTableView();
+}
+
+QTableView *ConnectionView::backendView() const
+{
+ return connectionViewWidget()->backendView();
+}
+
+ConnectionViewWidget *ConnectionView::connectionViewWidget() const
+{
+ return m_connectionViewWidget.data();
+}
+
+ConnectionModel *ConnectionView::connectionModel() const
+{
+ return m_connectionModel;
+}
+
+BindingModel *ConnectionView::bindingModel() const
+{
+ return m_bindingModel;
+}
+
+DynamicPropertiesModel *ConnectionView::dynamicPropertiesModel() const
+{
+ return m_dynamicPropertiesModel;
+}
+
+BackendModel *ConnectionView::backendModel() const
+{
+ return m_backendModel;
+}
+
+} // namesapce Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h
new file mode 100644
index 0000000000..da7623375a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <abstractview.h>
+#include <qmlitemnode.h>
+
+#include <QPointer>
+
+QT_BEGIN_NAMESPACE
+class QTableView;
+class QListView;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+class ConnectionViewWidget;
+class BindingModel;
+class ConnectionModel;
+class DynamicPropertiesModel;
+class BackendModel;
+
+class ConnectionView : public AbstractView
+{
+ Q_OBJECT
+
+public:
+ ConnectionView(QObject *parent = nullptr);
+ ~ConnectionView() override;
+
+ // AbstractView
+ void modelAttached(Model *model) override;
+ void modelAboutToBeDetached(Model *model) override;
+
+ void nodeCreated(const ModelNode &createdNode) override;
+ void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override;
+ void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override;
+ void nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId) override;
+ void propertiesAboutToBeRemoved(const QList<AbstractProperty>& propertyList) override;
+ void variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange) override;
+ void bindingPropertiesChanged(const QList<BindingProperty>& propertyList, PropertyChangeFlags propertyChange) override;
+
+ void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
+ const QList<ModelNode> &lastSelectedNodeList) override;
+
+ void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override;
+
+ WidgetInfo widgetInfo() override;
+ bool hasWidget() const override;
+
+ QTableView *connectionTableView() const;
+ QTableView *bindingTableView() const;
+ QTableView *dynamicPropertiesTableView() const;
+ QTableView *backendView() const;
+
+protected:
+ ConnectionViewWidget *connectionViewWidget() const;
+ ConnectionModel *connectionModel() const;
+ BindingModel *bindingModel() const;
+ DynamicPropertiesModel *dynamicPropertiesModel() const;
+ BackendModel *backendModel() const;
+
+
+private: //variables
+ QPointer<ConnectionViewWidget> m_connectionViewWidget;
+ ConnectionModel *m_connectionModel;
+ BindingModel *m_bindingModel;
+ DynamicPropertiesModel *m_dynamicPropertiesModel;
+ BackendModel *m_backendModel;
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp
new file mode 100644
index 0000000000..c9b1277ce4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp
@@ -0,0 +1,348 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "connectionviewwidget.h"
+#include "connectionview.h"
+#include "ui_connectionviewwidget.h"
+
+#include "delegates.h"
+#include "backendmodel.h"
+#include "bindingmodel.h"
+#include "connectionmodel.h"
+#include "dynamicpropertiesmodel.h"
+#include "theme.h"
+
+#include <designersettings.h>
+#include <qmldesignerplugin.h>
+
+#include <coreplugin/coreconstants.h>
+#include <utils/fileutils.h>
+#include <utils/utilsicons.h>
+
+#include <QToolButton>
+#include <QStyleFactory>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) :
+ QFrame(parent),
+ ui(new Ui::ConnectionViewWidget)
+{
+
+ setWindowTitle(tr("Connections", "Title of connection view"));
+ ui->setupUi(this);
+
+ QStyle *style = QStyleFactory::create("fusion");
+ setStyle(style);
+
+ setStyleSheet(Theme::replaceCssColors(QLatin1String(Utils::FileReader::fetchQrc(QLatin1String(":/connectionview/stylesheet.css")))));
+
+ //ui->tabWidget->tabBar()->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ ui->tabBar->setUsesScrollButtons(true);
+ ui->tabBar->setElideMode(Qt::ElideRight);
+
+ ui->tabBar->addTab(tr("Connections", "Title of connection view"));
+ ui->tabBar->addTab(tr("Bindings", "Title of connection view"));
+ ui->tabBar->addTab(tr("Properties", "Title of dynamic properties view"));
+
+ auto settings = QmlDesignerPlugin::instance()->settings();
+
+ if (!settings.value(DesignerSettingsKey::STANDALONE_MODE).toBool())
+ ui->tabBar->addTab(tr("Backends", "Title of dynamic properties view"));
+
+ ui->tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
+
+ const QString themedScrollBarCss = Theme::replaceCssColors(
+ QLatin1String(Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css"))));
+
+ ui->connectionView->setStyleSheet(themedScrollBarCss);
+ ui->bindingView->setStyleSheet(themedScrollBarCss);
+ ui->dynamicPropertiesView->setStyleSheet(themedScrollBarCss);
+ ui->backendView->setStyleSheet(themedScrollBarCss);
+
+ connect(ui->tabBar, &QTabBar::currentChanged,
+ ui->stackedWidget, &QStackedWidget::setCurrentIndex);
+
+ connect(ui->tabBar, &QTabBar::currentChanged,
+ this, &ConnectionViewWidget::handleTabChanged);
+
+ ui->stackedWidget->setCurrentIndex(0);
+}
+
+ConnectionViewWidget::~ConnectionViewWidget()
+{
+ delete ui;
+}
+
+void ConnectionViewWidget::setBindingModel(BindingModel *model)
+{
+ ui->bindingView->setModel(model);
+ ui->bindingView->verticalHeader()->hide();
+ ui->bindingView->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->bindingView->setItemDelegate(new BindingDelegate);
+ connect(ui->bindingView->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ this, &ConnectionViewWidget::bindingTableViewSelectionChanged);
+}
+
+void ConnectionViewWidget::setConnectionModel(ConnectionModel *model)
+{
+ ui->connectionView->setModel(model);
+ ui->connectionView->verticalHeader()->hide();
+ ui->connectionView->horizontalHeader()->setDefaultSectionSize(160);
+ ui->connectionView->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->connectionView->setItemDelegate(new ConnectionDelegate);
+ connect(ui->connectionView->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ this, &ConnectionViewWidget::connectionTableViewSelectionChanged);
+
+}
+
+void ConnectionViewWidget::setDynamicPropertiesModel(DynamicPropertiesModel *model)
+{
+ ui->dynamicPropertiesView->setModel(model);
+ ui->dynamicPropertiesView->verticalHeader()->hide();
+ ui->dynamicPropertiesView->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->dynamicPropertiesView->setItemDelegate(new DynamicPropertiesDelegate);
+ connect(ui->dynamicPropertiesView->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ this, &ConnectionViewWidget::dynamicPropertiesTableViewSelectionChanged);
+}
+
+void ConnectionViewWidget::setBackendModel(BackendModel *model)
+{
+ ui->backendView->setModel(model);
+ ui->backendView->verticalHeader()->hide();
+ ui->backendView->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->backendView->setItemDelegate(new BackendDelegate);
+ model->resetModel();
+ connect(ui->backendView->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ this, &ConnectionViewWidget::backendTableViewSelectionChanged);
+}
+
+QList<QToolButton *> ConnectionViewWidget::createToolBarWidgets()
+{
+ QList<QToolButton *> buttons;
+
+ buttons << new QToolButton();
+ buttons.constLast()->setIcon(Utils::Icons::PLUS_TOOLBAR.icon());
+ buttons.constLast()->setToolTip(tr("Add binding or connection."));
+ connect(buttons.constLast(), &QAbstractButton::clicked, this, &ConnectionViewWidget::addButtonClicked);
+ connect(this, &ConnectionViewWidget::setEnabledAddButton, buttons.constLast(), &QWidget::setEnabled);
+
+ buttons << new QToolButton();
+ buttons.constLast()->setIcon(Utils::Icons::MINUS.icon());
+ buttons.constLast()->setToolTip(tr("Remove selected binding or connection."));
+ buttons.constLast()->setShortcut(QKeySequence(Qt::Key_Delete));
+ connect(buttons.constLast(), &QAbstractButton::clicked, this, &ConnectionViewWidget::removeButtonClicked);
+ connect(this, &ConnectionViewWidget::setEnabledRemoveButton, buttons.constLast(), &QWidget::setEnabled);
+
+ return buttons;
+}
+
+ConnectionViewWidget::TabStatus ConnectionViewWidget::currentTab() const
+{
+ switch (ui->stackedWidget->currentIndex()) {
+ case 0: return ConnectionTab;
+ case 1: return BindingTab;
+ case 2: return DynamicPropertiesTab;
+ case 3: return BackendTab;
+ default: return InvalidTab;
+ }
+}
+
+void ConnectionViewWidget::resetItemViews()
+{
+ if (currentTab() == ConnectionTab) {
+ ui->connectionView->selectionModel()->clear();
+
+ } else if (currentTab() == BindingTab) {
+ ui->bindingView->selectionModel()->clear();
+
+ } else if (currentTab() == DynamicPropertiesTab) {
+ ui->dynamicPropertiesView->selectionModel()->clear();
+ } else if (currentTab() == BackendTab) {
+ ui->backendView->selectionModel()->clear();
+ }
+ invalidateButtonStatus();
+}
+
+void ConnectionViewWidget::invalidateButtonStatus()
+{
+ if (currentTab() == ConnectionTab) {
+ emit setEnabledRemoveButton(ui->connectionView->selectionModel()->hasSelection());
+ emit setEnabledAddButton(true);
+ } else if (currentTab() == BindingTab) {
+ emit setEnabledRemoveButton(ui->bindingView->selectionModel()->hasSelection());
+ auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model());
+ emit setEnabledAddButton(bindingModel->connectionView()->model() &&
+ bindingModel->connectionView()->selectedModelNodes().count() == 1);
+
+ } else if (currentTab() == DynamicPropertiesTab) {
+ emit setEnabledRemoveButton(ui->dynamicPropertiesView->selectionModel()->hasSelection());
+ auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model());
+ emit setEnabledAddButton(dynamicPropertiesModel->connectionView()->model() &&
+ dynamicPropertiesModel->connectionView()->selectedModelNodes().count() == 1);
+ } else if (currentTab() == BackendTab) {
+ emit setEnabledAddButton(true);
+ emit setEnabledRemoveButton(ui->backendView->selectionModel()->hasSelection());
+ }
+}
+
+QTableView *ConnectionViewWidget::connectionTableView() const
+{
+ return ui->connectionView;
+}
+
+QTableView *ConnectionViewWidget::bindingTableView() const
+{
+ return ui->bindingView;
+}
+
+QTableView *ConnectionViewWidget::dynamicPropertiesTableView() const
+{
+ return ui->dynamicPropertiesView;
+}
+
+QTableView *ConnectionViewWidget::backendView() const
+{
+ return ui->backendView;
+}
+
+void ConnectionViewWidget::handleTabChanged(int)
+{
+ invalidateButtonStatus();
+}
+
+void ConnectionViewWidget::removeButtonClicked()
+{
+ if (currentTab() == ConnectionTab) {
+ if (ui->connectionView->selectionModel()->selectedRows().isEmpty())
+ return;
+ int currentRow = ui->connectionView->selectionModel()->selectedRows().constFirst().row();
+ auto connectionModel = qobject_cast<ConnectionModel*>(ui->connectionView->model());
+ if (connectionModel) {
+ connectionModel->deleteConnectionByRow(currentRow);
+ }
+ } else if (currentTab() == BindingTab) {
+ if (ui->bindingView->selectionModel()->selectedRows().isEmpty())
+ return;
+ int currentRow = ui->bindingView->selectionModel()->selectedRows().constFirst().row();
+ auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model());
+ if (bindingModel) {
+ bindingModel->deleteBindindByRow(currentRow);
+ }
+ } else if (currentTab() == DynamicPropertiesTab) {
+ if (ui->dynamicPropertiesView->selectionModel()->selectedRows().isEmpty())
+ return;
+ int currentRow = ui->dynamicPropertiesView->selectionModel()->selectedRows().constFirst().row();
+ auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model());
+ if (dynamicPropertiesModel)
+ dynamicPropertiesModel->deleteDynamicPropertyByRow(currentRow);
+ } else if (currentTab() == BackendTab) {
+ int currentRow = ui->backendView->selectionModel()->selectedRows().constFirst().row();
+ auto backendModel = qobject_cast<BackendModel*>(ui->backendView->model());
+ if (backendModel)
+ backendModel->deletePropertyByRow(currentRow);
+ }
+
+ invalidateButtonStatus();
+}
+
+void ConnectionViewWidget::addButtonClicked()
+{
+
+ if (currentTab() == ConnectionTab) {
+ auto connectionModel = qobject_cast<ConnectionModel*>(ui->connectionView->model());
+ if (connectionModel) {
+ connectionModel->addConnection();
+ }
+ } else if (currentTab() == BindingTab) {
+ auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model());
+ if (bindingModel) {
+ bindingModel->addBindingForCurrentNode();
+ }
+
+ } else if (currentTab() == DynamicPropertiesTab) {
+ auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model());
+ if (dynamicPropertiesModel)
+ dynamicPropertiesModel->addDynamicPropertyForCurrentNode();
+ } else if (currentTab() == BackendTab) {
+ auto backendModel = qobject_cast<BackendModel*>(ui->backendView->model());
+ if (backendModel)
+ backendModel->addNewBackend();
+ }
+
+ invalidateButtonStatus();
+}
+
+void ConnectionViewWidget::bindingTableViewSelectionChanged(const QModelIndex &current, const QModelIndex & /*previous*/)
+{
+ if (currentTab() == BindingTab) {
+ if (current.isValid()) {
+ emit setEnabledRemoveButton(true);
+ } else {
+ emit setEnabledRemoveButton(false);
+ }
+ }
+}
+
+void ConnectionViewWidget::connectionTableViewSelectionChanged(const QModelIndex &current, const QModelIndex & /*previous*/)
+{
+ if (currentTab() == ConnectionTab) {
+ if (current.isValid()) {
+ emit setEnabledRemoveButton(true);
+ } else {
+ emit setEnabledRemoveButton(false);
+ }
+ }
+}
+
+void ConnectionViewWidget::dynamicPropertiesTableViewSelectionChanged(const QModelIndex &current, const QModelIndex & /*previous*/)
+{
+ if (currentTab() == DynamicPropertiesTab) {
+ if (current.isValid()) {
+ emit setEnabledRemoveButton(true);
+ } else {
+ emit setEnabledRemoveButton(false);
+ }
+ }
+}
+
+void ConnectionViewWidget::backendTableViewSelectionChanged(const QModelIndex &current, const QModelIndex & /*revious*/)
+{
+ if (currentTab() == BackendTab) {
+ if (current.isValid()) {
+ emit setEnabledRemoveButton(true);
+ } else {
+ emit setEnabledRemoveButton(false);
+ }
+ }
+
+}
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h
new file mode 100644
index 0000000000..2bcc3932c1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QFrame>
+#include <QAbstractItemView>
+
+QT_BEGIN_NAMESPACE
+class QToolButton;
+class QTableView;
+class QListView;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+namespace Ui { class ConnectionViewWidget; }
+
+namespace Internal {
+
+class BindingModel;
+class ConnectionModel;
+class DynamicPropertiesModel;
+class BackendModel;
+
+class ConnectionViewWidget : public QFrame
+{
+ Q_OBJECT
+
+public:
+
+ enum TabStatus {
+ ConnectionTab,
+ BindingTab,
+ DynamicPropertiesTab,
+ BackendTab,
+ InvalidTab
+ };
+
+ explicit ConnectionViewWidget(QWidget *parent = nullptr);
+ ~ConnectionViewWidget() override;
+
+ void setBindingModel(BindingModel *model);
+ void setConnectionModel(ConnectionModel *model);
+ void setDynamicPropertiesModel(DynamicPropertiesModel *model);
+ void setBackendModel(BackendModel *model);
+
+ QList<QToolButton*> createToolBarWidgets();
+
+ TabStatus currentTab() const;
+
+ void resetItemViews();
+ void invalidateButtonStatus();
+
+ QTableView *connectionTableView() const;
+ QTableView *bindingTableView() const;
+ QTableView *dynamicPropertiesTableView() const;
+ QTableView *backendView() const;
+
+ void bindingTableViewSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
+ void connectionTableViewSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
+ void dynamicPropertiesTableViewSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
+ void backendTableViewSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
+
+signals:
+ void setEnabledAddButton(bool enabled);
+ void setEnabledRemoveButton(bool enabled);
+
+private:
+ void handleTabChanged(int i);
+ void removeButtonClicked();
+ void addButtonClicked();
+
+private:
+ Ui::ConnectionViewWidget *ui;
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui
new file mode 100644
index 0000000000..18df078ec6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::ConnectionViewWidget</class>
+ <widget class="QWidget" name="QmlDesigner::ConnectionViewWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>994</width>
+ <height>611</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Connections</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <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>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="1" column="0">
+ <widget class="QWidget" name="widgetSpacer" 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>4</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>4</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QTabBar" name="tabBar" native="true"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>3</number>
+ </property>
+ <widget class="QWidget" name="connectionViewPage">
+ <layout class="QGridLayout" name="gridLayout_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 row="3" column="0" colspan="5">
+ <widget class="QTableView" name="connectionView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <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>
+ </widget>
+ <widget class="QWidget" name="bindingViewPage">
+ <layout class="QGridLayout" name="gridLayout_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 row="2" column="0" colspan="3">
+ <widget class="QTableView" name="bindingView">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <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>
+ </widget>
+ <widget class="QWidget" name="dynamicPropertiesPage">
+ <layout class="QGridLayout" name="gridLayout_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 row="0" column="0">
+ <widget class="QTableView" name="dynamicPropertiesView">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <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>
+ </widget>
+ <widget class="QWidget" name="backendViewPage">
+ <layout class="QGridLayout" name="gridLayout_5">
+ <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 row="0" column="0">
+ <widget class="QTableView" name="backendView">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <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>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ <zorder>stackedWidget</zorder>
+ <zorder>tabBar</zorder>
+ <zorder>widgetSpacer</zorder>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QTabBar</class>
+ <extends>QWidget</extends>
+ <header location="global">qtabbar.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp
new file mode 100644
index 0000000000..555f448858
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp
@@ -0,0 +1,361 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "delegates.h"
+
+#include "backendmodel.h"
+#include "connectionmodel.h"
+#include "bindingmodel.h"
+#include "dynamicpropertiesmodel.h"
+#include "connectionview.h"
+
+#include <bindingproperty.h>
+
+#include <utils/qtcassert.h>
+
+#include <QStyleFactory>
+#include <QItemEditorFactory>
+#include <QDebug>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+QStringList prependOnForSignalHandler(const QStringList &signalNames)
+{
+ QStringList signalHandlerNames;
+ foreach (const QString &signalName, signalNames) {
+ QString signalHandlerName = signalName;
+ if (!signalHandlerName.isEmpty()) {
+ QChar firstChar = signalHandlerName.at(0).toUpper();
+ signalHandlerName[0] = firstChar;
+ signalHandlerName.prepend(QLatin1String("on"));
+ signalHandlerNames.append(signalHandlerName);
+ }
+ }
+ return signalHandlerNames;
+}
+
+PropertiesComboBox::PropertiesComboBox(QWidget *parent) : QComboBox(parent)
+{
+ setEditable(true);
+ setValidator(new QRegularExpressionValidator(QRegularExpression(QLatin1String("[a-z|A-Z|0-9|._-]*")), this));
+}
+
+QString PropertiesComboBox::text() const
+{
+ return currentText();
+}
+
+void PropertiesComboBox::setText(const QString &text)
+{
+ setEditText(text);
+}
+
+void PropertiesComboBox::disableValidator()
+{
+ setValidator(nullptr);
+}
+
+ConnectionComboBox::ConnectionComboBox(QWidget *parent) : PropertiesComboBox(parent)
+{
+}
+
+QString ConnectionComboBox::text() const
+{
+ int index = findText(currentText());
+ if (index > -1) {
+ QVariant variantData = itemData(index);
+ if (variantData.isValid())
+ return variantData.toString();
+ }
+
+ return currentText();
+}
+
+ConnectionEditorDelegate::ConnectionEditorDelegate(QWidget *parent)
+ : QStyledItemDelegate(parent)
+{
+}
+
+void ConnectionEditorDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionViewItem opt = option;
+ opt.state &= ~QStyle::State_HasFocus;
+ QStyledItemDelegate::paint(painter, opt, index);
+}
+
+BindingDelegate::BindingDelegate(QWidget *parent) : ConnectionEditorDelegate(parent)
+{
+ static QItemEditorFactory *factory = nullptr;
+ if (factory == nullptr) {
+ factory = new QItemEditorFactory;
+ QItemEditorCreatorBase *creator
+ = new QItemEditorCreator<PropertiesComboBox>("text");
+ factory->registerEditor(QVariant::String, creator);
+ }
+
+ setItemEditorFactory(factory);
+}
+
+QWidget *BindingDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index);
+
+ const auto model = qobject_cast<const BindingModel*>(index.model());
+ if (!model) {
+ qWarning() << "BindingDelegate::createEditor no model";
+ return widget;
+ }
+ if (!model->connectionView()) {
+ qWarning() << "BindingDelegate::createEditor no connection view";
+ return widget;
+ }
+
+ model->connectionView()->allModelNodes();
+
+ auto bindingComboBox = qobject_cast<PropertiesComboBox*>(widget);
+ if (!bindingComboBox) {
+ qWarning() << "BindingDelegate::createEditor no bindingComboBox";
+ return widget;
+ }
+
+ BindingProperty bindingProperty = model->bindingPropertyForRow(index.row());
+
+ switch (index.column()) {
+ case BindingModel::TargetModelNodeRow:
+ return nullptr; //no editor
+ case BindingModel::TargetPropertyNameRow: {
+ bindingComboBox->addItems(model->possibleTargetProperties(bindingProperty));
+ } break;
+ case BindingModel::SourceModelNodeRow: {
+ foreach (const ModelNode &modelNode, model->connectionView()->allModelNodes()) {
+ if (!modelNode.id().isEmpty()) {
+ bindingComboBox->addItem(modelNode.id());
+ }
+ }
+ if (!bindingProperty.parentModelNode().isRootNode())
+ bindingComboBox->addItem(QLatin1String("parent"));
+ } break;
+ case BindingModel::SourcePropertyNameRow: {
+ bindingComboBox->addItems(model->possibleSourceProperties(bindingProperty));
+ bindingComboBox->disableValidator();
+ } break;
+ default: qWarning() << "BindingDelegate::createEditor column" << index.column();
+ }
+
+ connect(bindingComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
+ auto delegate = const_cast<BindingDelegate*>(this);
+ emit delegate->commitData(bindingComboBox);
+ });
+
+ return widget;
+}
+
+DynamicPropertiesDelegate::DynamicPropertiesDelegate(QWidget *parent) : ConnectionEditorDelegate(parent)
+{
+// static QItemEditorFactory *factory = 0;
+// if (factory == 0) {
+// factory = new QItemEditorFactory;
+// QItemEditorCreatorBase *creator
+// = new QItemEditorCreator<DynamicPropertiesComboBox>("text");
+// factory->registerEditor(QVariant::String, creator);
+// }
+
+// setItemEditorFactory(factory);
+}
+
+QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index);
+
+ const auto model = qobject_cast<const DynamicPropertiesModel*>(index.model());
+ if (!model) {
+ qWarning() << "BindingDelegate::createEditor no model";
+ return widget;
+ }
+
+ if (!model->connectionView()) {
+ qWarning() << "BindingDelegate::createEditor no connection view";
+ return widget;
+ }
+ model->connectionView()->allModelNodes();
+
+ switch (index.column()) {
+ case DynamicPropertiesModel::TargetModelNodeRow: {
+ return nullptr; //no editor
+ };
+ case DynamicPropertiesModel::PropertyNameRow: {
+ return QStyledItemDelegate::createEditor(parent, option, index);
+ };
+ case DynamicPropertiesModel::PropertyTypeRow: {
+
+ auto dynamicPropertiesComboBox = new PropertiesComboBox(parent);
+ connect(dynamicPropertiesComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
+ auto delegate = const_cast<DynamicPropertiesDelegate*>(this);
+ emit delegate->commitData(dynamicPropertiesComboBox);
+ });
+
+ dynamicPropertiesComboBox->addItem(QLatin1String("alias"));
+ //dynamicPropertiesComboBox->addItem(QLatin1String("Item"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("real"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("int"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("string"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("bool"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("url"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("color"));
+ dynamicPropertiesComboBox->addItem(QLatin1String("variant"));
+ return dynamicPropertiesComboBox;
+ };
+ case DynamicPropertiesModel::PropertyValueRow: {
+ return QStyledItemDelegate::createEditor(parent, option, index);
+ };
+ default: qWarning() << "BindingDelegate::createEditor column" << index.column();
+ }
+
+ return nullptr;
+}
+
+ConnectionDelegate::ConnectionDelegate(QWidget *parent) : ConnectionEditorDelegate(parent)
+{
+ static QItemEditorFactory *factory = nullptr;
+ if (factory == nullptr) {
+ factory = new QItemEditorFactory;
+ QItemEditorCreatorBase *creator
+ = new QItemEditorCreator<ConnectionComboBox>("text");
+ factory->registerEditor(QVariant::String, creator);
+ }
+
+ setItemEditorFactory(factory);
+}
+
+QWidget *ConnectionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+
+ QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index);
+
+ const auto connectionModel = qobject_cast<const ConnectionModel*>(index.model());
+
+ auto connectionComboBox = qobject_cast<ConnectionComboBox*>(widget);
+
+ if (!connectionModel) {
+ qWarning() << "ConnectionDelegate::createEditor no model";
+ return widget;
+ }
+
+ if (!connectionModel->connectionView()) {
+ qWarning() << "ConnectionDelegate::createEditor no connection view";
+ return widget;
+ }
+
+ if (!connectionComboBox) {
+ qWarning() << "ConnectionDelegate::createEditor no bindingComboBox";
+ return widget;
+ }
+
+ switch (index.column()) {
+ case ConnectionModel::TargetModelNodeRow: {
+ foreach (const ModelNode &modelNode, connectionModel->connectionView()->allModelNodes()) {
+ if (!modelNode.id().isEmpty()) {
+ connectionComboBox->addItem(modelNode.id());
+ }
+ }
+ } break;
+ case ConnectionModel::TargetPropertyNameRow: {
+ connectionComboBox->addItems(prependOnForSignalHandler(connectionModel->getSignalsForRow(index.row())));
+ } break;
+ case ConnectionModel::SourceRow: {
+ ModelNode rootModelNode = connectionModel->connectionView()->rootModelNode();
+ if (QmlItemNode::isValidQmlItemNode(rootModelNode) && !rootModelNode.id().isEmpty()) {
+
+ QString itemText = tr("Change to default state");
+ QString source = QString::fromLatin1("{ %1.state = \"\" }").arg(rootModelNode.id());
+ connectionComboBox->addItem(itemText, source);
+ connectionComboBox->disableValidator();
+
+ foreach (const QmlModelState &state, QmlItemNode(rootModelNode).states().allStates()) {
+ QString itemText = tr("Change state to %1").arg(state.name());
+ QString source = QString::fromLatin1("{ %1.state = \"%2\" }").arg(rootModelNode.id()).arg(state.name());
+ connectionComboBox->addItem(itemText, source);
+ }
+ }
+ connectionComboBox->disableValidator();
+ } break;
+
+ default: qWarning() << "ConnectionDelegate::createEditor column" << index.column();
+ }
+
+ connect(connectionComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
+ auto delegate = const_cast<ConnectionDelegate*>(this);
+ emit delegate->commitData(connectionComboBox);
+ });
+
+ return widget;
+}
+
+BackendDelegate::BackendDelegate(QWidget *parent) : ConnectionEditorDelegate(parent)
+{
+}
+
+QWidget *BackendDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ const auto model = qobject_cast<const BackendModel*>(index.model());
+
+ model->connectionView()->allModelNodes();
+
+ QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index);
+
+ QTC_ASSERT(model, return widget);
+ QTC_ASSERT(model->connectionView(), return widget);
+
+ switch (index.column()) {
+ case BackendModel::TypeNameColumn: {
+ auto backendComboBox = new PropertiesComboBox(parent);
+ backendComboBox->addItems(model->possibleCppTypes());
+ connect(backendComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
+ auto delegate = const_cast<BackendDelegate*>(this);
+ emit delegate->commitData(backendComboBox);
+ });
+ return backendComboBox;
+ };
+ case BackendModel::PropertyNameColumn: {
+ return widget;
+ };
+ case BackendModel::IsSingletonColumn: {
+ return nullptr; //no editor
+ };
+ case BackendModel::IsLocalColumn: {
+ return nullptr; //no editor
+ };
+ default: qWarning() << "BackendDelegate::createEditor column" << index.column();
+ }
+
+ return widget;
+}
+
+} // namesapce Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.h b/src/plugins/qmldesigner/components/connectioneditor/delegates.h
new file mode 100644
index 0000000000..b9792293ac
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QStandardItem>
+#include <QStyledItemDelegate>
+#include <QComboBox>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+class PropertiesComboBox : public QComboBox
+{
+ Q_OBJECT
+ Q_PROPERTY(QString text READ text WRITE setText USER true)
+public:
+ PropertiesComboBox(QWidget *parent = nullptr);
+
+ virtual QString text() const;
+ void setText(const QString &text);
+ void disableValidator();
+};
+
+class ConnectionComboBox : public PropertiesComboBox
+{
+ Q_OBJECT
+public:
+ ConnectionComboBox(QWidget *parent = nullptr);
+ QString text() const override;
+};
+
+class ConnectionEditorDelegate : public QStyledItemDelegate
+{
+public:
+ ConnectionEditorDelegate(QWidget *parent = nullptr);
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+};
+
+class BindingDelegate : public ConnectionEditorDelegate
+{
+public:
+ BindingDelegate(QWidget *parent = nullptr);
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+class DynamicPropertiesDelegate : public ConnectionEditorDelegate
+{
+public:
+ DynamicPropertiesDelegate(QWidget *parent = nullptr);
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+
+class ConnectionDelegate : public ConnectionEditorDelegate
+{
+ Q_OBJECT
+public:
+ ConnectionDelegate(QWidget *parent = nullptr);
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+class BackendDelegate : public ConnectionEditorDelegate
+{
+ Q_OBJECT
+public:
+ BackendDelegate(QWidget *parent = nullptr);
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp
new file mode 100644
index 0000000000..0a08e5c883
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp
@@ -0,0 +1,677 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "dynamicpropertiesmodel.h"
+
+#include "connectionview.h"
+
+#include <nodemetainfo.h>
+#include <nodeproperty.h>
+#include <variantproperty.h>
+#include <bindingproperty.h>
+#include <rewritingexception.h>
+#include <rewritertransaction.h>
+
+#include <utils/fileutils.h>
+
+#include <QMessageBox>
+#include <QTimer>
+#include <QUrl>
+
+namespace {
+
+bool compareVariantProperties(const QmlDesigner::VariantProperty &variantProperty01, const QmlDesigner::VariantProperty &variantProperty02)
+{
+ if (variantProperty01.parentModelNode() != variantProperty02.parentModelNode())
+ return false;
+ if (variantProperty01.name() != variantProperty02.name())
+ return false;
+ return true;
+}
+
+QString idOrTypeNameForNode(const QmlDesigner::ModelNode &modelNode)
+{
+ QString idLabel = modelNode.id();
+ if (idLabel.isEmpty())
+ idLabel = modelNode.simplifiedTypeName();
+
+ return idLabel;
+}
+
+QmlDesigner::PropertyName unusedProperty(const QmlDesigner::ModelNode &modelNode)
+{
+ QmlDesigner::PropertyName propertyName = "property";
+ int i = 0;
+ if (modelNode.metaInfo().isValid()) {
+ while (true) {
+ const QmlDesigner::PropertyName currentPropertyName = propertyName + QString::number(i).toLatin1();
+ if (!modelNode.hasProperty(currentPropertyName) && !modelNode.metaInfo().hasProperty(currentPropertyName))
+ return currentPropertyName;
+ i++;
+ }
+ }
+
+ return propertyName;
+}
+
+QVariant convertVariantForTypeName(const QVariant &variant, const QmlDesigner::TypeName &typeName)
+{
+ QVariant returnValue = variant;
+
+ if (typeName == "int") {
+ bool ok;
+ returnValue = variant.toInt(&ok);
+ if (!ok)
+ returnValue = 0;
+ } else if (typeName == "real") {
+ bool ok;
+ returnValue = variant.toReal(&ok);
+ if (!ok)
+ returnValue = 0.0;
+
+ } else if (typeName == "string") {
+ returnValue = variant.toString();
+
+ } else if (typeName == "bool") {
+ returnValue = variant.toBool();
+ } else if (typeName == "url") {
+ returnValue = variant.toUrl();
+ } else if (typeName == "color") {
+ if (QColor::isValidColor(variant.toString())) {
+ returnValue = variant.toString();
+ } else {
+ returnValue = QColor(Qt::black);
+ }
+ } else if (typeName == "Item") {
+ returnValue = 0;
+ }
+
+ return returnValue;
+}
+
+} //internal namespace
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+DynamicPropertiesModel::DynamicPropertiesModel(ConnectionView *parent)
+ : QStandardItemModel(parent)
+ , m_connectionView(parent)
+{
+ connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged);
+}
+
+void DynamicPropertiesModel::resetModel()
+{
+ beginResetModel();
+ clear();
+ setHorizontalHeaderLabels(QStringList({ tr("Item"), tr("Property"), tr("Property Type"),
+ tr("Property Value") }));
+
+ foreach (const ModelNode modelNode, m_selectedModelNodes)
+ addModelNode(modelNode);
+
+ endResetModel();
+}
+
+void DynamicPropertiesModel::bindingPropertyChanged(const BindingProperty &bindingProperty)
+{
+ if (!bindingProperty.isDynamic())
+ return;
+
+ m_handleDataChanged = false;
+
+ QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
+ if (!selectedNodes.contains(bindingProperty.parentModelNode()))
+ return;
+ if (!m_lock) {
+ int rowNumber = findRowForBindingProperty(bindingProperty);
+
+ if (rowNumber == -1) {
+ addBindingProperty(bindingProperty);
+ } else {
+ updateBindingProperty(rowNumber);
+ }
+ }
+
+ m_handleDataChanged = true;
+}
+
+void DynamicPropertiesModel::variantPropertyChanged(const VariantProperty &variantProperty)
+{
+ if (!variantProperty.isDynamic())
+ return;
+
+ m_handleDataChanged = false;
+
+ QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
+ if (!selectedNodes.contains(variantProperty.parentModelNode()))
+ return;
+ if (!m_lock) {
+ int rowNumber = findRowForVariantProperty(variantProperty);
+
+ if (rowNumber == -1) {
+ addVariantProperty(variantProperty);
+ } else {
+ updateVariantProperty(rowNumber);
+ }
+ }
+
+ m_handleDataChanged = true;
+}
+
+void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProperty)
+{
+ m_handleDataChanged = false;
+
+ QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
+ if (!selectedNodes.contains(bindingProperty.parentModelNode()))
+ return;
+ if (!m_lock) {
+ int rowNumber = findRowForBindingProperty(bindingProperty);
+ removeRow(rowNumber);
+ }
+
+ m_handleDataChanged = true;
+}
+
+void DynamicPropertiesModel::selectionChanged(const QList<ModelNode> &selectedNodes)
+{
+ m_handleDataChanged = false;
+ m_selectedModelNodes = selectedNodes;
+ resetModel();
+ m_handleDataChanged = true;
+}
+
+ConnectionView *DynamicPropertiesModel::connectionView() const
+{
+ return m_connectionView;
+}
+
+BindingProperty DynamicPropertiesModel::bindingPropertyForRow(int rowNumber) const
+{
+
+ const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
+ const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
+
+ ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
+
+ if (modelNode.isValid())
+ return modelNode.bindingProperty(targetPropertyName.toUtf8());
+
+ return BindingProperty();
+}
+
+VariantProperty DynamicPropertiesModel::variantPropertyForRow(int rowNumber) const
+{
+ const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
+ const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
+
+ ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId);
+
+ if (modelNode.isValid())
+ return modelNode.variantProperty(targetPropertyName.toUtf8());
+
+ return VariantProperty();
+}
+
+QStringList DynamicPropertiesModel::possibleTargetProperties(const BindingProperty &bindingProperty) const
+{
+ const ModelNode modelNode = bindingProperty.parentModelNode();
+
+ if (!modelNode.isValid()) {
+ qWarning() << " BindingModel::possibleTargetPropertiesForRow invalid model node";
+ return QStringList();
+ }
+
+ NodeMetaInfo metaInfo = modelNode.metaInfo();
+
+ if (metaInfo.isValid()) {
+ QStringList possibleProperties;
+ foreach (const PropertyName &propertyName, metaInfo.propertyNames()) {
+ if (metaInfo.propertyIsWritable(propertyName))
+ possibleProperties << QString::fromUtf8(propertyName);
+ }
+
+ return possibleProperties;
+ }
+
+ return QStringList();
+}
+
+void DynamicPropertiesModel::addDynamicPropertyForCurrentNode()
+{
+ if (connectionView()->selectedModelNodes().count() == 1) {
+ const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
+ if (modelNode.isValid()) {
+ try {
+ modelNode.variantProperty(unusedProperty(modelNode)).setDynamicTypeNameAndValue("string", QLatin1String("none.none"));
+ } catch (RewritingException &e) {
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException);
+ }
+ }
+ } else {
+ qWarning() << " BindingModel::addBindingForCurrentNode not one node selected";
+ }
+}
+
+QStringList DynamicPropertiesModel::possibleSourceProperties(const BindingProperty &bindingProperty) const
+{
+ const QString expression = bindingProperty.expression();
+ const QStringList stringlist = expression.split(QLatin1String("."));
+
+ PropertyName typeName;
+
+ if (bindingProperty.parentModelNode().metaInfo().isValid()) {
+ typeName = bindingProperty.parentModelNode().metaInfo().propertyTypeName(bindingProperty.name());
+ } else {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for target node";
+ }
+
+ const QString &id = stringlist.constFirst();
+
+ ModelNode modelNode = getNodeByIdOrParent(id, bindingProperty.parentModelNode());
+
+ if (!modelNode.isValid()) {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow invalid model node";
+ return QStringList();
+ }
+
+ NodeMetaInfo metaInfo = modelNode.metaInfo();
+
+ if (metaInfo.isValid()) {
+ QStringList possibleProperties;
+ foreach (const PropertyName &propertyName, metaInfo.propertyNames()) {
+ if (metaInfo.propertyTypeName(propertyName) == typeName) //### todo proper check
+ possibleProperties << QString::fromUtf8(propertyName);
+ }
+
+ return possibleProperties;
+ } else {
+ qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for source node";
+ }
+
+ return QStringList();
+}
+
+void DynamicPropertiesModel::deleteDynamicPropertyByRow(int rowNumber)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+ if (bindingProperty.isValid()) {
+ bindingProperty.parentModelNode().removeProperty(bindingProperty.name());
+ }
+
+ VariantProperty variantProperty = variantPropertyForRow(rowNumber);
+
+ if (variantProperty.isValid()) {
+ variantProperty.parentModelNode().removeProperty(variantProperty.name());
+ }
+
+ resetModel();
+}
+
+void DynamicPropertiesModel::addProperty(const QVariant &propertyValue,
+ const QString &propertyType,
+ const AbstractProperty &abstractProperty)
+{
+ QList<QStandardItem*> items;
+
+ QStandardItem *idItem;
+ QStandardItem *propertyNameItem;
+ QStandardItem *propertyTypeItem;
+ QStandardItem *propertyValueItem;
+
+ idItem = new QStandardItem(idOrTypeNameForNode(abstractProperty.parentModelNode()));
+ updateCustomData(idItem, abstractProperty);
+
+ propertyNameItem = new QStandardItem(QString::fromUtf8(abstractProperty.name()));
+
+ items.append(idItem);
+ items.append(propertyNameItem);
+
+
+ propertyTypeItem = new QStandardItem(propertyType);
+ items.append(propertyTypeItem);
+
+ propertyValueItem = new QStandardItem();
+ propertyValueItem->setData(propertyValue, Qt::DisplayRole);
+ items.append(propertyValueItem);
+
+ appendRow(items);
+}
+
+void DynamicPropertiesModel::addBindingProperty(const BindingProperty &property)
+{
+ QVariant value = property.expression();
+ QString type = QString::fromLatin1(property.dynamicTypeName());
+ addProperty(value, type, property);
+}
+
+void DynamicPropertiesModel::addVariantProperty(const VariantProperty &property)
+{
+ QVariant value = property.value();
+ QString type = QString::fromLatin1(property.dynamicTypeName());
+ addProperty(value, type, property);
+}
+
+void DynamicPropertiesModel::updateBindingProperty(int rowNumber)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ if (bindingProperty.isValid()) {
+ QString propertyName = QString::fromUtf8(bindingProperty.name());
+ updateDisplayRole(rowNumber, PropertyNameRow, propertyName);
+ QString value = bindingProperty.expression();
+ QString type = QString::fromUtf8(bindingProperty.dynamicTypeName());
+ updateDisplayRole(rowNumber, PropertyTypeRow, type);
+ updateDisplayRole(rowNumber, PropertyValueRow, value);
+ }
+}
+
+void DynamicPropertiesModel::updateVariantProperty(int rowNumber)
+{
+ VariantProperty variantProperty = variantPropertyForRow(rowNumber);
+
+ if (variantProperty.isValid()) {
+ QString propertyName = QString::fromUtf8(variantProperty.name());
+ updateDisplayRole(rowNumber, PropertyNameRow, propertyName);
+ QVariant value = variantProperty.value();
+ QString type = QString::fromUtf8(variantProperty.dynamicTypeName());
+ updateDisplayRole(rowNumber, PropertyTypeRow, type);
+
+ updateDisplayRoleFromVariant(rowNumber, PropertyValueRow, value);
+ }
+}
+
+void DynamicPropertiesModel::addModelNode(const ModelNode &modelNode)
+{
+ foreach (const BindingProperty &bindingProperty, modelNode.bindingProperties()) {
+ if (bindingProperty.isDynamic())
+ addBindingProperty(bindingProperty);
+ }
+
+ foreach (const VariantProperty &variantProperty, modelNode.variantProperties()) {
+ if (variantProperty.isDynamic())
+ addVariantProperty(variantProperty);
+ }
+}
+
+void DynamicPropertiesModel::updateValue(int row)
+{
+ BindingProperty bindingProperty = bindingPropertyForRow(row);
+
+ if (bindingProperty.isBindingProperty()) {
+ const QString expression = data(index(row, PropertyValueRow)).toString();
+
+ RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
+ try {
+ bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), expression);
+ transaction.commit(); //committing in the try block
+ } catch (Exception &e) {
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException);
+ }
+ return;
+ }
+
+ VariantProperty variantProperty = variantPropertyForRow(row);
+
+ if (variantProperty.isVariantProperty()) {
+ const QVariant value = data(index(row, PropertyValueRow));
+
+ RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue"));
+ try {
+ variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value);
+ transaction.commit(); //committing in the try block
+ } catch (Exception &e) {
+ m_exceptionError = e.description();
+ QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException);
+ }
+ }
+}
+
+void DynamicPropertiesModel::updatePropertyName(int rowNumber)
+{
+ const PropertyName newName = data(index(rowNumber, PropertyNameRow)).toString().toUtf8();
+ if (newName.isEmpty()) {
+ qWarning() << "DynamicPropertiesModel::updatePropertyName invalid property name";
+ return;
+ }
+
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ ModelNode targetNode = bindingProperty.parentModelNode();
+
+ if (bindingProperty.isBindingProperty()) {
+ connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){
+ const QString expression = bindingProperty.expression();
+ const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName();
+
+ targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, expression);
+ targetNode.removeProperty(bindingProperty.name());
+ });
+
+ updateCustomData(rowNumber, targetNode.bindingProperty(newName));
+ return;
+ }
+
+ VariantProperty variantProperty = variantPropertyForRow(rowNumber);
+
+ if (variantProperty.isVariantProperty()) {
+ const QVariant value = variantProperty.value();
+ const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName();
+ ModelNode targetNode = variantProperty.parentModelNode();
+
+ connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){
+ targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, value);
+ targetNode.removeProperty(variantProperty.name());
+ });
+
+ updateCustomData(rowNumber, targetNode.variantProperty(newName));
+ }
+}
+
+void DynamicPropertiesModel::updatePropertyType(int rowNumber)
+{
+
+ const TypeName newType = data(index(rowNumber, PropertyTypeRow)).toString().toLatin1();
+
+ if (newType.isEmpty()) {
+ qWarning() << "DynamicPropertiesModel::updatePropertyName invalid property type";
+ return;
+ }
+
+ BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
+
+ if (bindingProperty.isBindingProperty()) {
+ const QString expression = bindingProperty.expression();
+ const PropertyName propertyName = bindingProperty.name();
+ ModelNode targetNode = bindingProperty.parentModelNode();
+
+ connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
+ targetNode.removeProperty(bindingProperty.name());
+ targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, expression);
+ });
+
+ updateCustomData(rowNumber, targetNode.bindingProperty(propertyName));
+ return;
+ }
+
+ VariantProperty variantProperty = variantPropertyForRow(rowNumber);
+
+ if (variantProperty.isVariantProperty()) {
+ const QVariant value = variantProperty.value();
+ ModelNode targetNode = variantProperty.parentModelNode();
+ const PropertyName propertyName = variantProperty.name();
+
+ connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){
+ targetNode.removeProperty(variantProperty.name());
+ if (newType == "alias") { //alias properties have to be bindings
+ targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, QLatin1String("none.none"));
+ } else {
+ targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(newType, convertVariantForTypeName(value, newType));
+ }
+ });
+
+ updateCustomData(rowNumber, targetNode.variantProperty(propertyName));
+
+ if (variantProperty.isVariantProperty()) {
+ updateVariantProperty(rowNumber);
+ } else if (bindingProperty.isBindingProperty()) {
+ updateBindingProperty(rowNumber);
+ }
+ }
+}
+
+ModelNode DynamicPropertiesModel::getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const
+{
+ ModelNode modelNode;
+
+ if (id != QLatin1String("parent")) {
+ modelNode = connectionView()->modelNodeForId(id);
+ } else {
+ if (targetNode.hasParentProperty()) {
+ modelNode = targetNode.parentProperty().parentModelNode();
+ }
+ }
+ return modelNode;
+}
+
+void DynamicPropertiesModel::updateCustomData(QStandardItem *item, const AbstractProperty &property)
+{
+ item->setData(property.parentModelNode().internalId(), Qt::UserRole + 1);
+ item->setData(property.name(), Qt::UserRole + 2);
+}
+
+void DynamicPropertiesModel::updateCustomData(int row, const AbstractProperty &property)
+{
+ QStandardItem* idItem = item(row, 0);
+ updateCustomData(idItem, property);
+}
+
+int DynamicPropertiesModel::findRowForBindingProperty(const BindingProperty &bindingProperty) const
+{
+ for (int i=0; i < rowCount(); i++) {
+ if (compareBindingProperties(bindingPropertyForRow(i), bindingProperty))
+ return i;
+ }
+ //not found
+ return -1;
+}
+
+int DynamicPropertiesModel::findRowForVariantProperty(const VariantProperty &variantProperty) const
+{
+ for (int i=0; i < rowCount(); i++) {
+ if (compareVariantProperties(variantPropertyForRow(i), variantProperty))
+ return i;
+ }
+ //not found
+ return -1;
+}
+
+bool DynamicPropertiesModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty)
+{
+ //### todo we assume no expressions yet
+
+ const QString expression = bindingProperty.expression();
+
+ if (true) {
+ const QStringList stringList = expression.split(QLatin1String("."));
+
+ *sourceNode = stringList.constFirst();
+
+ QString propertyName;
+
+ for (int i=1; i < stringList.count(); i++) {
+ propertyName += stringList.at(i);
+ if (i != stringList.count() - 1)
+ propertyName += QLatin1String(".");
+ }
+ *sourceProperty = propertyName;
+ }
+ return true;
+}
+
+void DynamicPropertiesModel::updateDisplayRole(int row, int columns, const QString &string)
+{
+ QModelIndex modelIndex = index(row, columns);
+ if (data(modelIndex).toString() != string)
+ setData(modelIndex, string);
+}
+
+void DynamicPropertiesModel::updateDisplayRoleFromVariant(int row, int columns, const QVariant &variant)
+{
+ QModelIndex modelIndex = index(row, columns);
+ if (data(modelIndex) != variant)
+ setData(modelIndex, variant);
+}
+
+
+void DynamicPropertiesModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ if (!m_handleDataChanged)
+ return;
+
+ if (topLeft != bottomRight) {
+ qWarning() << "BindingModel::handleDataChanged multi edit?";
+ return;
+ }
+
+ m_lock = true;
+
+ int currentColumn = topLeft.column();
+ int currentRow = topLeft.row();
+
+ switch (currentColumn) {
+ case TargetModelNodeRow: {
+ //updating user data
+ } break;
+ case PropertyNameRow: {
+ updatePropertyName(currentRow);
+ } break;
+ case PropertyTypeRow: {
+ updatePropertyType(currentRow);
+ } break;
+ case PropertyValueRow: {
+ updateValue(currentRow);
+ } break;
+
+ default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn;
+ }
+
+ m_lock = false;
+}
+
+void DynamicPropertiesModel::handleException()
+{
+ QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
+ resetModel();
+}
+
+} // namespace Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h
new file mode 100644
index 0000000000..e0c9617fed
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <modelnode.h>
+#include <bindingproperty.h>
+#include <variantproperty.h>
+
+#include <QStandardItemModel>
+
+namespace QmlDesigner {
+
+namespace Internal {
+
+class ConnectionView;
+
+class DynamicPropertiesModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+public:
+ enum ColumnRoles {
+ TargetModelNodeRow = 0,
+ PropertyNameRow = 1,
+ PropertyTypeRow = 2,
+ PropertyValueRow = 3
+ };
+ DynamicPropertiesModel(ConnectionView *parent = nullptr);
+ void bindingPropertyChanged(const BindingProperty &bindingProperty);
+ void variantPropertyChanged(const VariantProperty &variantProperty);
+ void bindingRemoved(const BindingProperty &bindingProperty);
+ void selectionChanged(const QList<ModelNode> &selectedNodes);
+
+ ConnectionView *connectionView() const;
+ BindingProperty bindingPropertyForRow(int rowNumber) const;
+ VariantProperty variantPropertyForRow(int rowNumber) const;
+ QStringList possibleTargetProperties(const BindingProperty &bindingProperty) const;
+ QStringList possibleSourceProperties(const BindingProperty &bindingProperty) const;
+ void deleteDynamicPropertyByRow(int rowNumber);
+
+ void updateDisplayRoleFromVariant(int row, int columns, const QVariant &variant);
+ void addDynamicPropertyForCurrentNode();
+ void resetModel();
+
+protected:
+ void addProperty(const QVariant &propertyValue,
+ const QString &propertyType,
+ const AbstractProperty &abstractProperty);
+ void addBindingProperty(const BindingProperty &property);
+ void addVariantProperty(const VariantProperty &property);
+ void updateBindingProperty(int rowNumber);
+ void updateVariantProperty(int rowNumber);
+ void addModelNode(const ModelNode &modelNode);
+ void updateValue(int row);
+ void updatePropertyName(int rowNumber);
+ void updatePropertyType(int rowNumber);
+ ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const;
+ void updateCustomData(QStandardItem *item, const AbstractProperty &property);
+ void updateCustomData(int row, const AbstractProperty &property);
+ int findRowForBindingProperty(const BindingProperty &bindingProperty) const;
+ int findRowForVariantProperty(const VariantProperty &variantProperty) const;
+
+ bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty);
+
+ void updateDisplayRole(int row, int columns, const QString &string);
+
+private:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
+ void handleException();
+
+private:
+ QList<ModelNode> m_selectedModelNodes;
+ ConnectionView *m_connectionView;
+ bool m_lock = false;
+ bool m_handleDataChanged = false;
+ QString m_exceptionError;
+
+};
+
+} // namespace Internal
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css b/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css
new file mode 100644
index 0000000000..aeacc63733
--- /dev/null
+++ b/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css
@@ -0,0 +1,123 @@
+QFrame
+{
+ background-color: creatorTheme.QmlDesigner_BackgroundColorDarkAlternate;
+ color: creatorTheme.PanelTextColorLight;
+ font-size: creatorTheme.captionFontPixelSize;
+ border-radius: 0px;
+}
+
+QTableView {
+ color: creatorTheme.PanelTextColorLight;
+ selection-color: creatorTheme.PanelTextColorLight;
+ selection-background-color: creatorTheme.QmlDesigner_HighlightColor;
+
+}
+
+QTabBar QToolButton {
+ background-color: creatorTheme.QmlDesigner_BackgroundColorDarkAlternate;
+ border: 1px solid creatorTheme.QmlDesigner_BackgroundColorDarker;
+ border-radius: 0px;
+}
+
+QTableView::item
+{
+ border: 0px;
+ padding-left: 4px;
+}
+
+QTableView::item:focus
+{
+ border: none;
+ background-color: transparent;
+}
+
+QTableView::item:selected
+{
+ border: none
+}
+
+QHeaderView::section {
+ background-color: #494949;
+ padding: 4px;
+ border: 1px solid black;
+ color: #cacaca;
+ margin: 2px
+}
+
+QTableView {
+ alternate-background-color: #414141;
+}
+
+QWidget#widgetSpacer {
+ background-color: creatorTheme.QmlDesigner_TabLight;
+}
+
+QStackedWidget {
+ border: 0px;
+ background-color: #4f4f4f;
+}
+
+QTabBar::tab:selected {
+ border: none;
+ border-image: none;
+ image: none;
+
+ background-color: creatorTheme.QmlDesigner_TabLight;
+ color: creatorTheme.QmlDesigner_TabDark;
+}
+
+
+QTabBar::tab {
+ width: 92px;
+ height: 22px;
+ margin-top: 0x;
+ margin-bottom: 0px;
+ margin-left: 0px;
+ margin-right: 0px;
+ font: bold;
+ font-size: creatorTheme.captionFontPixelSize;
+ background-color: creatorTheme.QmlDesigner_TabDark;
+ color: creatorTheme.QmlDesigner_TabLight;
+}
+
+QSpinBox
+{
+ font-size: creatorTheme.captionFontPixelSize;
+ color: white;
+ padding-right: 2px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-left: 12px;
+ border: 2px solid #0F0F0F;
+ border-width: 1;
+ background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #2c2c2c, stop: 1 #333333);
+
+ min-height: 22px;
+}
+
+ QDoubleSpinBox
+ {
+ font-size: creatorTheme.captionFontPixelSize;
+ color: white;
+ padding-right: 2px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-left: 12px;
+ border: 2px solid #0F0F0F;
+ border-width: 1;
+ background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #2c2c2c, stop: 1 #333333);
+ min-height: 22px;
+ }
+
+QLineEdit
+{
+ color: white;
+ font-size: creatorTheme.captionFontPixelSize;
+ border: 2px solid #0F0F0F;
+ border-width: 1;
+ min-height: 26px;
+ background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #2c2c2c, stop: 1 #333333);
+}
diff --git a/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp b/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp
new file mode 100644
index 0000000000..8e2d5224e1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp
@@ -0,0 +1,187 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "animationcurve.h"
+#include "detail/curvesegment.h"
+
+#include <QLineF>
+
+namespace DesignTools {
+
+AnimationCurve::AnimationCurve()
+ : m_frames()
+{}
+
+AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames)
+ : m_frames(frames)
+ , m_minY(std::numeric_limits<double>::max())
+ , m_maxY(std::numeric_limits<double>::lowest())
+{
+ if (isValid()) {
+
+ for (auto e : extrema()) {
+
+ if (m_minY > e.y())
+ m_minY = e.y();
+
+ if (m_maxY < e.y())
+ m_maxY = e.y();
+ }
+
+ for (auto &frame : qAsConst(m_frames)) {
+ if (frame.position().y() < m_minY)
+ m_minY = frame.position().y();
+
+ if (frame.position().y() > m_maxY)
+ m_maxY = frame.position().y();
+ }
+ }
+}
+
+bool AnimationCurve::isValid() const
+{
+ return m_frames.size() >= 2;
+}
+
+double AnimationCurve::minimumTime() const
+{
+ if (!m_frames.empty())
+ return m_frames.front().position().x();
+
+ return std::numeric_limits<double>::max();
+}
+
+double AnimationCurve::maximumTime() const
+{
+ if (!m_frames.empty())
+ return m_frames.back().position().x();
+
+ return std::numeric_limits<double>::lowest();
+}
+
+double AnimationCurve::minimumValue() const
+{
+ return m_minY;
+}
+
+double AnimationCurve::maximumValue() const
+{
+ return m_maxY;
+}
+
+std::vector<Keyframe> AnimationCurve::keyframes() const
+{
+ return m_frames;
+}
+
+std::vector<QPointF> AnimationCurve::extrema() const
+{
+ std::vector<QPointF> out;
+
+ CurveSegment segment;
+ segment.setLeft(m_frames.at(0));
+
+ for (size_t i = 1; i < m_frames.size(); ++i) {
+
+ segment.setRight(m_frames[i]);
+
+ const auto es = segment.extrema();
+ out.insert(std::end(out), std::begin(es), std::end(es));
+
+ segment.setLeft(m_frames[i]);
+ }
+
+ return out;
+}
+
+std::vector<double> AnimationCurve::yForX(double x) const
+{
+ if (m_frames.front().position().x() > x)
+ return std::vector<double>();
+
+ CurveSegment segment;
+ for (auto &frame : m_frames) {
+ if (frame.position().x() > x) {
+ segment.setRight(frame);
+ return segment.yForX(x);
+ }
+ segment.setLeft(frame);
+ }
+ return std::vector<double>();
+}
+
+std::vector<double> AnimationCurve::xForY(double y, uint segment) const
+{
+ if (m_frames.size() > segment + 1) {
+ CurveSegment seg(m_frames[segment], m_frames[segment + 1]);
+ return seg.xForY(y);
+ }
+ return std::vector<double>();
+}
+
+bool AnimationCurve::intersects(const QPointF &coord, double radius)
+{
+ if (m_frames.size() < 2)
+ return false;
+
+ std::vector<CurveSegment> influencer;
+
+ CurveSegment current;
+ current.setLeft(m_frames.at(0));
+
+ for (size_t i = 1; i < m_frames.size(); ++i) {
+ Keyframe &frame = m_frames.at(i);
+
+ current.setRight(frame);
+
+ if (current.containsX(coord.x() - radius) ||
+ current.containsX(coord.x()) ||
+ current.containsX(coord.x() + radius)) {
+ influencer.push_back(current);
+ }
+
+ if (frame.position().x() > coord.x() + radius)
+ break;
+
+ current.setLeft(frame);
+ }
+
+ for (auto &segment : influencer) {
+ for (auto &y : segment.yForX(coord.x())) {
+ QLineF line(coord.x(), y, coord.x(), coord.y());
+ if (line.length() < radius)
+ return true;
+ }
+
+ for (auto &x : segment.xForY(coord.y())) {
+ QLineF line(x, coord.y(), coord.x(), coord.y());
+ if (line.length() < radius)
+ return true;
+ }
+ }
+ return false;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/animationcurve.h b/src/plugins/qmldesigner/components/curveeditor/animationcurve.h
new file mode 100644
index 0000000000..0533e479a1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/animationcurve.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "keyframe.h"
+
+#include <vector>
+
+namespace DesignTools {
+
+class AnimationCurve
+{
+public:
+ AnimationCurve();
+
+ AnimationCurve(const std::vector<Keyframe> &frames);
+
+ bool isValid() const;
+
+ double minimumTime() const;
+
+ double maximumTime() const;
+
+ double minimumValue() const;
+
+ double maximumValue() const;
+
+ std::vector<Keyframe> keyframes() const;
+
+ std::vector<QPointF> extrema() const;
+
+ std::vector<double> yForX(double x) const;
+
+ std::vector<double> xForY(double y, uint segment) const;
+
+ bool intersects(const QPointF &coord, double radius);
+
+private:
+ std::vector<Keyframe> m_frames;
+
+ double m_minY;
+
+ double m_maxY;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp
new file mode 100644
index 0000000000..4eba31c6bd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "curveeditor.h"
+#include "curveeditormodel.h"
+#include "detail/curveitem.h"
+#include "detail/graphicsview.h"
+#include "detail/treeview.h"
+
+#include <QHBoxLayout>
+#include <QSplitter>
+
+namespace DesignTools {
+
+CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent)
+ : QWidget(parent)
+ , m_tree(new TreeView(model, this))
+ , m_view(new GraphicsView(model))
+{
+ QSplitter *splitter = new QSplitter;
+ splitter->addWidget(m_tree);
+ splitter->addWidget(m_view);
+ splitter->setStretchFactor(1, 2);
+
+ QHBoxLayout *box = new QHBoxLayout;
+ box->addWidget(splitter);
+ setLayout(box);
+
+ connect(m_tree, &TreeView::curvesSelected, m_view, &GraphicsView::reset);
+}
+
+void CurveEditor::zoomX(double zoom)
+{
+ m_view->setZoomX(zoom);
+}
+
+void CurveEditor::zoomY(double zoom)
+{
+ m_view->setZoomY(zoom);
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.h b/src/plugins/qmldesigner/components/curveeditor/curveeditor.h
new file mode 100644
index 0000000000..a2c5873be0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QWidget>
+
+namespace DesignTools {
+
+class CurveEditorModel;
+class GraphicsView;
+class TreeView;
+
+class CurveEditor : public QWidget
+{
+ Q_OBJECT
+
+public:
+ CurveEditor(CurveEditorModel *model, QWidget *parent = nullptr);
+
+ void zoomX(double zoom);
+
+ void zoomY(double zoom);
+
+private:
+ TreeView *m_tree;
+
+ GraphicsView *m_view;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri
new file mode 100644
index 0000000000..31ffe5d818
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri
@@ -0,0 +1,46 @@
+INCLUDEPATH += $$PWD
+
+HEADERS += \
+ $$PWD/animationcurve.h \
+ $$PWD/curveeditor.h \
+ $$PWD/curveeditormodel.h \
+ $$PWD/detail/colorcontrol.h \
+ $$PWD/detail/curveeditorstyledialog.h \
+ $$PWD/detail/curveitem.h \
+ $$PWD/detail/curvesegment.h \
+ $$PWD/detail/graphicsscene.h \
+ $$PWD/detail/graphicsview.h \
+ $$PWD/detail/handleitem.h \
+ $$PWD/detail/keyframeitem.h \
+ $$PWD/detail/playhead.h \
+ $$PWD/detail/selectableitem.h \
+ $$PWD/detail/selector.h \
+ $$PWD/detail/shortcut.h \
+ $$PWD/detail/treeitemdelegate.h \
+ $$PWD/detail/treemodel.h \
+ $$PWD/detail/treeview.h \
+ $$PWD/keyframe.h \
+ $$PWD/treeitem.h
+
+SOURCES += \
+ $$PWD/animationcurve.cpp \
+ $$PWD/curveeditor.cpp \
+ $$PWD/curveeditormodel.cpp \
+ $$PWD/detail/colorcontrol.cpp \
+ $$PWD/detail/curveeditorstyledialog.cpp \
+ $$PWD/detail/curveitem.cpp \
+ $$PWD/detail/curvesegment.cpp \
+ $$PWD/detail/graphicsscene.cpp \
+ $$PWD/detail/graphicsview.cpp \
+ $$PWD/detail/handleitem.cpp \
+ $$PWD/detail/keyframeitem.cpp \
+ $$PWD/detail/playhead.cpp \
+ $$PWD/detail/selectableitem.cpp \
+ $$PWD/detail/selector.cpp \
+ $$PWD/detail/shortcut.cpp \
+ $$PWD/detail/treeitemdelegate.cpp \
+ $$PWD/detail/treemodel.cpp \
+ $$PWD/detail/treeview.cpp \
+ $$PWD/detail/utils.cpp \
+ $$PWD/keyframe.cpp \
+ $$PWD/treeitem.cpp
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.cpp
new file mode 100644
index 0000000000..3b8b26b763
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "curveeditormodel.h"
+#include "treeitem.h"
+#include "detail/graphicsview.h"
+
+namespace DesignTools {
+
+CurveEditorModel::CurveEditorModel(QObject *parent)
+ : TreeModel(parent)
+{}
+
+CurveEditorModel::~CurveEditorModel() {}
+
+
+void CurveEditorModel::setCurrentFrame(int frame)
+{
+ if (graphicsView())
+ graphicsView()->setCurrentFrame(frame);
+}
+
+void CurveEditorModel::setCurve(unsigned int id, const AnimationCurve &curve)
+{
+ if (TreeItem *item = find(id)) {
+ if (PropertyTreeItem *propertyItem = item->asPropertyItem()) {
+ propertyItem->setCurve(curve);
+ emit curveChanged(propertyItem);
+ }
+ }
+}
+
+void CurveEditorModel::reset(const std::vector<TreeItem *> &items)
+{
+ beginResetModel();
+
+ initialize();
+
+ unsigned int counter = 0;
+ for (auto *item : items) {
+ item->setId(++counter);
+ root()->addChild(item);
+ }
+
+ endResetModel();
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.h b/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.h
new file mode 100644
index 0000000000..6e212d4c6a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditormodel.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "detail/treemodel.h"
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+class QPointF;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+struct CurveEditorStyle;
+
+class AnimationCurve;
+class PropertyTreeItem;
+class TreeItem;
+
+class CurveEditorModel : public TreeModel
+{
+ Q_OBJECT
+
+signals:
+ void currentFrameChanged(int frame);
+
+ void curveChanged(PropertyTreeItem *item);
+
+public:
+ virtual double minimumTime() const = 0;
+
+ virtual double maximumTime() const = 0;
+
+ virtual CurveEditorStyle style() const = 0;
+
+public:
+ CurveEditorModel(QObject *parent = nullptr);
+
+ ~CurveEditorModel() override;
+
+ void setCurrentFrame(int frame);
+
+ void setCurve(unsigned int id, const AnimationCurve &curve);
+
+ void reset(const std::vector<TreeItem *> &items);
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h
new file mode 100644
index 0000000000..03ea11c8c1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "detail/shortcut.h"
+
+#include <QBitmap>
+#include <QBrush>
+#include <QColor>
+#include <QDialog>
+#include <QIcon>
+#include <QKeySequence>
+
+namespace DesignTools {
+
+struct TreeItemStyleOption
+{
+ double margins;
+ QIcon pinnedIcon = QIcon(":/ICON_PINNED");
+ QIcon unpinnedIcon = QIcon(":/ICON_UNPINNED");
+ QIcon lockedIcon = QIcon(":/ICON_LOCKED");
+ QIcon unlockedIcon = QIcon(":/ICON_UNLOCKED");
+};
+
+struct HandleItemStyleOption
+{
+ double size = 10.0;
+ double lineWidth = 1.0;
+ QColor color = QColor(200, 0, 0);
+ QColor selectionColor = QColor(200, 200, 200);
+};
+
+struct KeyframeItemStyleOption
+{
+ double size = 10.0;
+ QColor color = QColor(200, 200, 0);
+ QColor selectionColor = QColor(200, 200, 200);
+};
+
+struct CurveItemStyleOption
+{
+ double width = 1.0;
+ QColor color = QColor(0, 200, 0);
+ QColor selectionColor = QColor(200, 200, 200);
+};
+
+struct PlayheadStyleOption
+{
+ double width = 20.0;
+ double radius = 4.0;
+ QColor color = QColor(200, 200, 0);
+};
+
+struct Shortcuts
+{
+ Shortcut newSelection = Shortcut(Qt::LeftButton);
+ Shortcut addToSelection = Shortcut(Qt::LeftButton, Qt::ControlModifier | Qt::ShiftModifier);
+ Shortcut removeFromSelection = Shortcut(Qt::LeftButton, Qt::ShiftModifier);
+ Shortcut toggleSelection = Shortcut(Qt::LeftButton, Qt::ControlModifier);
+
+ Shortcut zoom = Shortcut(Qt::RightButton, Qt::AltModifier);
+ Shortcut pan = Shortcut(Qt::MiddleButton, Qt::AltModifier);
+ Shortcut frameAll = Shortcut(Qt::NoModifier, Qt::Key_A);
+};
+
+struct CurveEditorStyle
+{
+ Shortcuts shortcuts;
+
+ QBrush backgroundBrush = QBrush(QColor(5, 0, 100));
+ QBrush backgroundAlternateBrush = QBrush(QColor(0, 0, 50));
+ QColor fontColor = QColor(200, 200, 200);
+ QColor gridColor = QColor(128, 128, 128);
+ double canvasMargin = 5.0;
+ int zoomInWidth = 100;
+ int zoomInHeight = 100;
+ double timeAxisHeight = 40.0;
+ double timeOffsetLeft = 10.0;
+ double timeOffsetRight = 10.0;
+ QColor rangeBarColor = QColor(128, 128, 128);
+ QColor rangeBarCapsColor = QColor(50, 50, 255);
+ double valueAxisWidth = 60.0;
+ double valueOffsetTop = 10.0;
+ double valueOffsetBottom = 10.0;
+
+ HandleItemStyleOption handleStyle;
+ KeyframeItemStyleOption keyframeStyle;
+ CurveItemStyleOption curveStyle;
+ TreeItemStyleOption treeItemStyle;
+ PlayheadStyleOption playhead;
+};
+
+inline QPixmap pixmapFromIcon(const QIcon &icon, const QSize &size, const QColor &color)
+{
+ QPixmap pixmap = icon.pixmap(size);
+ QPixmap mask(pixmap.size());
+ mask.fill(color);
+ mask.setMask(pixmap.createMaskFromColor(Qt::transparent));
+ return mask;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.cpp
new file mode 100644
index 0000000000..a833a4c6ff
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.cpp
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "colorcontrol.h"
+
+#include <QColorDialog>
+#include <QEvent>
+#include <QHelpEvent>
+#include <QPainter>
+#include <QToolTip>
+
+namespace DesignTools {
+
+ColorControl::ColorControl()
+ : QWidget(nullptr)
+ , m_color(Qt::black)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFixedHeight(20);
+}
+
+ColorControl::ColorControl(const QColor &color, QWidget *parent)
+ : QWidget(parent)
+ , m_color(color)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFixedHeight(20);
+}
+
+ColorControl::~ColorControl() = default;
+
+QColor ColorControl::value() const
+{
+ return m_color;
+}
+
+void ColorControl::setValue(const QColor &val)
+{
+ m_color = val;
+}
+
+bool ColorControl::event(QEvent *event)
+{
+ if (event->type() == QEvent::ToolTip) {
+ if (auto helpEvent = static_cast<const QHelpEvent *>(event)) {
+ QToolTip::showText(helpEvent->globalPos(), m_color.name());
+ return true;
+ }
+ }
+ return QWidget::event(event);
+}
+
+void ColorControl::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ painter.fillRect(event->rect(), m_color);
+}
+
+void ColorControl::mouseReleaseEvent(QMouseEvent *event)
+{
+ QColor color = QColorDialog::getColor(m_color, this);
+
+ event->accept();
+
+ if (color != m_color) {
+ m_color = color;
+ update();
+ emit valueChanged();
+ }
+}
+
+void ColorControl::mousePressEvent(QMouseEvent *event)
+{
+ // Required if embedded in a QGraphicsProxywidget
+ // in order to call mouseRelease properly.
+ QWidget::mousePressEvent(event);
+ event->accept();
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.h b/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.h
new file mode 100644
index 0000000000..54dfe194f8
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/colorcontrol.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QWidget>
+
+namespace DesignTools {
+
+class ColorControl : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ColorControl();
+
+ ColorControl(const QColor &color, QWidget *parent = nullptr);
+
+ ~ColorControl() override;
+
+ QColor value() const;
+
+ void setValue(const QColor &val);
+
+protected:
+ bool event(QEvent *event) override;
+
+ void paintEvent(QPaintEvent *event) override;
+
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+ void mousePressEvent(QMouseEvent *event) override;
+
+signals:
+ void valueChanged();
+
+private:
+ QColor m_color;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.cpp
new file mode 100644
index 0000000000..a8b653a74d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.cpp
@@ -0,0 +1,267 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "curveeditorstyledialog.h"
+#include "colorcontrol.h"
+#include "curveeditorstyle.h"
+
+#include <QDebug>
+#include <QDoubleSpinBox>
+#include <QLabel>
+#include <QPushButton>
+#include <QSpinBox>
+#include <QVBoxLayout>
+
+namespace DesignTools {
+
+QHBoxLayout *createRow(const QString &title, QWidget *widget)
+{
+ auto *label = new QLabel(title);
+ label->setFixedWidth(200);
+ label->setAlignment(Qt::AlignRight);
+
+ auto *box = new QHBoxLayout;
+ box->addWidget(label);
+ box->addWidget(widget);
+ return box;
+}
+
+CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget *parent)
+ : QDialog(parent)
+ , m_printButton(new QPushButton("Print"))
+ , m_background(new ColorControl(style.backgroundBrush.color()))
+ , m_backgroundAlternate(new ColorControl(style.backgroundAlternateBrush.color()))
+ , m_fontColor(new ColorControl(style.fontColor))
+ , m_gridColor(new ColorControl(style.gridColor))
+ , m_canvasMargin(new QDoubleSpinBox())
+ , m_zoomInWidth(new QSpinBox())
+ , m_zoomInHeight(new QSpinBox())
+ , m_timeAxisHeight(new QDoubleSpinBox())
+ , m_timeOffsetLeft(new QDoubleSpinBox())
+ , m_timeOffsetRight(new QDoubleSpinBox())
+ , m_rangeBarColor(new ColorControl(style.rangeBarCapsColor))
+ , m_rangeBarCapsColor(new ColorControl(style.rangeBarCapsColor))
+ , m_valueAxisWidth(new QDoubleSpinBox())
+ , m_valueOffsetTop(new QDoubleSpinBox())
+ , m_valueOffsetBottom(new QDoubleSpinBox())
+ , m_handleSize(new QDoubleSpinBox())
+ , m_handleLineWidth(new QDoubleSpinBox())
+ , m_handleColor(new ColorControl(style.handleStyle.color))
+ , m_handleSelectionColor(new ColorControl(style.handleStyle.selectionColor))
+ , m_keyframeSize(new QDoubleSpinBox())
+ , m_keyframeColor(new ColorControl(style.keyframeStyle.color))
+ , m_keyframeSelectionColor(new ColorControl(style.keyframeStyle.selectionColor))
+ , m_curveWidth(new QDoubleSpinBox())
+ , m_curveColor(new ColorControl(style.curveStyle.color))
+ , m_curveSelectionColor(new ColorControl(style.curveStyle.selectionColor))
+ , m_treeMargins(new QDoubleSpinBox())
+ , m_playheadWidth(new QDoubleSpinBox())
+ , m_playheadRadius(new QDoubleSpinBox())
+ , m_playheadColor(new ColorControl(style.playhead.color))
+
+{
+ m_canvasMargin->setValue(style.canvasMargin);
+ m_zoomInWidth->setValue(style.zoomInWidth);
+ m_zoomInHeight->setValue(style.zoomInHeight);
+ m_zoomInHeight->setMaximum(9000);
+
+ m_timeAxisHeight->setValue(style.timeAxisHeight);
+ m_timeOffsetLeft->setValue(style.timeOffsetLeft);
+ m_timeOffsetRight->setValue(style.timeOffsetRight);
+ m_valueAxisWidth->setValue(style.valueAxisWidth);
+ m_valueOffsetTop->setValue(style.valueOffsetTop);
+ m_valueOffsetBottom->setValue(style.valueOffsetBottom);
+ m_handleSize->setValue(style.handleStyle.size);
+ m_handleLineWidth->setValue(style.handleStyle.lineWidth);
+ m_keyframeSize->setValue(style.keyframeStyle.size);
+ m_curveWidth->setValue(style.curveStyle.width);
+ m_treeMargins->setValue(style.treeItemStyle.margins);
+ m_playheadWidth->setValue(style.playhead.width);
+ m_playheadRadius->setValue(style.playhead.radius);
+
+ connect(m_printButton, &QPushButton::released, this, &CurveEditorStyleDialog::printStyle);
+
+ auto intChanged = [this](int) { emitStyleChanged(); };
+ auto doubleChanged = [this](double) { emitStyleChanged(); };
+ auto colorChanged = [this]() { emitStyleChanged(); };
+
+ auto intSignal = static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged);
+ auto doubleSignal = static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged);
+
+ connect(m_background, &ColorControl::valueChanged, colorChanged);
+ connect(m_backgroundAlternate, &ColorControl::valueChanged, colorChanged);
+ connect(m_fontColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_gridColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_canvasMargin, doubleSignal, doubleChanged);
+ connect(m_zoomInWidth, intSignal, intChanged);
+ connect(m_zoomInHeight, intSignal, intChanged);
+ connect(m_timeAxisHeight, doubleSignal, doubleChanged);
+ connect(m_timeOffsetLeft, doubleSignal, doubleChanged);
+ connect(m_timeOffsetRight, doubleSignal, doubleChanged);
+ connect(m_rangeBarColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_rangeBarCapsColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_valueAxisWidth, doubleSignal, doubleChanged);
+ connect(m_valueOffsetTop, doubleSignal, doubleChanged);
+ connect(m_valueOffsetBottom, doubleSignal, doubleChanged);
+ connect(m_handleSize, doubleSignal, doubleChanged);
+ connect(m_handleLineWidth, doubleSignal, doubleChanged);
+ connect(m_handleColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_handleSelectionColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_keyframeSize, doubleSignal, doubleChanged);
+ connect(m_keyframeColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_keyframeSelectionColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_curveWidth, doubleSignal, doubleChanged);
+ connect(m_curveColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_curveSelectionColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_treeMargins, doubleSignal, doubleChanged);
+ connect(m_playheadWidth, doubleSignal, doubleChanged);
+ connect(m_playheadRadius, doubleSignal, doubleChanged);
+ connect(m_playheadColor, &ColorControl::valueChanged, colorChanged);
+
+ auto *box = new QVBoxLayout;
+ box->addLayout(createRow("Background Color", m_background));
+ box->addLayout(createRow("Alternate Background Color", m_backgroundAlternate));
+ box->addLayout(createRow("Font Color", m_fontColor));
+ box->addLayout(createRow("Grid Color", m_gridColor));
+ box->addLayout(createRow("Canvas Margin", m_canvasMargin));
+ box->addLayout(createRow("Zoom In Width", m_zoomInWidth));
+ box->addLayout(createRow("Zoom In Height", m_zoomInHeight));
+ box->addLayout(createRow("Time Axis Height", m_timeAxisHeight));
+ box->addLayout(createRow("Time Axis Left Offset", m_timeOffsetLeft));
+ box->addLayout(createRow("Time Axis Right Offset", m_timeOffsetRight));
+ box->addLayout(createRow("Range Bar Color", m_rangeBarColor));
+ box->addLayout(createRow("Range Bar Caps Color", m_rangeBarCapsColor));
+ box->addLayout(createRow("Value Axis Width", m_valueAxisWidth));
+ box->addLayout(createRow("Value Axis Top Offset", m_valueOffsetTop));
+ box->addLayout(createRow("Value Axis Bottom Offset", m_valueOffsetBottom));
+ box->addLayout(createRow("Handle Size", m_handleSize));
+ box->addLayout(createRow("Handle Line Width", m_handleLineWidth));
+ box->addLayout(createRow("Handle Color", m_handleColor));
+ box->addLayout(createRow("Handle Selection Color", m_handleSelectionColor));
+ box->addLayout(createRow("Keyframe Size", m_keyframeSize));
+ box->addLayout(createRow("Keyframe Color", m_keyframeColor));
+ box->addLayout(createRow("Keyframe Selection Color", m_keyframeSelectionColor));
+ box->addLayout(createRow("Curve Width", m_curveWidth));
+ box->addLayout(createRow("Curve Color", m_curveColor));
+ box->addLayout(createRow("Curve Selection Color", m_curveSelectionColor));
+ box->addLayout(createRow("Treeview margins", m_treeMargins));
+ box->addLayout(createRow("Playhead width", m_playheadWidth));
+ box->addLayout(createRow("Playhead radius", m_playheadRadius));
+ box->addLayout(createRow("Playhead color", m_playheadColor));
+
+ box->addWidget(m_printButton);
+ setLayout(box);
+}
+
+CurveEditorStyle CurveEditorStyleDialog::style() const
+{
+ CurveEditorStyle style;
+ style.backgroundBrush = QBrush(m_background->value());
+ style.backgroundAlternateBrush = QBrush(m_backgroundAlternate->value());
+ style.fontColor = m_fontColor->value();
+ style.gridColor = m_gridColor->value();
+ style.canvasMargin = m_canvasMargin->value();
+ style.zoomInWidth = m_zoomInWidth->value();
+ style.zoomInHeight = m_zoomInHeight->value();
+ style.timeAxisHeight = m_timeAxisHeight->value();
+ style.timeOffsetLeft = m_timeOffsetLeft->value();
+ style.timeOffsetRight = m_timeOffsetRight->value();
+ style.rangeBarColor = m_rangeBarColor->value();
+ style.rangeBarCapsColor = m_rangeBarCapsColor->value();
+ style.valueAxisWidth = m_valueAxisWidth->value();
+ style.valueOffsetTop = m_valueOffsetTop->value();
+ style.valueOffsetBottom = m_valueOffsetBottom->value();
+ style.handleStyle.size = m_handleSize->value();
+ style.handleStyle.lineWidth = m_handleLineWidth->value();
+ style.handleStyle.color = m_handleColor->value();
+ style.handleStyle.selectionColor = m_handleSelectionColor->value();
+ style.keyframeStyle.size = m_keyframeSize->value();
+ style.keyframeStyle.color = m_keyframeColor->value();
+ style.keyframeStyle.selectionColor = m_keyframeSelectionColor->value();
+ style.curveStyle.width = m_curveWidth->value();
+ style.curveStyle.color = m_curveColor->value();
+ style.curveStyle.selectionColor = m_curveSelectionColor->value();
+ style.treeItemStyle.margins = m_treeMargins->value();
+ style.playhead.width = m_playheadWidth->value();
+ style.playhead.radius = m_playheadRadius->value();
+ style.playhead.color = m_playheadColor->value();
+
+ return style;
+}
+
+void CurveEditorStyleDialog::emitStyleChanged()
+{
+ emit styleChanged(style());
+}
+
+void CurveEditorStyleDialog::printStyle()
+{
+ auto toString = [](const QColor &color) {
+ QString tmp
+ = QString("QColor(%1, %2, %3)").arg(color.red()).arg(color.green()).arg(color.blue());
+ return qPrintable(tmp);
+ };
+
+ CurveEditorStyle s = style();
+ qDebug() << "";
+ qDebug().nospace() << "CurveEditorStyle out;";
+ qDebug().nospace() << "out.backgroundBrush = QBrush(" << toString(s.backgroundBrush.color())
+ << ");";
+ qDebug().nospace() << "out.backgroundAlternateBrush = QBrush("
+ << toString(s.backgroundAlternateBrush.color()) << ");";
+ qDebug().nospace() << "out.fontColor = " << toString(s.fontColor) << ";";
+ qDebug().nospace() << "out.gridColor = " << toString(s.gridColor) << ";";
+ qDebug().nospace() << "out.canvasMargin = " << s.canvasMargin << ";";
+ qDebug().nospace() << "out.zoomInWidth = " << s.zoomInWidth << ";";
+ qDebug().nospace() << "out.zoomInHeight = " << s.zoomInHeight << ";";
+ qDebug().nospace() << "out.timeAxisHeight = " << s.timeAxisHeight << ";";
+ qDebug().nospace() << "out.timeOffsetLeft = " << s.timeOffsetLeft << ";";
+ qDebug().nospace() << "out.timeOffsetRight = " << s.timeOffsetRight << ";";
+ qDebug().nospace() << "out.rangeBarColor = " << toString(s.rangeBarColor) << ";";
+ qDebug().nospace() << "out.rangeBarCapsColor = " << toString(s.rangeBarCapsColor) << ";";
+ qDebug().nospace() << "out.valueAxisWidth = " << s.valueAxisWidth << ";";
+ qDebug().nospace() << "out.valueOffsetTop = " << s.valueOffsetTop << ";";
+ qDebug().nospace() << "out.valueOffsetBottom = " << s.valueOffsetBottom << ";";
+ qDebug().nospace() << "out.handleStyle.size = " << s.handleStyle.size << ";";
+ qDebug().nospace() << "out.handleStyle.lineWidth = " << s.handleStyle.lineWidth << ";";
+ qDebug().nospace() << "out.handleStyle.color = " << toString(s.handleStyle.color) << ";";
+ qDebug().nospace() << "out.handleStyle.selectionColor = "
+ << toString(s.handleStyle.selectionColor) << ";";
+ qDebug().nospace() << "out.keyframeStyle.size = " << s.keyframeStyle.size << ";";
+ qDebug().nospace() << "out.keyframeStyle.color = " << toString(s.keyframeStyle.color) << ";";
+ qDebug().nospace() << "out.keyframeStyle.selectionColor = "
+ << toString(s.keyframeStyle.selectionColor) << ";";
+ qDebug().nospace() << "out.curveStyle.width = " << s.curveStyle.width << ";";
+ qDebug().nospace() << "out.curveStyle.color = " << toString(s.curveStyle.color) << ";";
+ qDebug().nospace() << "out.curveStyle.selectionColor = "
+ << toString(s.curveStyle.selectionColor) << ";";
+ qDebug().nospace() << "out.treeItemStyle.margins = " << s.treeItemStyle.margins << ";";
+ qDebug().nospace() << "out.playheadStyle.width = " << s.playhead.width << ";";
+ qDebug().nospace() << "out.playheadStyle.radius = " << s.playhead.radius << ";";
+ qDebug().nospace() << "out.playheadStyle.color = " << toString(s.playhead.color) << ";";
+ qDebug().nospace() << "return out;";
+ qDebug() << "";
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.h b/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.h
new file mode 100644
index 0000000000..f1dc3bc372
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveeditorstyledialog.h
@@ -0,0 +1,127 @@
+
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+class QPushButton;
+class QSpinBox;
+class QDoubleSpinBox;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+class ColorControl;
+
+struct CurveEditorStyle;
+
+class CurveEditorStyleDialog : public QDialog
+{
+ Q_OBJECT
+
+signals:
+ void styleChanged(const CurveEditorStyle &style);
+
+public:
+ CurveEditorStyleDialog(CurveEditorStyle &style, QWidget *parent = nullptr);
+
+ CurveEditorStyle style() const;
+
+private:
+ void emitStyleChanged();
+
+ void printStyle();
+
+private:
+ QPushButton *m_printButton;
+
+ ColorControl *m_background;
+
+ ColorControl *m_backgroundAlternate;
+
+ ColorControl *m_fontColor;
+
+ ColorControl *m_gridColor;
+
+ QDoubleSpinBox *m_canvasMargin;
+
+ QSpinBox *m_zoomInWidth;
+
+ QSpinBox *m_zoomInHeight;
+
+ QDoubleSpinBox *m_timeAxisHeight;
+
+ QDoubleSpinBox *m_timeOffsetLeft;
+
+ QDoubleSpinBox *m_timeOffsetRight;
+
+ ColorControl *m_rangeBarColor;
+
+ ColorControl *m_rangeBarCapsColor;
+
+ QDoubleSpinBox *m_valueAxisWidth;
+
+ QDoubleSpinBox *m_valueOffsetTop;
+
+ QDoubleSpinBox *m_valueOffsetBottom;
+
+ // HandleItem
+ QDoubleSpinBox *m_handleSize;
+
+ QDoubleSpinBox *m_handleLineWidth;
+
+ ColorControl *m_handleColor;
+
+ ColorControl *m_handleSelectionColor;
+
+ // KeyframeItem
+ QDoubleSpinBox *m_keyframeSize;
+
+ ColorControl *m_keyframeColor;
+
+ ColorControl *m_keyframeSelectionColor;
+
+ // CurveItem
+ QDoubleSpinBox *m_curveWidth;
+
+ ColorControl *m_curveColor;
+
+ ColorControl *m_curveSelectionColor;
+
+ // TreeItem
+ QDoubleSpinBox *m_treeMargins;
+
+ // Playhead
+ QDoubleSpinBox *m_playheadWidth;
+
+ QDoubleSpinBox *m_playheadRadius;
+
+ ColorControl *m_playheadColor;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp
new file mode 100644
index 0000000000..38efd29571
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "curveitem.h"
+#include "animationcurve.h"
+#include "graphicsscene.h"
+#include "keyframeitem.h"
+#include "utils.h"
+
+#include <QPainter>
+#include <QPainterPath>
+
+#include <cmath>
+
+namespace DesignTools {
+
+CurveItem::CurveItem(QGraphicsItem *parent)
+ : QGraphicsObject(parent)
+ , m_id(0)
+ , m_style()
+ , m_transform()
+ , m_keyframes()
+ , m_underMouse(false)
+ , m_itemDirty(false)
+ , m_pathDirty(true)
+{}
+
+CurveItem::CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem *parent)
+ : QGraphicsObject(parent)
+ , m_id(id)
+ , m_style()
+ , m_transform()
+ , m_keyframes()
+ , m_underMouse(false)
+ , m_itemDirty(false)
+ , m_pathDirty(true)
+{
+ setAcceptHoverEvents(true);
+
+ setFlag(QGraphicsItem::ItemIsMovable, false);
+
+ auto emitCurveChanged = [this]() {
+ m_itemDirty = true;
+ m_pathDirty = true;
+ update();
+ };
+
+ for (auto frame : curve.keyframes()) {
+ auto *item = new KeyframeItem(frame, this);
+ QObject::connect(item, &KeyframeItem::redrawCurve, emitCurveChanged);
+ m_keyframes.push_back(item);
+ }
+}
+
+CurveItem::~CurveItem() {}
+
+int CurveItem::type() const
+{
+ return Type;
+}
+
+QRectF CurveItem::boundingRect() const
+{
+ auto bbox = [](QRectF &bounds, const Keyframe &frame) {
+ grow(bounds, frame.position());
+ grow(bounds, frame.leftHandle());
+ grow(bounds, frame.rightHandle());
+ };
+
+ QRectF bounds;
+ for (auto *item : m_keyframes)
+ bbox(bounds, item->keyframe());
+
+ return m_transform.mapRect(bounds);
+}
+
+bool CurveItem::contains(const QPointF &point) const
+{
+ bool valid = false;
+ QPointF transformed(m_transform.inverted(&valid).map(point));
+
+ double width = std::abs(20.0 / scaleY(m_transform));
+
+ if (valid)
+ return curve().intersects(transformed, width);
+
+ return false;
+}
+
+void CurveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ if (m_keyframes.size() > 1) {
+ QPen pen = painter->pen();
+ QColor col = m_underMouse ? Qt::red : m_style.color;
+
+ pen.setWidthF(m_style.width);
+ pen.setColor(hasSelection() ? m_style.selectionColor : col);
+
+ painter->save();
+ painter->setPen(pen);
+ painter->drawPath(path());
+
+ painter->restore();
+ }
+}
+
+bool CurveItem::isDirty() const
+{
+ return m_itemDirty;
+}
+
+bool CurveItem::hasSelection() const
+{
+ for (auto *frame : m_keyframes) {
+ if (frame->selected())
+ return true;
+ }
+
+ return false;
+}
+
+unsigned int CurveItem::id() const
+{
+ return m_id;
+}
+
+QPainterPath CurveItem::path() const
+{
+ if (m_pathDirty) {
+ Keyframe previous = m_keyframes.front()->keyframe();
+ Keyframe current;
+
+ m_path = QPainterPath(m_transform.map(previous.position()));
+ for (size_t i = 1; i < m_keyframes.size(); ++i) {
+ current = m_keyframes[i]->keyframe();
+
+ if (previous.rightHandle().isNull() || current.leftHandle().isNull()) {
+ m_path.lineTo(m_transform.map(current.position()));
+ } else {
+ m_path.cubicTo(
+ m_transform.map(previous.rightHandle()),
+ m_transform.map(current.leftHandle()),
+ m_transform.map(current.position()));
+ }
+
+ previous = current;
+ }
+ m_pathDirty = false;
+ }
+
+ return m_path;
+}
+
+AnimationCurve CurveItem::curve() const
+{
+ std::vector<Keyframe> out;
+ out.reserve(m_keyframes.size());
+ for (auto item : m_keyframes)
+ out.push_back(item->keyframe());
+
+ return out;
+}
+
+void CurveItem::setDirty(bool dirty)
+{
+ m_itemDirty = dirty;
+}
+
+QRectF CurveItem::setComponentTransform(const QTransform &transform)
+{
+ m_pathDirty = true;
+
+ prepareGeometryChange();
+ m_transform = transform;
+ for (auto frame : m_keyframes)
+ frame->setComponentTransform(transform);
+
+ return boundingRect();
+}
+
+void CurveItem::setStyle(const CurveEditorStyle &style)
+{
+ m_style = style.curveStyle;
+
+ for (auto *frame : m_keyframes)
+ frame->setStyle(style);
+}
+
+void CurveItem::connect(GraphicsScene *scene)
+{
+ for (auto *frame : m_keyframes) {
+ QObject::connect(frame, &KeyframeItem::keyframeMoved, scene, &GraphicsScene::keyframeMoved);
+ QObject::connect(frame, &KeyframeItem::handleMoved, scene, &GraphicsScene::handleMoved);
+ }
+}
+
+void CurveItem::setIsUnderMouse(bool under)
+{
+ if (under != m_underMouse) {
+ m_underMouse = under;
+ update();
+ }
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h
new file mode 100644
index 0000000000..90e68e20f1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+#include "selectableitem.h"
+
+#include <QGraphicsObject>
+
+namespace DesignTools {
+
+class AnimationCurve;
+class KeyframeItem;
+class GraphicsScene;
+
+class CurveItem : public QGraphicsObject
+{
+ Q_OBJECT
+
+public:
+ CurveItem(QGraphicsItem *parent = nullptr);
+
+ CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem *parent = nullptr);
+
+ ~CurveItem() override;
+
+ enum { Type = ItemTypeCurve };
+
+ int type() const override;
+
+ QRectF boundingRect() const override;
+
+ bool contains(const QPointF &point) const override;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ bool isDirty() const;
+
+ bool hasSelection() const;
+
+ unsigned int id() const;
+
+ AnimationCurve curve() const;
+
+ void setDirty(bool dirty);
+
+ QRectF setComponentTransform(const QTransform &transform);
+
+ void setStyle(const CurveEditorStyle &style);
+
+ void connect(GraphicsScene *scene);
+
+ void setIsUnderMouse(bool under);
+
+private:
+ QPainterPath path() const;
+
+ unsigned int m_id;
+
+ CurveItemStyleOption m_style;
+
+ QTransform m_transform;
+
+ std::vector<KeyframeItem *> m_keyframes;
+
+ bool m_underMouse;
+
+ bool m_itemDirty;
+
+ mutable bool m_pathDirty;
+
+ mutable QPainterPath m_path;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp
new file mode 100644
index 0000000000..40f675f3ec
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp
@@ -0,0 +1,277 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "curvesegment.h"
+#include "utils.h"
+
+#include <qmath.h>
+
+#include <assert.h>
+
+namespace DesignTools {
+
+class CubicPolynomial
+{
+public:
+ CubicPolynomial(double p0, double p1, double p2, double p3);
+
+ std::vector<double> extrema() const;
+
+ std::vector<double> roots() const;
+
+private:
+ double m_a;
+ double m_b;
+ double m_c;
+ double m_d;
+};
+
+CubicPolynomial::CubicPolynomial(double p0, double p1, double p2, double p3)
+ : m_a(p3 - 3.0 * p2 + 3.0 * p1 - p0)
+ , m_b(3.0 * p2 - 6.0 * p1 + 3.0 * p0)
+ , m_c(3.0 * p1 - 3.0 * p0)
+ , m_d(p0)
+{}
+
+std::vector<double> CubicPolynomial::extrema() const
+{
+ std::vector<double> out;
+
+ auto addValidValue = [&out](double value) {
+ if (!std::isnan(value) && !std::isinf(value))
+ out.push_back(clamp(value, 0.0, 1.0));
+ };
+
+ // Find the roots of the first derivative of y.
+ auto pd2 = (2.0 * m_b) / (3.0 * m_a) / 2.0;
+ auto q = m_c / (3.0 * m_a);
+
+ auto radi = std::pow(pd2, 2.0) - q;
+
+ auto x1 = -pd2 + std::sqrt(radi);
+ auto x2 = -pd2 - std::sqrt(radi);
+
+ addValidValue(x1);
+ addValidValue(x2);
+
+ return out;
+}
+
+std::vector<double> CubicPolynomial::roots() const
+{
+ std::vector<double> out;
+
+ auto addValidValue = [&out](double value) {
+ if (!(std::isnan(value) || std::isinf(value)))
+ out.push_back(value);
+ };
+
+ if (m_a == 0.0) {
+ // Linear
+ if (m_b == 0.0) {
+ if (m_c != 0.0)
+ out.push_back(-m_d / m_c);
+ // Quadratic
+ } else {
+ const double p = m_c / m_b / 2.0;
+ const double q = m_d / m_b;
+ addValidValue(-p + std::sqrt(std::pow(p, 2.0) - q));
+ addValidValue(-p - std::sqrt(std::pow(p, 2.0) - q));
+ }
+ // Cubic
+ } else {
+ const double p = 3.0 * m_a * m_c - std::pow(m_b, 2.0);
+ const double q = 2.0 * std::pow(m_b, 3.0) - 9.0 * m_a * m_b * m_c
+ + 27.0 * std::pow(m_a, 2.0) * m_d;
+
+ auto disc = std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0);
+
+ auto toX = [&](double y) { return (y - m_b) / (3.0 * m_a); };
+
+ // One real solution.
+ if (disc >= 0) {
+ auto u = (1.0 / 2.0)
+ * std::cbrt(-4.0 * q
+ + 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0)));
+ auto v = (1.0 / 2.0)
+ * std::cbrt(-4.0 * q
+ - 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0)));
+
+ addValidValue(toX(u + v));
+ // Three real solutions.
+ } else {
+ auto phi = acos(-q / (2 * std::sqrt(-std::pow(p, 3.0))));
+ auto y1 = std::sqrt(-p) * 2.0 * cos(phi / 3.0);
+ auto y2 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (2.0 * M_PI / 3.0));
+ auto y3 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (4.0 * M_PI / 3.0));
+
+ addValidValue(toX(y1));
+ addValidValue(toX(y2));
+ addValidValue(toX(y3));
+ }
+ }
+ return out;
+}
+
+CurveSegment::CurveSegment()
+ : m_left()
+ , m_right()
+{}
+
+CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right)
+ : m_left(left)
+ , m_right(right)
+{}
+
+bool CurveSegment::containsX(double x) const
+{
+ return m_left.position().x() <= x && m_right.position().x() >= x;
+}
+
+double evaluateForT(double t, double p0, double p1, double p2, double p3)
+{
+ assert(t >= 0. && t <= 1.);
+
+ const double it = 1.0 - t;
+
+ return p0 * std::pow(it, 3.0) + p1 * 3.0 * std::pow(it, 2.0) * t
+ + p2 * 3.0 * it * std::pow(t, 2.0) + p3 * std::pow(t, 3.0);
+}
+
+QPointF CurveSegment::evaluate(double t) const
+{
+ const double x = evaluateForT(
+ t,
+ m_left.position().x(),
+ m_left.rightHandle().x(),
+ m_right.leftHandle().x(),
+ m_right.position().x());
+
+ const double y = evaluateForT(
+ t,
+ m_left.position().y(),
+ m_left.rightHandle().y(),
+ m_right.leftHandle().y(),
+ m_right.position().y());
+
+ return QPointF(x, y);
+}
+
+std::vector<QPointF> CurveSegment::extrema() const
+{
+ std::vector<QPointF> out;
+
+ auto polynomial = CubicPolynomial(
+ m_left.position().y(),
+ m_left.rightHandle().y(),
+ m_right.leftHandle().y(),
+ m_right.position().y());
+
+ for (double t : polynomial.extrema()) {
+
+ const double x = evaluateForT(
+ t,
+ m_left.position().x(),
+ m_left.rightHandle().x(),
+ m_right.leftHandle().x(),
+ m_right.position().x());
+
+ const double y = evaluateForT(
+ t,
+ m_left.position().y(),
+ m_left.rightHandle().y(),
+ m_right.leftHandle().y(),
+ m_right.position().y());
+
+ out.push_back(QPointF(x, y));
+ }
+ return out;
+}
+
+std::vector<double> CurveSegment::yForX(double x) const
+{
+ std::vector<double> out;
+
+ auto polynomial = CubicPolynomial(
+ m_left.position().x() - x,
+ m_left.rightHandle().x() - x,
+ m_right.leftHandle().x() - x,
+ m_right.position().x() - x);
+
+ for (double t : polynomial.roots()) {
+ if (t < 0.0 || t > 1.0)
+ continue;
+
+ const double y = evaluateForT(
+ t,
+ m_left.position().y(),
+ m_left.rightHandle().y(),
+ m_right.leftHandle().y(),
+ m_right.position().y());
+
+ out.push_back(y);
+ }
+
+ return out;
+}
+
+std::vector<double> CurveSegment::xForY(double y) const
+{
+ std::vector<double> out;
+
+ auto polynomial = CubicPolynomial(
+ m_left.position().y() - y,
+ m_left.rightHandle().y() - y,
+ m_right.leftHandle().y() - y,
+ m_right.position().y() - y);
+
+ for (double t : polynomial.roots()) {
+ if (t < 0.0 || t > 1.0)
+ continue;
+
+ const double x = evaluateForT(
+ t,
+ m_left.position().x(),
+ m_left.rightHandle().x(),
+ m_right.leftHandle().x(),
+ m_right.position().x());
+
+ out.push_back(x);
+ }
+
+ return out;
+}
+
+void CurveSegment::setLeft(const Keyframe &frame)
+{
+ m_left = frame;
+}
+
+void CurveSegment::setRight(const Keyframe &frame)
+{
+ m_right = frame;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h b/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h
new file mode 100644
index 0000000000..5dbce58bcc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "keyframe.h"
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+class QPointF;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+class CurveSegment
+{
+public:
+ CurveSegment();
+
+ CurveSegment(const Keyframe &first, const Keyframe &last);
+
+ bool containsX(double x) const;
+
+ QPointF evaluate(double t) const;
+
+ std::vector<QPointF> extrema() const;
+
+ std::vector<double> yForX(double x) const;
+
+ std::vector<double> xForY(double y) const;
+
+ void setLeft(const Keyframe &frame);
+
+ void setRight(const Keyframe &frame);
+
+private:
+ Keyframe m_left;
+
+ Keyframe m_right;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp
new file mode 100644
index 0000000000..096e57aafa
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp
@@ -0,0 +1,225 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "graphicsscene.h"
+#include "animationcurve.h"
+#include "curveitem.h"
+#include "graphicsview.h"
+#include "handleitem.h"
+
+#include <QGraphicsSceneMouseEvent>
+
+namespace DesignTools {
+
+GraphicsScene::GraphicsScene(QObject *parent)
+ : QGraphicsScene(parent)
+ , m_dirty(true)
+ , m_limits()
+{}
+
+bool GraphicsScene::empty() const
+{
+ return items().empty();
+}
+
+double GraphicsScene::minimumTime() const
+{
+ return limits().left();
+}
+
+double GraphicsScene::maximumTime() const
+{
+ return limits().right();
+}
+
+double GraphicsScene::minimumValue() const
+{
+ return limits().bottom();
+}
+
+double GraphicsScene::maximumValue() const
+{
+ return limits().top();
+}
+
+void GraphicsScene::addCurveItem(CurveItem *item)
+{
+ m_dirty = true;
+ addItem(item);
+ item->connect(this);
+}
+
+void GraphicsScene::setComponentTransform(const QTransform &transform)
+{
+ QRectF bounds;
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item))
+ bounds = bounds.united(curveItem->setComponentTransform(transform));
+ }
+
+ if (bounds.isNull()) {
+ if (GraphicsView *gview = graphicsView())
+ bounds = gview->defaultRasterRect();
+ }
+
+ if (bounds.isValid())
+ setSceneRect(bounds);
+}
+
+void GraphicsScene::keyframeMoved(KeyframeItem *movedItem, const QPointF &direction)
+{
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (item == movedItem)
+ continue;
+
+ if (auto *frameItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ if (frameItem->selected())
+ frameItem->moveKeyframe(direction);
+ }
+ }
+}
+
+void GraphicsScene::handleMoved(KeyframeItem *frame,
+ HandleSlot handle,
+ double angle,
+ double deltaLength)
+{
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (item == frame)
+ continue;
+
+ if (auto *frameItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ if (frameItem->selected())
+ frameItem->moveHandle(handle, angle, deltaLength);
+ }
+ }
+}
+
+void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
+{
+ QGraphicsScene::mouseMoveEvent(mouseEvent);
+
+ if (hasActiveItem())
+ return;
+
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item))
+ curveItem->setIsUnderMouse(curveItem->contains(mouseEvent->scenePos()));
+ }
+}
+
+void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
+{
+ QGraphicsScene::mouseReleaseEvent(mouseEvent);
+
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) {
+ if (curveItem->contains(mouseEvent->scenePos()))
+ curveItem->setSelected(true);
+
+ if (curveItem->isDirty()) {
+ emit curveChanged(curveItem->id(), curveItem->curve());
+ curveItem->setDirty(false);
+ m_dirty = true;
+ }
+ }
+ }
+}
+
+bool GraphicsScene::hasActiveKeyframe() const
+{
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *kitem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ if (kitem->activated())
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicsScene::hasActiveHandle() const
+{
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *hitem = qgraphicsitem_cast<HandleItem *>(item)) {
+ if (hitem->activated())
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicsScene::hasActiveItem() const
+{
+ return hasActiveKeyframe() || hasActiveHandle();
+}
+
+GraphicsView *GraphicsScene::graphicsView() const
+{
+ const QList<QGraphicsView *> viewList = views();
+ for (auto &&view : viewList) {
+ if (GraphicsView *gview = qobject_cast<GraphicsView *>(view))
+ return gview;
+ }
+ return nullptr;
+}
+
+QRectF GraphicsScene::limits() const
+{
+ if (m_dirty) {
+ QPointF min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
+ QPointF max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest());
+
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) {
+ auto curve = curveItem->curve();
+ if (min.x() > curve.minimumTime())
+ min.rx() = curve.minimumTime();
+
+ if (min.y() > curve.minimumValue())
+ min.ry() = curve.minimumValue();
+
+ if (max.x() < curve.maximumTime())
+ max.rx() = curve.maximumTime();
+
+ if (max.y() < curve.maximumValue())
+ max.ry() = curve.maximumValue();
+ }
+ }
+
+ m_limits = QRectF(QPointF(min.x(), max.y()), QPointF(max.x(), min.y()));
+ m_dirty = false;
+ }
+ return m_limits;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.h b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.h
new file mode 100644
index 0000000000..981c326b5a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "keyframeitem.h"
+
+#include <QGraphicsScene>
+
+namespace DesignTools {
+
+class AnimationCurve;
+class CurveItem;
+class GraphicsView;
+
+class GraphicsScene : public QGraphicsScene
+{
+ Q_OBJECT
+
+signals:
+ void curveChanged(unsigned int id, const AnimationCurve &curve);
+
+public:
+ GraphicsScene(QObject *parent = nullptr);
+
+ bool empty() const;
+
+ bool hasActiveKeyframe() const;
+
+ bool hasActiveHandle() const;
+
+ bool hasActiveItem() const;
+
+ double minimumTime() const;
+
+ double maximumTime() const;
+
+ double minimumValue() const;
+
+ double maximumValue() const;
+
+ void addCurveItem(CurveItem *item);
+
+ void setComponentTransform(const QTransform &transform);
+
+ void keyframeMoved(KeyframeItem *item, const QPointF &direction);
+
+ void handleMoved(KeyframeItem *frame, HandleSlot handle, double angle, double deltaLength);
+
+protected:
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
+
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
+
+private:
+ using QGraphicsScene::addItem;
+
+ GraphicsView *graphicsView() const;
+
+ QRectF limits() const;
+
+ mutable bool m_dirty;
+
+ mutable QRectF m_limits;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp
new file mode 100644
index 0000000000..ae60888d1f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp
@@ -0,0 +1,523 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "graphicsview.h"
+#include "curveeditormodel.h"
+#include "curveitem.h"
+#include "utils.h"
+
+#include <QAction>
+#include <QMenu>
+#include <QResizeEvent>
+#include <QScrollBar>
+
+#include <cmath>
+
+namespace DesignTools {
+
+GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent)
+ : QGraphicsView(parent)
+ , m_zoomX(0.0)
+ , m_zoomY(0.0)
+ , m_transform()
+ , m_scene()
+ , m_model(model)
+ , m_playhead(this)
+ , m_selector()
+ , m_style(model->style())
+ , m_dialog(m_style)
+{
+ model->setGraphicsView(this);
+
+ setScene(&m_scene);
+ setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ setResizeAnchor(QGraphicsView::NoAnchor);
+ setTransformationAnchor(QGraphicsView::NoAnchor);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ connect(&m_dialog, &CurveEditorStyleDialog::styleChanged, this, &GraphicsView::setStyle);
+
+ auto itemSlot = [this](unsigned int id, const AnimationCurve &curve) {
+ applyZoom(m_zoomX, m_zoomY);
+ m_model->setCurve(id, curve);
+ };
+
+ connect(&m_scene, &GraphicsScene::curveChanged, itemSlot);
+
+ applyZoom(m_zoomX, m_zoomY);
+ update();
+}
+
+CurveEditorModel *GraphicsView::model() const
+{
+ return m_model;
+}
+
+CurveEditorStyle GraphicsView::editorStyle() const
+{
+ return m_style;
+}
+
+bool GraphicsView::hasActiveItem() const
+{
+ return m_scene.hasActiveItem();
+}
+
+bool GraphicsView::hasActiveHandle() const
+{
+ return m_scene.hasActiveHandle();
+}
+
+double GraphicsView::minimumTime() const
+{
+ bool check = m_model->minimumTime() < m_scene.minimumTime();
+ return check ? m_model->minimumTime() : m_scene.minimumTime();
+}
+
+double GraphicsView::maximumTime() const
+{
+ bool check = m_model->maximumTime() > m_scene.maximumTime();
+ return check ? m_model->maximumTime() : m_scene.maximumTime();
+}
+
+double GraphicsView::minimumValue() const
+{
+ return m_scene.empty() ? -1.0 : m_scene.minimumValue();
+}
+
+double GraphicsView::maximumValue() const
+{
+ return m_scene.empty() ? 1.0 : m_scene.maximumValue();
+}
+
+double GraphicsView::zoomX() const
+{
+ return m_zoomX;
+}
+
+double GraphicsView::zoomY() const
+{
+ return m_zoomY;
+}
+
+QRectF GraphicsView::canvasRect() const
+{
+ QRect r = viewport()->rect().adjusted(
+ m_style.valueAxisWidth + m_style.canvasMargin,
+ m_style.timeAxisHeight + m_style.canvasMargin,
+ -m_style.canvasMargin,
+ -m_style.canvasMargin);
+
+ return mapToScene(r).boundingRect();
+}
+
+QRectF GraphicsView::timeScaleRect() const
+{
+ QRect vp(viewport()->rect());
+ QPoint tl = vp.topLeft() + QPoint(m_style.valueAxisWidth, 0);
+ QPoint br = vp.topRight() + QPoint(0, m_style.timeAxisHeight);
+ return mapToScene(QRect(tl, br)).boundingRect();
+}
+
+QRectF GraphicsView::valueScaleRect() const
+{
+ QRect vp(viewport()->rect());
+ QPoint tl = vp.topLeft() + QPoint(0, m_style.timeAxisHeight);
+ QPoint br = vp.bottomLeft() + QPoint(m_style.valueAxisWidth, 0);
+ return mapToScene(QRect(tl, br)).boundingRect();
+}
+
+QRectF GraphicsView::defaultRasterRect() const
+{
+ QPointF topLeft(mapTimeToX(minimumTime()), mapValueToY(maximumValue()));
+ QPointF bottomRight(mapTimeToX(maximumTime()), mapValueToY(minimumValue()));
+ return QRectF(topLeft, bottomRight);
+}
+
+void GraphicsView::setStyle(const CurveEditorStyle &style)
+{
+ m_style = style;
+
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item))
+ curveItem->setStyle(style);
+ }
+
+ applyZoom(m_zoomX, m_zoomY);
+ viewport()->update();
+}
+
+void GraphicsView::setZoomX(double zoom, const QPoint &pivot)
+{
+ applyZoom(zoom, m_zoomY, pivot);
+ viewport()->update();
+}
+
+void GraphicsView::setZoomY(double zoom, const QPoint &pivot)
+{
+ applyZoom(m_zoomX, zoom, pivot);
+ viewport()->update();
+}
+
+void GraphicsView::setCurrentFrame(int frame)
+{
+ int clampedFrame = clamp(frame, m_model->minimumTime(), m_model->maximumTime());
+ m_playhead.moveToFrame(clampedFrame, this);
+ viewport()->update();
+}
+
+void GraphicsView::scrollContent(double x, double y)
+{
+ QScrollBar *hs = horizontalScrollBar();
+ QScrollBar *vs = verticalScrollBar();
+ hs->setValue(hs->value() + x);
+ vs->setValue(vs->value() + y);
+}
+
+void GraphicsView::reset(const std::vector<CurveItem *> &items)
+{
+ m_scene.clear();
+ for (auto *item : items)
+ m_scene.addCurveItem(item);
+
+ applyZoom(m_zoomX, m_zoomY);
+ viewport()->update();
+}
+
+void GraphicsView::resizeEvent(QResizeEvent *event)
+{
+ QGraphicsView::resizeEvent(event);
+ applyZoom(m_zoomX, m_zoomY);
+}
+
+void GraphicsView::keyPressEvent(QKeyEvent *event)
+{
+ Shortcut shortcut(event->modifiers(), static_cast<Qt::Key>(event->key()));
+ if (shortcut == m_style.shortcuts.frameAll)
+ applyZoom(0.0, 0.0);
+}
+
+void GraphicsView::mousePressEvent(QMouseEvent *event)
+{
+ if (m_playhead.mousePress(globalToScene(event->globalPos())))
+ return;
+
+ Shortcut shortcut(event);
+ if (shortcut == Shortcut(Qt::LeftButton)) {
+ QPointF pos = mapToScene(event->pos());
+ if (timeScaleRect().contains(pos)) {
+ setCurrentFrame(std::round(mapXtoTime(pos.x())));
+ event->accept();
+ return;
+ }
+ }
+
+ QGraphicsView::mousePressEvent(event);
+
+ m_selector.mousePress(event, this);
+}
+
+void GraphicsView::mouseMoveEvent(QMouseEvent *event)
+{
+ if (m_playhead.mouseMove(globalToScene(event->globalPos()), this))
+ return;
+
+ QGraphicsView::mouseMoveEvent(event);
+
+ m_selector.mouseMove(event, this, m_playhead);
+}
+
+void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
+{
+ QGraphicsView::mouseReleaseEvent(event);
+
+ m_playhead.mouseRelease(this);
+ m_selector.mouseRelease(event, this);
+ this->viewport()->update();
+}
+
+void GraphicsView::wheelEvent(QWheelEvent *event)
+{
+ if (event->modifiers().testFlag(Qt::AltModifier))
+ return;
+
+ QGraphicsView::wheelEvent(event);
+}
+
+void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
+{
+ if (event->modifiers() != Qt::NoModifier)
+ return;
+
+ auto openStyleEditor = [this]() { m_dialog.show(); };
+
+ QMenu menu;
+ QAction *openEditorAction = menu.addAction(tr("Open Style Editor"));
+ connect(openEditorAction, &QAction::triggered, openStyleEditor);
+
+ menu.exec(event->globalPos());
+}
+
+void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
+{
+ QRectF abscissa = timeScaleRect();
+ if (abscissa.isValid())
+ drawTimeScale(painter, abscissa);
+
+ auto ordinate = valueScaleRect();
+ if (ordinate.isValid())
+ drawValueScale(painter, ordinate);
+
+ m_playhead.paint(painter, this);
+
+ painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()),
+ m_style.backgroundAlternateBrush);
+
+ m_selector.paint(painter);
+}
+
+void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
+{
+ painter->fillRect(rect, m_style.backgroundBrush);
+ painter->fillRect(scene()->sceneRect(), m_style.backgroundAlternateBrush);
+
+ drawGrid(painter, rect);
+ drawExtremaX(painter, rect);
+ drawExtremaY(painter, rect);
+}
+
+int GraphicsView::mapTimeToX(double time) const
+{
+ return std::round(time * scaleX(m_transform));
+}
+
+int GraphicsView::mapValueToY(double y) const
+{
+ return std::round(y * scaleY(m_transform));
+}
+
+double GraphicsView::mapXtoTime(int x) const
+{
+ return static_cast<double>(x) / scaleX(m_transform);
+}
+
+double GraphicsView::mapYtoValue(int y) const
+{
+ return static_cast<double>(y) / scaleY(m_transform);
+}
+
+QPointF GraphicsView::globalToScene(const QPoint &point) const
+{
+ return mapToScene(viewport()->mapFromGlobal(point));
+}
+
+QPointF GraphicsView::globalToRaster(const QPoint &point) const
+{
+ QPointF scene = globalToScene(point);
+ return QPointF(mapXtoTime(scene.x()), mapYtoValue(scene.y()));
+}
+
+void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
+{
+ QPointF pivotRaster(globalToRaster(pivot));
+
+ m_zoomX = clamp(x, 0.0, 1.0);
+ m_zoomY = clamp(y, 0.0, 1.0);
+
+ double minTime = minimumTime();
+ double maxTime = maximumTime();
+
+ double minValue = minimumValue();
+ double maxValue = maximumValue();
+
+ QRectF canvas = canvasRect();
+
+ double xZoomedOut = canvas.width() / (maxTime - minTime);
+ double xZoomedIn = m_style.zoomInWidth;
+ double scaleX = lerp(clamp(m_zoomX, 0.0, 1.0), xZoomedOut, xZoomedIn);
+
+ double yZoomedOut = canvas.height() / (maxValue - minValue);
+ double yZoomedIn = m_style.zoomInHeight;
+ double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn);
+
+ m_transform = QTransform::fromScale(scaleX, scaleY);
+
+ m_scene.setComponentTransform(m_transform);
+
+ QRectF sr = m_scene.sceneRect().adjusted(
+ -m_style.valueAxisWidth - m_style.canvasMargin,
+ -m_style.timeAxisHeight - m_style.canvasMargin,
+ m_style.canvasMargin,
+ m_style.canvasMargin);
+
+ setSceneRect(sr);
+
+ m_playhead.resize(this);
+
+ if (!pivot.isNull()) {
+ QPointF deltaTransformed = pivotRaster - globalToRaster(pivot);
+ scrollContent(mapTimeToX(deltaTransformed.x()), mapValueToY(deltaTransformed.y()));
+ }
+}
+
+void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect)
+{
+ QRectF gridRect = rect.adjusted(
+ m_style.valueAxisWidth + m_style.canvasMargin,
+ m_style.timeAxisHeight + m_style.canvasMargin,
+ -m_style.canvasMargin,
+ -m_style.canvasMargin);
+
+ if (!gridRect.isValid())
+ return;
+
+ auto drawVerticalLine = [painter, gridRect](double position) {
+ painter->drawLine(position, gridRect.top(), position, gridRect.bottom());
+ };
+
+ painter->save();
+ painter->setPen(m_style.gridColor);
+
+ double timeIncrement = timeLabelInterval(painter, m_model->maximumTime());
+ for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
+ drawVerticalLine(mapTimeToX(i));
+
+ painter->restore();
+}
+
+void GraphicsView::drawExtremaX(QPainter *painter, const QRectF &rect)
+{
+ auto drawVerticalLine = [rect, painter](double position) {
+ painter->drawLine(position, rect.top(), position, rect.bottom());
+ };
+
+ painter->save();
+ painter->setPen(Qt::red);
+ drawVerticalLine(mapTimeToX(m_model->minimumTime()));
+ drawVerticalLine(mapTimeToX(m_model->maximumTime()));
+ painter->restore();
+}
+
+void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect)
+{
+ if (m_scene.empty())
+ return;
+
+ auto drawHorizontalLine = [rect, painter](double position) {
+ painter->drawLine(rect.left(), position, rect.right(), position);
+ };
+
+ painter->save();
+ painter->setPen(Qt::blue);
+ drawHorizontalLine(mapValueToY(m_scene.minimumValue()));
+ drawHorizontalLine(mapValueToY(m_scene.maximumValue()));
+
+ drawHorizontalLine(mapValueToY(0.0));
+
+ painter->restore();
+}
+
+void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
+{
+ painter->save();
+ painter->setPen(m_style.fontColor);
+ painter->fillRect(rect, m_style.backgroundAlternateBrush);
+
+ QFontMetrics fm(painter->font());
+
+ auto paintLabeledTick = [this, painter, rect, fm](double time) {
+ QString timeText = QString("%1").arg(time);
+
+ int position = mapTimeToX(time);
+
+ QRect textRect = fm.boundingRect(timeText);
+ textRect.moveCenter(QPoint(position, rect.center().y()));
+
+ painter->drawText(textRect, Qt::AlignCenter, timeText);
+ painter->drawLine(position, rect.bottom() - 2, position, textRect.bottom() + 2);
+ };
+
+ double timeIncrement = timeLabelInterval(painter, maximumTime());
+ for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
+ paintLabeledTick(i);
+
+ painter->restore();
+}
+
+void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect)
+{
+ painter->save();
+ painter->setPen(m_style.fontColor);
+ painter->fillRect(rect, m_style.backgroundAlternateBrush);
+
+ QFontMetrics fm(painter->font());
+ auto paintLabeledTick = [this, painter, rect, fm](double value) {
+ QString valueText = QString("%1").arg(value);
+
+ int position = mapValueToY(value);
+
+ QRect textRect = fm.boundingRect(valueText);
+ textRect.moveCenter(QPoint(rect.center().x(), position));
+ painter->drawText(textRect, Qt::AlignCenter, valueText);
+ };
+
+ paintLabeledTick(minimumValue());
+ paintLabeledTick(maximumValue());
+ painter->restore();
+}
+
+double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime)
+{
+ QFontMetrics fm(painter->font());
+ int minTextSpacing = fm.width(QString("X%1X").arg(maxTime));
+
+ int deltaTime = 1;
+ int nextFactor = 5;
+
+ double tickDistance = mapTimeToX(deltaTime);
+
+ while (true) {
+ if (tickDistance == 0 && deltaTime > maxTime)
+ return maxTime;
+
+ if (tickDistance > minTextSpacing)
+ break;
+
+ deltaTime *= nextFactor;
+ tickDistance = mapTimeToX(deltaTime);
+
+ if (nextFactor == 5)
+ nextFactor = 2;
+ else
+ nextFactor = 5;
+ }
+
+ return deltaTime;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h
new file mode 100644
index 0000000000..1975e3696d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+#include "curveeditorstyledialog.h"
+#include "graphicsscene.h"
+#include "playhead.h"
+#include "selector.h"
+
+#include <QGraphicsView>
+
+namespace DesignTools {
+
+class CurveItem;
+class CurveEditorModel;
+class Playhead;
+
+class GraphicsView : public QGraphicsView
+{
+ Q_OBJECT
+
+ friend class Playhead;
+
+public:
+ GraphicsView(CurveEditorModel *model, QWidget *parent = nullptr);
+
+ CurveEditorModel *model() const;
+
+ CurveEditorStyle editorStyle() const;
+
+ bool hasActiveItem() const;
+
+ bool hasActiveHandle() const;
+
+ int mapTimeToX(double time) const;
+
+ int mapValueToY(double value) const;
+
+ double mapXtoTime(int x) const;
+
+ double mapYtoValue(int y) const;
+
+ QPointF globalToScene(const QPoint &point) const;
+
+ QPointF globalToRaster(const QPoint &point) const;
+
+ double minimumTime() const;
+
+ double maximumTime() const;
+
+ double minimumValue() const;
+
+ double maximumValue() const;
+
+ double zoomX() const;
+
+ double zoomY() const;
+
+ QRectF canvasRect() const;
+
+ QRectF timeScaleRect() const;
+
+ QRectF valueScaleRect() const;
+
+ QRectF defaultRasterRect() const;
+
+ void setStyle(const CurveEditorStyle &style);
+
+ void setZoomX(double zoom, const QPoint &pivot = QPoint());
+
+ void setZoomY(double zoom, const QPoint &pivot = QPoint());
+
+ void setCurrentFrame(int frame);
+
+ void scrollContent(double x, double y);
+
+ void reset(const std::vector<CurveItem *> &items);
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+ void keyPressEvent(QKeyEvent *event) override;
+
+ void mousePressEvent(QMouseEvent *event) override;
+
+ void mouseMoveEvent(QMouseEvent *event) override;
+
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+ void wheelEvent(QWheelEvent *event) override;
+
+ void contextMenuEvent(QContextMenuEvent *event) override;
+
+ void drawForeground(QPainter *painter, const QRectF &rect) override;
+
+ void drawBackground(QPainter *painter, const QRectF &rect) override;
+
+private:
+ void applyZoom(double x, double y, const QPoint &pivot = QPoint());
+
+ void drawGrid(QPainter *painter, const QRectF &rect);
+
+ void drawExtremaX(QPainter *painter, const QRectF &rect);
+
+ void drawExtremaY(QPainter *painter, const QRectF &rect);
+
+ void drawTimeScale(QPainter *painter, const QRectF &rect);
+
+ void drawValueScale(QPainter *painter, const QRectF &rect);
+
+ double timeLabelInterval(QPainter *painter, double maxTime);
+
+private:
+ double m_zoomX;
+
+ double m_zoomY;
+
+ QTransform m_transform;
+
+ GraphicsScene m_scene;
+
+ CurveEditorModel *m_model;
+
+ Playhead m_playhead;
+
+ Selector m_selector;
+
+ CurveEditorStyle m_style;
+
+ CurveEditorStyleDialog m_dialog;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp
new file mode 100644
index 0000000000..c54b26e279
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "handleitem.h"
+#include "utils.h"
+
+#include <QPainter>
+
+namespace DesignTools {
+
+struct HandleGeometry
+{
+ HandleGeometry(const QPointF &pos, const HandleItemStyleOption &style)
+ {
+ QPointF topLeft(-style.size / 2.0, -style.size / 2.0);
+ handle = QRectF(topLeft, -topLeft);
+ toKeyframe = QLineF(QPointF(0.0, 0.0), -pos);
+ angle = -toKeyframe.angle() + 45.0;
+ }
+
+ QRectF handle;
+
+ QLineF toKeyframe;
+
+ double angle;
+};
+
+HandleItem::HandleItem(QGraphicsItem *parent)
+ : SelectableItem(parent)
+ , m_style()
+{
+ setFlag(QGraphicsItem::ItemStacksBehindParent, true);
+}
+
+HandleItem::~HandleItem() {}
+
+int HandleItem::type() const
+{
+ return Type;
+}
+
+QRectF HandleItem::boundingRect() const
+{
+ HandleGeometry geom(pos(), m_style);
+
+ QTransform transform;
+ transform.rotate(geom.angle);
+
+ QRectF bounds = bbox(geom.handle, transform);
+ grow(bounds, -pos());
+ return bounds;
+}
+
+void HandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+
+ QColor handleColor(isSelected() ? m_style.selectionColor : m_style.color);
+
+ HandleGeometry geom(pos(), m_style);
+
+ QPen pen = painter->pen();
+ pen.setWidthF(m_style.lineWidth);
+ pen.setColor(handleColor);
+
+ painter->save();
+ painter->setPen(pen);
+
+ painter->drawLine(geom.toKeyframe);
+ painter->rotate(geom.angle);
+ painter->fillRect(geom.handle, handleColor);
+
+ painter->restore();
+}
+
+void HandleItem::setStyle(const CurveEditorStyle &style)
+{
+ m_style = style.handleStyle;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h
new file mode 100644
index 0000000000..4c9126c629
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+#include "selectableitem.h"
+
+namespace DesignTools {
+
+class HandleItem : public SelectableItem
+{
+ Q_OBJECT
+
+public:
+ HandleItem(QGraphicsItem *parent);
+
+ ~HandleItem() override;
+
+ enum { Type = ItemTypeHandle };
+
+ int type() const override;
+
+ QRectF boundingRect() const override;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ void setStyle(const CurveEditorStyle &style);
+
+private:
+ HandleItemStyleOption m_style;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp
new file mode 100644
index 0000000000..94cdbbcbb1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp
@@ -0,0 +1,251 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "keyframeitem.h"
+#include "handleitem.h"
+
+#include <QPainter>
+
+#include <cmath>
+
+namespace DesignTools {
+
+KeyframeItem::KeyframeItem(QGraphicsItem *parent)
+ : SelectableItem(parent)
+ , m_frame()
+{}
+
+KeyframeItem::KeyframeItem(const Keyframe &keyframe, QGraphicsItem *parent)
+ : SelectableItem(parent)
+ , m_transform()
+ , m_frame(keyframe)
+ , m_left(keyframe.hasLeftHandle() ? new HandleItem(this) : nullptr)
+ , m_right(keyframe.hasRightHandle() ? new HandleItem(this) : nullptr)
+{
+ auto updatePosition = [this]() { this->updatePosition(true); };
+ connect(this, &QGraphicsObject::xChanged, updatePosition);
+ connect(this, &QGraphicsObject::yChanged, updatePosition);
+
+ if (m_left) {
+ m_left->setPos(m_frame.leftHandle() - m_frame.position());
+ auto updateLeftHandle = [this]() { updateHandle(m_left); };
+ connect(m_left, &QGraphicsObject::xChanged, updateLeftHandle);
+ connect(m_left, &QGraphicsObject::yChanged, updateLeftHandle);
+ m_left->hide();
+ }
+
+ if (m_right) {
+ m_right->setPos(m_frame.rightHandle() - m_frame.position());
+ auto updateRightHandle = [this]() { updateHandle(m_right); };
+ connect(m_right, &QGraphicsObject::xChanged, updateRightHandle);
+ connect(m_right, &QGraphicsObject::yChanged, updateRightHandle);
+ m_right->hide();
+ }
+
+ setPos(m_frame.position());
+}
+
+int KeyframeItem::type() const
+{
+ return Type;
+}
+
+QRectF KeyframeItem::boundingRect() const
+{
+ QPointF topLeft(-m_style.size / 2.0, -m_style.size / 2.0);
+ return QRectF(topLeft, -topLeft);
+}
+
+void KeyframeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+
+ QPen pen = painter->pen();
+ pen.setColor(Qt::black);
+
+ painter->save();
+ painter->setPen(pen);
+ painter->setBrush(selected() ? Qt::red : m_style.color);
+ painter->drawEllipse(boundingRect());
+
+ painter->restore();
+}
+
+KeyframeItem::~KeyframeItem() {}
+
+Keyframe KeyframeItem::keyframe() const
+{
+ return m_frame;
+}
+
+void KeyframeItem::setComponentTransform(const QTransform &transform)
+{
+ m_transform = transform;
+
+ if (m_left)
+ m_left->setPos(m_transform.map(m_frame.leftHandle() - m_frame.position()));
+
+ if (m_right)
+ m_right->setPos(m_transform.map(m_frame.rightHandle() - m_frame.position()));
+
+ setPos(m_transform.map(m_frame.position()));
+}
+
+void KeyframeItem::setStyle(const CurveEditorStyle &style)
+{
+ m_style = style.keyframeStyle;
+
+ if (m_left)
+ m_left->setStyle(style);
+
+ if (m_right)
+ m_right->setStyle(style);
+}
+
+void KeyframeItem::updatePosition(bool update)
+{
+ bool ok = false;
+ QPointF position = m_transform.inverted(&ok).map(pos());
+
+ if (!ok)
+ return;
+
+ QPointF oldPosition = m_frame.position();
+ m_frame.setPosition(position);
+
+ if (m_left)
+ updateHandle(m_left, false);
+
+ if (m_right)
+ updateHandle(m_right, false);
+
+ if (update) {
+ emit redrawCurve();
+ emit keyframeMoved(this, position - oldPosition);
+ }
+}
+
+void KeyframeItem::moveKeyframe(const QPointF &direction)
+{
+ this->blockSignals(true);
+ setPos(m_transform.map(m_frame.position() + direction));
+ updatePosition(false);
+ this->blockSignals(false);
+ emit redrawCurve();
+}
+
+void KeyframeItem::moveHandle(HandleSlot handle, double deltaAngle, double deltaLength)
+{
+ auto move = [this, deltaAngle, deltaLength](HandleItem *item) {
+ QLineF current(QPointF(0.0, 0.0), item->pos());
+ current.setAngle(current.angle() + deltaAngle);
+ current.setLength(current.length() + deltaLength);
+ item->setPos(current.p2());
+ updateHandle(item, false);
+ };
+
+ this->blockSignals(true);
+
+ if (handle == HandleSlot::Left)
+ move(m_left);
+ else if (handle == HandleSlot::Right)
+ move(m_right);
+
+ this->blockSignals(false);
+
+ emit redrawCurve();
+}
+
+void KeyframeItem::updateHandle(HandleItem *handle, bool emitChanged)
+{
+ bool ok = false;
+
+ QPointF handlePosition = m_transform.inverted(&ok).map(handle->pos());
+
+ if (!ok)
+ return;
+
+ QPointF oldPosition;
+ QPointF newPosition;
+ HandleSlot slot = HandleSlot::Undefined;
+ if (handle == m_left) {
+ slot = HandleSlot::Left;
+ oldPosition = m_frame.leftHandle();
+ m_frame.setLeftHandle(m_frame.position() + handlePosition);
+ newPosition = m_frame.leftHandle();
+ } else {
+ slot = HandleSlot::Right;
+ oldPosition = m_frame.rightHandle();
+ m_frame.setRightHandle(m_frame.position() + handlePosition);
+ newPosition = m_frame.rightHandle();
+ }
+
+ if (emitChanged) {
+ QLineF oldLine(m_frame.position(), oldPosition);
+ QLineF newLine(m_frame.position(), newPosition);
+
+ QLineF mappedOld = m_transform.map(oldLine);
+ QLineF mappedNew = m_transform.map(newLine);
+
+ auto angle = mappedOld.angleTo(mappedNew);
+ auto deltaLength = mappedNew.length() - mappedOld.length();
+
+ emit redrawCurve();
+ emit handleMoved(this, slot, angle, deltaLength);
+ }
+}
+
+QVariant KeyframeItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
+{
+ if (change == ItemPositionChange) {
+ bool ok;
+ QPointF position = m_transform.inverted(&ok).map(value.toPointF());
+ if (ok) {
+ position.setX(std::round(position.x()));
+ return QVariant(m_transform.map(position));
+ }
+ }
+
+ return QGraphicsItem::itemChange(change, value);
+}
+
+void KeyframeItem::selectionCallback()
+{
+ auto setHandleVisibility = [](HandleItem *handle, bool visible) {
+ if (handle)
+ handle->setVisible(visible);
+ };
+
+ if (selected()) {
+ setHandleVisibility(m_left, true);
+ setHandleVisibility(m_right, true);
+ } else {
+ setHandleVisibility(m_left, false);
+ setHandleVisibility(m_right, false);
+ }
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h
new file mode 100644
index 0000000000..ae65be1e9b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+#include "keyframe.h"
+#include "selectableitem.h"
+
+#include <QGraphicsObject>
+
+namespace DesignTools {
+
+class HandleItem;
+
+enum class HandleSlot { Undefined, Left, Right };
+
+class KeyframeItem : public SelectableItem
+{
+ Q_OBJECT
+
+signals:
+ void redrawCurve();
+
+ void keyframeMoved(KeyframeItem *item, const QPointF &direction);
+
+ void handleMoved(KeyframeItem *frame, HandleSlot handle, double angle, double deltaLength);
+
+public:
+ KeyframeItem(QGraphicsItem *parent = nullptr);
+
+ KeyframeItem(const Keyframe &keyframe, QGraphicsItem *parent = nullptr);
+
+ ~KeyframeItem() override;
+
+ enum { Type = ItemTypeKeyframe };
+
+ int type() const override;
+
+ QRectF boundingRect() const override;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ Keyframe keyframe() const;
+
+ void setComponentTransform(const QTransform &transform);
+
+ void setStyle(const CurveEditorStyle &style);
+
+ void moveKeyframe(const QPointF &direction);
+
+ void moveHandle(HandleSlot handle, double deltaAngle, double deltaLength);
+
+protected:
+ QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
+
+ void selectionCallback() override;
+
+private:
+ void updatePosition(bool emit = true);
+
+ void updateHandle(HandleItem *handle, bool emit = true);
+
+private:
+ QTransform m_transform;
+
+ KeyframeItemStyleOption m_style;
+
+ Keyframe m_frame;
+
+ HandleItem *m_left;
+
+ HandleItem *m_right;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/playhead.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/playhead.cpp
new file mode 100644
index 0000000000..e2e6d21274
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/playhead.cpp
@@ -0,0 +1,187 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "playhead.h"
+#include "curveeditormodel.h"
+#include "graphicsview.h"
+
+#include <QApplication>
+#include <QGraphicsScene>
+#include <QPainter>
+
+#include <cmath>
+
+namespace DesignTools {
+
+constexpr double g_playheadMargin = 5.0;
+
+Playhead::Playhead(GraphicsView *view)
+ : m_frame(0)
+ , m_moving(false)
+ , m_rect()
+ , m_timer()
+{
+ m_timer.setSingleShot(true);
+ m_timer.setInterval(30);
+ QObject::connect(&m_timer, &QTimer::timeout, view, [this, view]() {
+ if (QApplication::mouseButtons() == Qt::LeftButton)
+ mouseMoveOutOfBounds(view);
+ });
+}
+
+int Playhead::currentFrame() const
+{
+ return m_frame;
+}
+
+void Playhead::moveToFrame(int frame, GraphicsView *view)
+{
+ m_frame = frame;
+ m_rect.moveCenter(QPointF(view->mapTimeToX(m_frame), m_rect.center().y()));
+}
+
+void Playhead::resize(GraphicsView *view)
+{
+ QRectF viewRect = view->mapToScene(view->viewport()->rect()).boundingRect();
+
+ CurveEditorStyle style = view->editorStyle();
+
+ QPointF tlr(style.valueAxisWidth, style.timeAxisHeight - style.playhead.width);
+ QPointF brr(style.valueAxisWidth + style.playhead.width, -g_playheadMargin);
+
+ QPointF tl = viewRect.topLeft() + tlr;
+ QPointF br = viewRect.bottomLeft() + brr;
+
+ m_rect = QRectF(tl, br);
+
+ moveToFrame(m_frame, view);
+}
+
+bool Playhead::mousePress(const QPointF &pos)
+{
+ QRectF hitRect = m_rect;
+ hitRect.setBottom(hitRect.top() + hitRect.width());
+
+ m_moving = hitRect.contains(pos);
+
+ return m_moving;
+}
+
+bool Playhead::mouseMove(const QPointF &pos, GraphicsView *view)
+{
+ if (m_moving) {
+ CurveEditorStyle style = view->editorStyle();
+
+ QRectF canvas = view->canvasRect().adjusted(0.0, -style.timeAxisHeight, 0.0, 0.0);
+
+ if (canvas.contains(pos))
+ view->setCurrentFrame(std::round(view->mapXtoTime(pos.x())));
+ else if (!m_timer.isActive())
+ m_timer.start();
+ }
+
+ return m_moving;
+}
+
+void Playhead::mouseMoveOutOfBounds(GraphicsView *view)
+{
+ if (QApplication::mouseButtons() != Qt::LeftButton)
+ return;
+
+ CurveEditorStyle style = view->editorStyle();
+ QRectF canvas = view->canvasRect();
+ QPointF pos = view->globalToScene(QCursor::pos());
+
+ if (pos.x() > canvas.right()) {
+ double speed = (pos.x() - canvas.right());
+ double nextCenter = m_rect.center().x() + speed;
+ double frame = std::round(view->mapXtoTime(nextCenter));
+ view->setCurrentFrame(frame);
+
+ double framePosition = view->mapTimeToX(frame);
+ double rightSideOut = framePosition + style.playhead.width / 2.0 + style.canvasMargin;
+ double overshoot = rightSideOut - canvas.right();
+ view->scrollContent(overshoot, 0.0);
+
+ } else if (pos.x() < canvas.left()) {
+ double speed = (canvas.left() - pos.x());
+ double nextCenter = m_rect.center().x() - speed;
+ double frame = std::round(view->mapXtoTime(nextCenter));
+ view->setCurrentFrame(frame);
+
+ double framePosition = view->mapTimeToX(frame);
+ double leftSideOut = framePosition - style.playhead.width / 2.0 - style.canvasMargin;
+ double undershoot = canvas.left() - leftSideOut;
+ view->scrollContent(-undershoot, 0.0);
+ }
+
+ m_timer.start();
+}
+
+void Playhead::mouseRelease(GraphicsView *view)
+{
+ if (m_moving)
+ emit view->model()->currentFrameChanged(m_frame);
+
+ m_moving = false;
+}
+
+void Playhead::paint(QPainter *painter, GraphicsView *view) const
+{
+ CurveEditorStyle style = view->editorStyle();
+
+ painter->save();
+ painter->setPen(style.playhead.color);
+ painter->setRenderHint(QPainter::Antialiasing, true);
+
+ QRectF rect = m_rect;
+ rect.setBottom(m_rect.top() + m_rect.width());
+
+ QPointF top(rect.center().x(), rect.top());
+ QPointF right(rect.right(), rect.top());
+ QPointF bottom(rect.center().x(), rect.bottom());
+ QPointF left(rect.left(), rect.top());
+
+ QLineF rightToBottom(right, bottom);
+ rightToBottom.setLength(style.playhead.radius);
+
+ QLineF leftToBottom(left, bottom);
+ leftToBottom.setLength(style.playhead.radius);
+
+ QPainterPath path(top);
+ path.lineTo(right - QPointF(style.playhead.radius, 0.));
+ path.quadTo(right, rightToBottom.p2());
+ path.lineTo(bottom);
+ path.lineTo(leftToBottom.p2());
+ path.quadTo(left, left + QPointF(style.playhead.radius, 0.));
+ path.closeSubpath();
+
+ painter->fillPath(path, style.playhead.color);
+ painter->drawLine(top + QPointF(0., 5.), QPointF(m_rect.center().x(), m_rect.bottom()));
+
+ painter->restore();
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/playhead.h b/src/plugins/qmldesigner/components/curveeditor/detail/playhead.h
new file mode 100644
index 0000000000..28d2a21a5c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/playhead.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QRectF>
+#include <QTimer>
+
+QT_BEGIN_NAMESPACE
+class QPainter;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+class GraphicsView;
+
+class Playhead
+{
+public:
+ Playhead(GraphicsView *view);
+
+ int currentFrame() const;
+
+ void paint(QPainter *painter, GraphicsView *view) const;
+
+ void moveToFrame(int frame, GraphicsView *view);
+
+ void resize(GraphicsView *view);
+
+ bool mousePress(const QPointF &pos);
+
+ bool mouseMove(const QPointF &pos, GraphicsView *view);
+
+ void mouseRelease(GraphicsView *view);
+
+private:
+ void mouseMoveOutOfBounds(GraphicsView *view);
+
+ int m_frame;
+
+ bool m_moving;
+
+ QRectF m_rect;
+
+ QTimer m_timer;
+
+ GraphicsView *m_view;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp
new file mode 100644
index 0000000000..fb61682090
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "selectableitem.h"
+#include "keyframeitem.h"
+
+#include <QDebug>
+
+namespace DesignTools {
+
+SelectableItem::SelectableItem(QGraphicsItem *parent)
+ : QGraphicsObject(parent)
+ , m_active(false)
+ , m_selected(false)
+ , m_preSelected(SelectionMode::Undefined)
+{
+ setFlag(QGraphicsItem::ItemIsSelectable, false);
+
+ setFlag(QGraphicsItem::ItemIsMovable, true);
+ setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
+ setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
+}
+
+SelectableItem::~SelectableItem() {}
+
+bool SelectableItem::activated() const
+{
+ return m_active;
+}
+
+bool SelectableItem::selected() const
+{
+ switch (m_preSelected) {
+ case SelectionMode::Clear:
+ return false;
+ case SelectionMode::New:
+ return true;
+ case SelectionMode::Add:
+ return true;
+ case SelectionMode::Remove:
+ return false;
+ case SelectionMode::Toggle:
+ return !m_selected;
+ default:
+ return m_selected;
+ }
+
+ return false;
+}
+
+void SelectableItem::setActivated(bool active)
+{
+ m_active = active;
+}
+
+void SelectableItem::setPreselected(SelectionMode mode)
+{
+ m_preSelected = mode;
+ selectionCallback();
+}
+
+void SelectableItem::applyPreselection()
+{
+ m_selected = selected();
+ m_preSelected = SelectionMode::Undefined;
+}
+
+void SelectableItem::selectionCallback() {}
+
+void SelectableItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ m_active = true;
+ QGraphicsObject::mousePressEvent(event);
+}
+
+void SelectableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (type() == KeyframeItem::Type && !selected())
+ return;
+
+ QGraphicsObject::mouseMoveEvent(event);
+}
+
+void SelectableItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ m_active = false;
+ QGraphicsObject::mouseReleaseEvent(event);
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.h
new file mode 100644
index 0000000000..0a2a4898a1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QGraphicsObject>
+
+namespace DesignTools {
+
+enum ItemType
+{
+ ItemTypeKeyframe = QGraphicsItem::UserType + 1,
+ ItemTypeHandle = QGraphicsItem::UserType + 2,
+ ItemTypeCurve = QGraphicsItem::UserType + 3
+};
+
+enum class SelectionMode : unsigned int
+{
+ Undefined,
+ Clear,
+ New,
+ Add,
+ Remove,
+ Toggle
+};
+
+class SelectableItem : public QGraphicsObject
+{
+ Q_OBJECT
+
+public:
+ SelectableItem(QGraphicsItem *parent = nullptr);
+
+ ~SelectableItem() override;
+
+ bool activated() const;
+
+ bool selected() const;
+
+ void setActivated(bool active);
+
+ void setPreselected(SelectionMode mode);
+
+ void applyPreselection();
+
+protected:
+ virtual void selectionCallback();
+
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+
+private:
+ bool m_active;
+
+ bool m_selected;
+
+ SelectionMode m_preSelected;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp
new file mode 100644
index 0000000000..633c1ddd39
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp
@@ -0,0 +1,221 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "selector.h"
+#include "graphicsview.h"
+#include "keyframeitem.h"
+#include "playhead.h"
+
+#include <QApplication>
+
+#include <cmath>
+
+namespace DesignTools {
+
+Selector::Selector() {}
+
+void Selector::paint(QPainter *painter)
+{
+ QPen pen(Qt::white);
+
+ painter->save();
+ painter->setPen(pen);
+
+ if (!m_lasso.isEmpty())
+ painter->drawPath(m_lasso);
+
+ if (!m_rect.isNull())
+ painter->drawRect(m_rect);
+
+ painter->restore();
+}
+
+void Selector::mousePress(QMouseEvent *event, GraphicsView *view)
+{
+ m_shortcut = Shortcut(event);
+
+ if (view->hasActiveHandle())
+ return;
+
+ if (select(SelectionTool::Undefined, view->globalToScene(event->globalPos()), view))
+ applyPreSelection(view);
+
+ m_mouseInit = event->globalPos();
+ m_mouseCurr = event->globalPos();
+
+ QPointF click = view->globalToScene(m_mouseInit);
+
+ m_lasso = QPainterPath(click);
+ m_lasso.closeSubpath();
+
+ m_rect = QRectF(click, click);
+}
+
+void Selector::mouseMove(QMouseEvent *event, GraphicsView *view, Playhead &playhead)
+{
+ if (m_mouseInit.isNull())
+ return;
+
+ QPointF delta = event->globalPos() - m_mouseInit;
+ if (delta.manhattanLength() < QApplication::startDragDistance())
+ return;
+
+ if (m_shortcut == m_shortcuts.newSelection || m_shortcut == m_shortcuts.addToSelection
+ || m_shortcut == m_shortcuts.removeFromSelection
+ || m_shortcut == m_shortcuts.toggleSelection) {
+ if (view->hasActiveItem())
+ return;
+
+ select(m_tool, view->globalToScene(event->globalPos()), view);
+
+ event->accept();
+ view->viewport()->update();
+
+ } else if (m_shortcut == m_shortcuts.zoom) {
+ double bigger = std::abs(delta.x()) > std::abs(delta.y()) ? delta.x() : delta.y();
+ double factor = bigger / view->width();
+ view->setZoomX(view->zoomX() + factor, m_mouseInit);
+ m_mouseCurr = event->globalPos();
+ event->accept();
+
+ } else if (m_shortcut == m_shortcuts.pan) {
+ view->scrollContent(-delta.x(), -delta.y());
+ playhead.resize(view);
+ m_mouseCurr = event->globalPos();
+ }
+}
+
+void Selector::mouseRelease(QMouseEvent *event, GraphicsView *view)
+{
+ Q_UNUSED(event);
+
+ applyPreSelection(view);
+
+ m_shortcut = Shortcut();
+ m_mouseInit = QPoint();
+ m_mouseCurr = QPoint();
+ m_lasso = QPainterPath();
+ m_rect = QRectF();
+}
+
+bool Selector::select(const SelectionTool &tool, const QPointF &pos, GraphicsView *view)
+{
+ auto selectWidthTool = [this, tool](SelectionMode mode, const QPointF &pos, GraphicsView *view) {
+ switch (tool) {
+ case SelectionTool::Lasso:
+ return lassoSelection(mode, pos, view);
+ case SelectionTool::Rectangle:
+ return rectangleSelection(mode, pos, view);
+ default:
+ return pressSelection(mode, pos, view);
+ }
+ };
+
+ if (m_shortcut == m_shortcuts.newSelection) {
+ clearSelection(view);
+ return selectWidthTool(SelectionMode::New, pos, view);
+ } else if (m_shortcut == m_shortcuts.addToSelection) {
+ return selectWidthTool(SelectionMode::Add, pos, view);
+ } else if (m_shortcut == m_shortcuts.removeFromSelection) {
+ return selectWidthTool(SelectionMode::Remove, pos, view);
+ } else if (m_shortcut == m_shortcuts.toggleSelection) {
+ return selectWidthTool(SelectionMode::Toggle, pos, view);
+ }
+
+ return false;
+}
+
+bool Selector::pressSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view)
+{
+ bool out = false;
+ const auto itemList = view->items();
+ for (auto *item : itemList) {
+ if (auto *frame = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ QRectF itemRect = frame->mapRectToScene(frame->boundingRect());
+ if (itemRect.contains(pos)) {
+ frame->setPreselected(mode);
+ out = true;
+ }
+ }
+ }
+ return out;
+}
+
+bool Selector::rectangleSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view)
+{
+ bool out = false;
+ m_rect.setBottomRight(pos);
+ const auto itemList = view->items();
+ for (auto *item : itemList) {
+ if (auto *keyframeItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ if (m_rect.contains(keyframeItem->pos())) {
+ keyframeItem->setPreselected(mode);
+ out = true;
+ } else {
+ keyframeItem->setPreselected(SelectionMode::Undefined);
+ }
+ }
+ }
+ return out;
+}
+
+bool Selector::lassoSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view)
+{
+ bool out = false;
+ m_lasso.lineTo(pos);
+ const auto itemList = view->items();
+ for (auto *item : itemList) {
+ if (auto *keyframeItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ if (m_lasso.contains(keyframeItem->pos())) {
+ keyframeItem->setPreselected(mode);
+ out = true;
+ } else {
+ keyframeItem->setPreselected(SelectionMode::Undefined);
+ }
+ }
+ }
+ return out;
+}
+
+void Selector::clearSelection(GraphicsView *view)
+{
+ const auto itemList = view->items();
+ for (auto *item : itemList) {
+ if (auto *frameItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
+ frameItem->setPreselected(SelectionMode::Clear);
+ frameItem->applyPreselection();
+ }
+ }
+}
+
+void Selector::applyPreSelection(GraphicsView *view)
+{
+ const auto itemList = view->items();
+ for (auto *item : itemList) {
+ if (auto *keyframeItem = qgraphicsitem_cast<KeyframeItem *>(item))
+ keyframeItem->applyPreselection();
+ }
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selector.h b/src/plugins/qmldesigner/components/curveeditor/detail/selector.h
new file mode 100644
index 0000000000..e8f53d9d59
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/selector.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+#include "selectableitem.h"
+
+#include <QMouseEvent>
+#include <QPainterPath>
+#include <QPoint>
+#include <QRectF>
+
+namespace DesignTools {
+
+class GraphicsView;
+class Playhead;
+
+enum class SelectionTool { Undefined, Lasso, Rectangle };
+
+class Selector
+{
+public:
+ Selector();
+
+ void paint(QPainter *painter);
+
+ void mousePress(QMouseEvent *event, GraphicsView *view);
+
+ void mouseMove(QMouseEvent *event, GraphicsView *view, Playhead &playhead);
+
+ void mouseRelease(QMouseEvent *event, GraphicsView *view);
+
+private:
+ bool select(const SelectionTool &tool, const QPointF &pos, GraphicsView *view);
+
+ bool pressSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view);
+
+ bool rectangleSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view);
+
+ bool lassoSelection(SelectionMode mode, const QPointF &pos, GraphicsView *view);
+
+ void clearSelection(GraphicsView *view);
+
+ void applyPreSelection(GraphicsView *view);
+
+ Shortcuts m_shortcuts = Shortcuts();
+
+ Shortcut m_shortcut;
+
+ SelectionMode m_mode = SelectionMode::Undefined;
+
+ SelectionTool m_tool = SelectionTool::Rectangle;
+
+ QPoint m_mouseInit = QPoint();
+
+ QPoint m_mouseCurr = QPoint();
+
+ QPainterPath m_lasso = QPainterPath();
+
+ QRectF m_rect = QRectF();
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.cpp
new file mode 100644
index 0000000000..d6d04dbbae
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.cpp
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "shortcut.h"
+
+namespace DesignTools {
+
+Shortcut::Shortcut()
+ : m_key()
+ , m_buttons()
+ , m_modifiers()
+{}
+
+Shortcut::Shortcut(QMouseEvent *event)
+ : m_key()
+ , m_buttons(event->buttons())
+ , m_modifiers(event->modifiers())
+{}
+
+Shortcut::Shortcut(const Qt::KeyboardModifiers &mods, const Qt::Key &key)
+ : m_key(key)
+ , m_buttons()
+ , m_modifiers(mods)
+{}
+
+Shortcut::Shortcut(const Qt::MouseButtons &buttons, const Qt::KeyboardModifiers &mods)
+ : m_key()
+ , m_buttons(buttons)
+ , m_modifiers(mods)
+{}
+
+Shortcut::Shortcut(const Qt::MouseButtons &buttons,
+ const Qt::KeyboardModifiers &mods,
+ const Qt::Key &key)
+ : m_key(key)
+ , m_buttons(buttons)
+ , m_modifiers(mods)
+{}
+
+bool Shortcut::exactMatch(const Qt::Key &key) const
+{
+ return m_key == key;
+}
+
+bool Shortcut::exactMatch(const Qt::MouseButton &button) const
+{
+ return static_cast<int>(m_buttons) == static_cast<int>(button);
+}
+
+bool Shortcut::exactMatch(const Qt::KeyboardModifier &modifier) const
+{
+ return static_cast<int>(m_modifiers) == static_cast<int>(modifier);
+}
+
+bool Shortcut::operator==(const Shortcut &other) const
+{
+ return m_key == other.m_key && m_buttons == other.m_buttons && m_modifiers == other.m_modifiers;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.h b/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.h
new file mode 100644
index 0000000000..a9e075bd8b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/shortcut.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QMouseEvent>
+
+namespace DesignTools {
+
+class Shortcut
+{
+public:
+ Shortcut();
+
+ Shortcut(QMouseEvent *event);
+
+ Shortcut(const Qt::KeyboardModifiers &mods, const Qt::Key &key);
+
+ Shortcut(const Qt::MouseButtons &buttons, const Qt::KeyboardModifiers &mods = Qt::NoModifier);
+
+ Shortcut(const Qt::MouseButtons &buttons, const Qt::KeyboardModifiers &mods, const Qt::Key &key);
+
+ bool exactMatch(const Qt::Key &key) const;
+
+ bool exactMatch(const Qt::MouseButton &button) const;
+
+ bool exactMatch(const Qt::KeyboardModifier &modifier) const;
+
+ bool operator==(const Shortcut &other) const;
+
+private:
+ Qt::Key m_key;
+
+ Qt::MouseButtons m_buttons;
+
+ Qt::KeyboardModifiers m_modifiers;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.cpp
new file mode 100644
index 0000000000..21633ddce1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "treeitemdelegate.h"
+#include "treeitem.h"
+
+#include <QEvent>
+#include <QMouseEvent>
+#include <QPainter>
+
+namespace DesignTools {
+
+TreeItemDelegate::TreeItemDelegate(const CurveEditorStyle &style, QObject *parent)
+ : QStyledItemDelegate(parent)
+ , m_style(style)
+{}
+
+QSize TreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ return QStyledItemDelegate::sizeHint(option, index);
+}
+
+void TreeItemDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ if (index.column() == 1 || index.column() == 2) {
+
+ int height = option.rect.size().height();
+ QRect iconRect(QPoint(0, 0), QSize(height, height));
+ iconRect.moveCenter(option.rect.center());
+
+ auto *treeItem = static_cast<TreeItem *>(index.internalPointer());
+ if (option.state & QStyle::State_MouseOver && iconRect.contains(m_mousePos)) {
+
+ painter->fillRect(option.rect, option.backgroundBrush);
+
+ if (index.column() == 1) {
+
+ if (treeItem->locked()) {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.unlockedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+
+ } else {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.lockedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+ }
+
+ } else if (index.column() == 2) {
+
+ if (treeItem->pinned()) {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.unpinnedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+
+ } else {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.pinnedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+
+ }
+ }
+
+ } else {
+
+ if (treeItem->locked() && index.column() == 1) {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.lockedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+
+ } else if (treeItem->pinned() && index.column() == 2) {
+
+ QPixmap pixmap = pixmapFromIcon(
+ m_style.treeItemStyle.pinnedIcon,
+ iconRect.size(),
+ m_style.fontColor);
+
+ painter->drawPixmap(iconRect, pixmap);
+
+ }
+ }
+ } else {
+ QStyledItemDelegate::paint(painter, option, index);
+ }
+}
+
+void TreeItemDelegate::setStyle(const CurveEditorStyle &style)
+{
+ m_style = style;
+}
+
+bool TreeItemDelegate::editorEvent(QEvent *event,
+ QAbstractItemModel *model,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index)
+{
+ if (event->type() == QEvent::MouseMove)
+ m_mousePos = static_cast<QMouseEvent *>(event)->pos();
+
+ return QStyledItemDelegate::editorEvent(event, model, option, index);
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.h b/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.h
new file mode 100644
index 0000000000..6479f48942
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeitemdelegate.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "curveeditorstyle.h"
+
+#include <QStyledItemDelegate>
+
+namespace DesignTools {
+
+class TreeItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ TreeItemDelegate(const CurveEditorStyle &style, QObject *parent = nullptr);
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+
+ void paint(
+ QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+ void setStyle(const CurveEditorStyle &style);
+
+protected:
+ bool editorEvent(
+ QEvent *event,
+ QAbstractItemModel *model,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) override;
+
+private:
+ CurveEditorStyle m_style;
+
+ QPoint m_mousePos;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp
new file mode 100644
index 0000000000..1ea037091d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "treemodel.h"
+#include "treeitem.h"
+#include "detail/graphicsview.h"
+
+#include <QIcon>
+
+namespace DesignTools {
+
+TreeModel::TreeModel(QObject *parent)
+ : QAbstractItemModel(parent)
+ , m_view(nullptr)
+ , m_root(new TreeItem("Root"))
+{}
+
+TreeModel::~TreeModel()
+{
+ if (m_root) {
+ delete m_root;
+ m_root = nullptr;
+ }
+
+ m_view = nullptr;
+}
+
+QVariant TreeModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
+
+ if (role == Qt::DecorationRole && index.column() == 0)
+ return item->icon();
+
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ return item->data(index.column());
+}
+
+QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation == Qt::Horizontal)
+ return m_root->headerData(section);
+
+ return QVariant();
+}
+
+QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ TreeItem *parentItem = m_root;
+
+ if (parent.isValid())
+ parentItem = static_cast<TreeItem *>(parent.internalPointer());
+
+ if (TreeItem *childItem = parentItem->child(row))
+ return createIndex(row, column, childItem);
+
+ return QModelIndex();
+}
+
+QModelIndex TreeModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer());
+
+ if (TreeItem *parentItem = childItem->parent()) {
+ if (parentItem == m_root)
+ return QModelIndex();
+
+ return createIndex(parentItem->row(), 0, parentItem);
+ }
+ return QModelIndex();
+}
+
+int TreeModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.column() > 0)
+ return 0;
+
+ TreeItem *parentItem = m_root;
+
+ if (parent.isValid())
+ parentItem = static_cast<TreeItem *>(parent.internalPointer());
+
+ return parentItem->rowCount();
+}
+
+int TreeModel::columnCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return m_root->columnCount();
+}
+
+void TreeModel::setGraphicsView(GraphicsView *view)
+{
+ m_view = view;
+}
+
+GraphicsView *TreeModel::graphicsView() const
+{
+ return m_view;
+}
+
+void TreeModel::initialize()
+{
+ if (m_root)
+ delete m_root;
+
+ m_root = new TreeItem("Root");
+}
+
+TreeItem *TreeModel::root()
+{
+ return m_root;
+}
+
+TreeItem *TreeModel::find(unsigned int id)
+{
+ return m_root->find(id);
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.h b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.h
new file mode 100644
index 0000000000..209b2ee506
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QAbstractItemModel>
+
+#include <vector>
+
+namespace DesignTools {
+
+class GraphicsView;
+class TreeItem;
+
+class TreeModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ TreeModel(QObject *parent = nullptr);
+
+ ~TreeModel() override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+
+ QModelIndex parent(const QModelIndex &index) const override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ void setGraphicsView(GraphicsView *view);
+
+protected:
+ GraphicsView *graphicsView() const;
+
+ void initialize();
+
+ TreeItem *root();
+
+ TreeItem *find(unsigned int id);
+
+private:
+ GraphicsView *m_view;
+
+ TreeItem *m_root;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp
new file mode 100644
index 0000000000..3b9a4ef7c5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "treeview.h"
+#include "curveeditormodel.h"
+#include "curveitem.h"
+#include "treeitem.h"
+#include "treeitemdelegate.h"
+
+#include <QHeaderView>
+#include <QMouseEvent>
+
+namespace DesignTools {
+
+TreeView::TreeView(CurveEditorModel *model, QWidget *parent)
+ : QTreeView(parent)
+{
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+ setUniformRowHeights(true);
+ setRootIsDecorated(false);
+ setMouseTracking(true);
+ setHeaderHidden(true);
+
+ model->setParent(this);
+ setModel(model);
+
+ auto expandItems = [this]() { expandAll(); };
+ connect(model, &QAbstractItemModel::modelReset, expandItems);
+
+ auto *delegate = new TreeItemDelegate(model->style(), this);
+ setItemDelegate(delegate);
+
+ setSelectionBehavior(QAbstractItemView::SelectRows);
+ setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+ connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &TreeView::changeSelection);
+ setStyle(model->style());
+
+ header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ header()->setSectionResizeMode(1, QHeaderView::Fixed);
+ header()->setSectionResizeMode(2, QHeaderView::Fixed);
+
+ header()->setStretchLastSection(false);
+ header()->resizeSection(1, 20);
+ header()->resizeSection(2, 20);
+}
+
+void TreeView::setStyle(const CurveEditorStyle &style)
+{
+ QPalette pal = palette();
+ pal.setBrush(QPalette::Base, style.backgroundBrush);
+ pal.setBrush(QPalette::Button, style.backgroundAlternateBrush);
+ pal.setBrush(QPalette::Text, style.fontColor);
+
+ // Tmp to see what happens on windows/macOS.
+ pal.setBrush(backgroundRole(), Qt::white);
+ pal.setBrush(foregroundRole(), Qt::white);
+
+ setPalette(pal);
+
+ if (auto *delegate = qobject_cast<TreeItemDelegate *>(itemDelegate()))
+ delegate->setStyle(style);
+}
+
+void TreeView::changeCurve(unsigned int id, const AnimationCurve &curve)
+{
+ if (auto *curveModel = qobject_cast<CurveEditorModel *>(model()))
+ curveModel->setCurve(id, curve);
+}
+
+void TreeView::changeSelection(const QItemSelection &selected, const QItemSelection &deselected)
+{
+ Q_UNUSED(selected);
+ Q_UNUSED(deselected);
+
+ std::vector<CurveItem *> curves;
+ for (auto index : selectedIndexes()) {
+ if (index.isValid() && index.column() == 0) {
+ auto *treeItem = static_cast<TreeItem *>(index.internalPointer());
+ if (auto *propertyItem = treeItem->asPropertyItem())
+ curves.push_back(new CurveItem(treeItem->id(), propertyItem->curve()));
+ }
+ }
+
+ emit curvesSelected(curves);
+}
+
+QSize TreeView::sizeHint() const
+{
+ return QSize(170, 300);
+}
+
+void TreeView::mousePressEvent(QMouseEvent *event)
+{
+ QModelIndex index = indexAt(event->pos());
+ if (index.isValid()) {
+ auto *treeItem = static_cast<TreeItem *>(index.internalPointer());
+ if (index.column() == 1)
+ treeItem->setLocked(!treeItem->locked());
+ else if (index.column() == 2)
+ treeItem->setPinned(!treeItem->pinned());
+ }
+ QTreeView::mousePressEvent(event);
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h
new file mode 100644
index 0000000000..9d3af647ad
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QTreeView>
+
+namespace DesignTools {
+
+class AnimationCurve;
+class CurveEditorModel;
+class CurveItem;
+
+struct CurveEditorStyle;
+
+class TreeView : public QTreeView
+{
+ Q_OBJECT
+
+signals:
+ void curvesSelected(const std::vector<CurveItem *> &curves);
+
+public:
+ TreeView(CurveEditorModel *model, QWidget *parent = nullptr);
+
+ void changeCurve(unsigned int id, const AnimationCurve &curve);
+
+ void setStyle(const CurveEditorStyle &style);
+
+protected:
+ QSize sizeHint() const override;
+
+ void mousePressEvent(QMouseEvent *event) override;
+
+private:
+ void changeSelection(const QItemSelection &selected, const QItemSelection &deselected);
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp
new file mode 100644
index 0000000000..4933bcbe88
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp
@@ -0,0 +1,107 @@
+
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include <QPalette>
+#include <QPointF>
+#include <QRectF>
+#include <QTransform>
+
+namespace DesignTools {
+
+double clamp(double val, double lo, double hi)
+{
+ return val < lo ? lo : (val > hi ? hi : val);
+}
+
+double lerp(double blend, double a, double b)
+{
+ return (1.0 - blend) * a + blend * b;
+}
+
+double scaleX(const QTransform &transform)
+{
+ return transform.m11();
+}
+
+double scaleY(const QTransform &transform)
+{
+ return transform.m22();
+}
+
+void grow(QRectF &rect, const QPointF &point)
+{
+ if (rect.left() > point.x())
+ rect.setLeft(point.x());
+
+ if (rect.right() < point.x())
+ rect.setRight(point.x());
+
+ if (rect.top() > point.y())
+ rect.setTop(point.y());
+
+ if (rect.bottom() < point.y())
+ rect.setBottom(point.y());
+}
+
+QRectF bbox(const QRectF &rect, const QTransform &transform)
+{
+ QRectF out = rect;
+ grow(out, transform.map(rect.topLeft()));
+ grow(out, transform.map(rect.topRight()));
+ grow(out, transform.map(rect.bottomLeft()));
+ grow(out, transform.map(rect.bottomRight()));
+ return out;
+}
+
+QPalette singleColorPalette(const QColor &color)
+{
+ QPalette palette;
+ palette.setColor(QPalette::Window, color);
+ palette.setColor(QPalette::Background, color);
+ palette.setColor(QPalette::WindowText, color);
+ palette.setColor(QPalette::Foreground, color);
+ palette.setColor(QPalette::Base, color);
+ palette.setColor(QPalette::AlternateBase, color);
+ palette.setColor(QPalette::ToolTipBase, color);
+ palette.setColor(QPalette::ToolTipText, color);
+ palette.setColor(QPalette::Text, color);
+
+ palette.setColor(QPalette::Button, color);
+ palette.setColor(QPalette::ButtonText, color);
+ palette.setColor(QPalette::BrightText, color);
+ palette.setColor(QPalette::Light, color);
+ palette.setColor(QPalette::Midlight, color);
+ palette.setColor(QPalette::Dark, color);
+ palette.setColor(QPalette::Mid, color);
+ palette.setColor(QPalette::Shadow, color);
+ palette.setColor(QPalette::Highlight, color);
+ palette.setColor(QPalette::HighlightedText, color);
+ palette.setColor(QPalette::Link, color);
+ palette.setColor(QPalette::LinkVisited, color);
+ return palette;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/utils.h b/src/plugins/qmldesigner/components/curveeditor/detail/utils.h
new file mode 100644
index 0000000000..77cbd2c7bf
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/utils.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+QT_BEGIN_NAMESPACE
+class QColor;
+class QPalette;
+class QPointF;
+class QRectF;
+class QTransform;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+double clamp(double val, double lo, double hi);
+
+double lerp(double blend, double a, double b);
+
+double scaleX(const QTransform &transform);
+
+double scaleY(const QTransform &transform);
+
+void grow(QRectF &rect, const QPointF &point);
+
+QRectF bbox(const QRectF &rect, const QTransform &transform);
+
+QPalette singleColorPalette(const QColor &color);
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp b/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp
new file mode 100644
index 0000000000..8ff577c0a5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "keyframe.h"
+
+namespace DesignTools {
+
+Keyframe::Keyframe()
+ : m_position()
+ , m_leftHandle()
+ , m_rightHandle()
+{}
+
+Keyframe::Keyframe(const QPointF &position)
+ : m_position(position)
+ , m_leftHandle()
+ , m_rightHandle()
+{}
+
+Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle)
+ : m_position(position)
+ , m_leftHandle(leftHandle)
+ , m_rightHandle(rightHandle)
+{}
+
+bool Keyframe::hasLeftHandle() const
+{
+ return !m_leftHandle.isNull();
+}
+
+bool Keyframe::hasRightHandle() const
+{
+ return !m_rightHandle.isNull();
+}
+
+QPointF Keyframe::position() const
+{
+ return m_position;
+}
+
+QPointF Keyframe::leftHandle() const
+{
+ return m_leftHandle;
+}
+
+QPointF Keyframe::rightHandle() const
+{
+ return m_rightHandle;
+}
+
+void Keyframe::setPosition(const QPointF &pos)
+{
+ m_position = pos;
+}
+
+void Keyframe::setLeftHandle(const QPointF &pos)
+{
+ m_leftHandle = pos;
+}
+
+void Keyframe::setRightHandle(const QPointF &pos)
+{
+ m_rightHandle = pos;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/keyframe.h b/src/plugins/qmldesigner/components/curveeditor/keyframe.h
new file mode 100644
index 0000000000..5e6042531b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/keyframe.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QPointF>
+
+namespace DesignTools {
+
+class Keyframe
+{
+public:
+ Keyframe();
+
+ Keyframe(const QPointF &position);
+
+ Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle);
+
+ bool hasLeftHandle() const;
+
+ bool hasRightHandle() const;
+
+ QPointF position() const;
+
+ QPointF leftHandle() const;
+
+ QPointF rightHandle() const;
+
+ void setPosition(const QPointF &pos);
+
+ void setLeftHandle(const QPointF &pos);
+
+ void setRightHandle(const QPointF &pos);
+
+private:
+ QPointF m_position;
+
+ QPointF m_leftHandle;
+
+ QPointF m_rightHandle;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp
new file mode 100644
index 0000000000..beed419e54
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp
@@ -0,0 +1,227 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "treeitem.h"
+
+#include <QIcon>
+#include <QVariant>
+
+namespace DesignTools {
+
+TreeItem::TreeItem(const QString &name)
+ : m_name(name)
+ , m_id(0)
+ , m_locked(false)
+ , m_pinned(false)
+ , m_parent(nullptr)
+ , m_children()
+{}
+
+TreeItem::~TreeItem()
+{
+ m_parent = nullptr;
+
+ qDeleteAll(m_children);
+}
+
+QIcon TreeItem::icon() const
+{
+ return QIcon();
+}
+
+NodeTreeItem *TreeItem::asNodeItem()
+{
+ return nullptr;
+}
+
+PropertyTreeItem *TreeItem::asPropertyItem()
+{
+ return nullptr;
+}
+
+unsigned int TreeItem::id() const
+{
+ return m_id;
+}
+
+bool TreeItem::locked() const
+{
+ return m_locked;
+}
+
+bool TreeItem::pinned() const
+{
+ return m_pinned;
+}
+
+int TreeItem::row() const
+{
+ if (m_parent) {
+ for (size_t i = 0; i < m_parent->m_children.size(); ++i) {
+ if (m_parent->m_children[i] == this)
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+int TreeItem::column() const
+{
+ return 0;
+}
+
+int TreeItem::rowCount() const
+{
+ return m_children.size();
+}
+
+int TreeItem::columnCount() const
+{
+ return 3;
+}
+
+TreeItem *TreeItem::parent() const
+{
+ return m_parent;
+}
+
+TreeItem *TreeItem::child(int row) const
+{
+ if (row < 0 || row >= static_cast<int>(m_children.size()))
+ return nullptr;
+
+ return m_children.at(row);
+}
+
+TreeItem *TreeItem::find(unsigned int id) const
+{
+ for (auto *child : m_children) {
+ if (child->id() == id)
+ return child;
+
+ if (auto *childsChild = child->find(id))
+ return childsChild;
+ }
+
+ return nullptr;
+}
+
+QVariant TreeItem::data(int column) const
+{
+ switch (column) {
+ case 0:
+ return QVariant(m_name);
+ case 1:
+ return QVariant(m_locked);
+ case 2:
+ return QVariant(m_pinned);
+ case 3:
+ return QVariant(m_id);
+ default:
+ return QVariant();
+ }
+}
+
+QVariant TreeItem::headerData(int column) const
+{
+ switch (column) {
+ case 0:
+ return QString("Name");
+ case 1:
+ return QString("L");
+ case 2:
+ return QString("P");
+ case 3:
+ return QString("Id");
+ default:
+ return QVariant();
+ }
+}
+
+void TreeItem::setId(unsigned int &id)
+{
+ m_id = id;
+
+ for (auto *child : m_children)
+ child->setId(++id);
+}
+
+void TreeItem::addChild(TreeItem *child)
+{
+ child->m_parent = this;
+ m_children.push_back(child);
+}
+
+void TreeItem::setLocked(bool locked)
+{
+ m_locked = locked;
+}
+
+void TreeItem::setPinned(bool pinned)
+{
+ m_pinned = pinned;
+}
+
+
+NodeTreeItem::NodeTreeItem(const QString &name, const QIcon &icon)
+ : TreeItem(name)
+ , m_icon(icon)
+{
+ Q_UNUSED(icon);
+}
+
+NodeTreeItem *NodeTreeItem::asNodeItem()
+{
+ return this;
+}
+
+QIcon NodeTreeItem::icon() const
+{
+ return m_icon;
+}
+
+
+PropertyTreeItem::PropertyTreeItem(const QString &name, const AnimationCurve &curve)
+ : TreeItem(name)
+ , m_curve(curve)
+{}
+
+PropertyTreeItem *PropertyTreeItem::asPropertyItem()
+{
+ return this;
+}
+
+AnimationCurve PropertyTreeItem::curve() const
+{
+ return m_curve;
+}
+
+void PropertyTreeItem::setCurve(const AnimationCurve &curve)
+{
+ m_curve = curve;
+}
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/curveeditor/treeitem.h b/src/plugins/qmldesigner/components/curveeditor/treeitem.h
new file mode 100644
index 0000000000..5b31dc2fc8
--- /dev/null
+++ b/src/plugins/qmldesigner/components/curveeditor/treeitem.h
@@ -0,0 +1,137 @@
+
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Tooling
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "animationcurve.h"
+
+#include <QIcon>
+#include <QString>
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+class QIcon;
+class QVariant;
+QT_END_NAMESPACE
+
+namespace DesignTools {
+
+class NodeTreeItem;
+class PropertyTreeItem;
+
+class TreeItem
+{
+public:
+ TreeItem(const QString &name);
+
+ virtual ~TreeItem();
+
+ virtual QIcon icon() const;
+
+ virtual NodeTreeItem *asNodeItem();
+
+ virtual PropertyTreeItem *asPropertyItem();
+
+ unsigned int id() const;
+
+ bool locked() const;
+
+ bool pinned() const;
+
+ int row() const;
+
+ int column() const;
+
+ int rowCount() const;
+
+ int columnCount() const;
+
+ TreeItem *parent() const;
+
+ TreeItem *child(int row) const;
+
+ TreeItem *find(unsigned int row) const;
+
+ QVariant data(int column) const;
+
+ QVariant headerData(int column) const;
+
+ void setId(unsigned int &id);
+
+ void addChild(TreeItem *child);
+
+ void setLocked(bool locked);
+
+ void setPinned(bool pinned);
+
+protected:
+ QString m_name;
+
+ unsigned int m_id;
+
+ bool m_locked;
+
+ bool m_pinned;
+
+ TreeItem *m_parent;
+
+ std::vector<TreeItem *> m_children;
+};
+
+
+class NodeTreeItem : public TreeItem
+{
+public:
+ NodeTreeItem(const QString &name, const QIcon &icon);
+
+ NodeTreeItem *asNodeItem() override;
+
+ QIcon icon() const override;
+
+private:
+ QIcon m_icon;
+};
+
+
+class PropertyTreeItem : public TreeItem
+{
+public:
+ PropertyTreeItem(const QString &name, const AnimationCurve &curve);
+
+ PropertyTreeItem *asPropertyItem() override;
+
+ AnimationCurve curve() const;
+
+ void setCurve(const AnimationCurve &curve);
+
+private:
+ using TreeItem::addChild;
+
+ AnimationCurve m_curve;
+};
+
+} // End namespace DesignTools.
diff --git a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp
index 356fbf4f32..6d3cef0fbd 100644
--- a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp
@@ -64,7 +64,7 @@ QWidget *BackgroundAction::createWidget(QWidget *parent)
}
comboBox->setCurrentIndex(0);
- connect(comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &BackgroundAction::emitBackgroundChanged);
comboBox->setProperty("hideborder", true);
diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
index 0737e2ab45..6446e2422f 100644
--- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
@@ -42,7 +42,6 @@ static Q_LOGGING_CATEGORY(dragToolInfo, "qtc.qmldesigner.formeditor", QtWarningM
namespace QmlDesigner {
-
DragTool::DragTool(FormEditorView *editorView)
: AbstractFormEditorTool(editorView),
m_moveManipulator(editorView->scene()->manipulatorLayerItem(), editorView),
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
index 2cc524c799..9f6d3e9cd8 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
@@ -41,7 +41,6 @@
namespace QmlDesigner {
-
FormEditorScene *FormEditorItem::scene() const {
return qobject_cast<FormEditorScene*>(QGraphicsItem::scene());
}
@@ -110,7 +109,7 @@ void FormEditorItem::updateGeometry()
m_boundingRect = m_paintedBoundingRect.united(m_selectionBoundingRect);
setTransform(qmlItemNode().instanceTransformWithContentTransform());
//the property for zValue is called z in QGraphicsObject
- if (qmlItemNode().instanceValue("z").isValid())
+ if (qmlItemNode().instanceValue("z").isValid() && !qmlItemNode().isRootModelNode())
setZValue(qmlItemNode().instanceValue("z").toDouble());
}
@@ -260,7 +259,7 @@ static void paintTextInPlaceHolderForInvisbleItem(QPainter *painter,
QFontMetrics fm(font);
painter->rotate(90);
- if (fm.width(displayText) > (boundingRect.height() - 32) && displayText.length() > 4) {
+ if (fm.horizontalAdvance(displayText) > (boundingRect.height() - 32) && displayText.length() > 4) {
displayText = fm.elidedText(displayText, Qt::ElideRight, boundingRect.height() - 32, Qt::TextShowMnemonic);
}
diff --git a/src/plugins/qmldesigner/components/formeditor/numberseriesaction.cpp b/src/plugins/qmldesigner/components/formeditor/numberseriesaction.cpp
index 0ce0a995ba..27bfbe4f83 100644
--- a/src/plugins/qmldesigner/components/formeditor/numberseriesaction.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/numberseriesaction.cpp
@@ -59,7 +59,7 @@ QWidget *NumberSeriesAction::createWidget(QWidget *parent)
comboBox->setModel(m_comboBoxModel.data());
comboBox->setCurrentIndex(m_comboBoxModelIndex);
- connect(comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &NumberSeriesAction::emitValueChanged);
return comboBox;
diff --git a/src/plugins/qmldesigner/components/importmanager/importswidget.cpp b/src/plugins/qmldesigner/components/importmanager/importswidget.cpp
index 0d2218a42b..07ada7c858 100644
--- a/src/plugins/qmldesigner/components/importmanager/importswidget.cpp
+++ b/src/plugins/qmldesigner/components/importmanager/importswidget.cpp
@@ -39,7 +39,7 @@ ImportsWidget::ImportsWidget(QWidget *parent) :
{
setWindowTitle(tr("Import Manager"));
m_addImportComboBox = new ImportManagerComboBox(this);
- connect(m_addImportComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
+ connect(m_addImportComboBox, QOverload<int>::of(&QComboBox::activated),
this, &ImportsWidget::addSelectedImport);
}
diff --git a/src/plugins/qmldesigner/components/integration/componentaction.cpp b/src/plugins/qmldesigner/components/integration/componentaction.cpp
index daedbe8d7d..c3c6875d63 100644
--- a/src/plugins/qmldesigner/components/integration/componentaction.cpp
+++ b/src/plugins/qmldesigner/components/integration/componentaction.cpp
@@ -53,7 +53,7 @@ QWidget *ComponentAction::createWidget(QWidget *parent)
comboBox->setToolTip(tr("Edit sub components defined in this file."));
comboBox->setModel(m_componentView->standardItemModel());
comboBox->setCurrentIndex(-1);
- connect(comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
+ connect(comboBox, QOverload<int>::of(&QComboBox::activated),
this, &ComponentAction::emitCurrentComponentChanged);
connect(this, &ComponentAction::currentIndexChanged, comboBox, &QComboBox::setCurrentIndex);
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp
index 7a0f1f10b5..4f23154945 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.cpp
+++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp
@@ -64,7 +64,6 @@ enum {
namespace QmlDesigner {
-
/**
\class QmlDesigner::DesignDocument
@@ -197,7 +196,7 @@ QString DesignDocument::simplfiedDisplayName() const
return rootModelNode().simplifiedTypeName();
}
-void DesignDocument::updateFileName(const Utils::FileName & /*oldFileName*/, const Utils::FileName &newFileName)
+void DesignDocument::updateFileName(const Utils::FilePath & /*oldFileName*/, const Utils::FilePath &newFileName)
{
if (m_documentModel)
m_documentModel->setFileUrl(QUrl::fromLocalFile(newFileName.toString()));
@@ -210,11 +209,11 @@ void DesignDocument::updateFileName(const Utils::FileName & /*oldFileName*/, con
emit displayNameChanged(displayName());
}
-Utils::FileName DesignDocument::fileName() const
+Utils::FilePath DesignDocument::fileName() const
{
if (editor())
return editor()->document()->filePath();
- return Utils::FileName();
+ return Utils::FilePath();
}
Kit *DesignDocument::currentKit() const
@@ -251,7 +250,7 @@ void DesignDocument::loadDocument(QPlainTextEdit *edit)
m_inFileComponentTextModifier.reset();
- updateFileName(Utils::FileName(), fileName());
+ updateFileName(Utils::FilePath(), fileName());
updateQrcFiles();
@@ -288,7 +287,7 @@ void DesignDocument::updateQrcFiles()
ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName());
if (currentProject) {
- for (const Utils::FileName &fileName : currentProject->files(ProjectExplorer::Project::SourceFiles)) {
+ for (const Utils::FilePath &fileName : currentProject->files(ProjectExplorer::Project::SourceFiles)) {
if (fileName.endsWith(".qrc"))
QmlJS::ModelManagerInterface::instance()->updateQrcFile(fileName.toString());
}
@@ -366,18 +365,13 @@ void DesignDocument::deleteSelected()
if (!currentModel())
return;
- try {
- RewriterTransaction transaction(rewriterView(), QByteArrayLiteral("DesignDocument::deleteSelected"));
+ rewriterView()->executeInTransaction("DesignDocument::deleteSelected", [this](){
QList<ModelNode> toDelete = view()->selectedModelNodes();
foreach (ModelNode node, toDelete) {
if (node.isValid() && !node.isRootNode() && QmlObjectNode::isValidQmlObjectNode(node))
QmlObjectNode(node).destroy();
}
-
- transaction.commit();
- } catch (const RewritingException &e) {
- e.showException();
- }
+ });
}
void DesignDocument::copySelected()
@@ -466,10 +460,8 @@ void DesignDocument::paste()
}
}
- QList<ModelNode> pastedNodeList;
-
- try {
- RewriterTransaction transaction(rewriterView(), QByteArrayLiteral("DesignDocument::paste1"));
+ rewriterView()->executeInTransaction("DesignDocument::paste1", [this, &view, selectedNodes, targetNode](){
+ QList<ModelNode> pastedNodeList;
int offset = double(qrand()) / RAND_MAX * 20 - 10;
@@ -482,14 +474,10 @@ void DesignDocument::paste()
}
view.setSelectedModelNodes(pastedNodeList);
- transaction.commit();
- } catch (const RewritingException &e) {
- qWarning() << e.description(); //silent error
- }
- } else {
- try {
- RewriterTransaction transaction(rewriterView(), QByteArrayLiteral("DesignDocument::paste2"));
+ });
+ } else {
+ rewriterView()->executeInTransaction("DesignDocument::paste1", [this, &view, selectedNodes, rootNode](){
currentModel()->attachView(&view);
ModelNode pastedNode(view.insertModel(rootNode));
ModelNode targetNode;
@@ -501,9 +489,9 @@ void DesignDocument::paste()
targetNode = view.rootModelNode();
if (targetNode.hasParentProperty() &&
- (pastedNode.simplifiedTypeName() == targetNode.simplifiedTypeName()) &&
- (pastedNode.variantProperty("width").value() == targetNode.variantProperty("width").value()) &&
- (pastedNode.variantProperty("height").value() == targetNode.variantProperty("height").value()))
+ (pastedNode.simplifiedTypeName() == targetNode.simplifiedTypeName()) &&
+ (pastedNode.variantProperty("width").value() == targetNode.variantProperty("width").value()) &&
+ (pastedNode.variantProperty("height").value() == targetNode.variantProperty("height").value()))
targetNode = targetNode.parentProperty().parentModelNode();
@@ -515,15 +503,9 @@ void DesignDocument::paste()
} else {
qWarning() << "Cannot reparent to" << targetNode;
}
-
- transaction.commit();
- NodeMetaInfo::clearCache();
-
view.setSelectedModelNodes({pastedNode});
- transaction.commit();
- } catch (const RewritingException &e) {
- qWarning() << e.description(); //silent error
- }
+ });
+ NodeMetaInfo::clearCache();
}
}
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h
index 0bb5377c6d..eba0cf4060 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.h
+++ b/src/plugins/qmldesigner/components/integration/designdocument.h
@@ -90,7 +90,7 @@ public:
TextEditor::BaseTextEditor *textEditor() const;
QPlainTextEdit *plainTextEdit() const;
- Utils::FileName fileName() const;
+ Utils::FilePath fileName() const;
ProjectExplorer::Kit *currentKit() const;
bool isDocumentLoaded() const;
@@ -121,7 +121,7 @@ public:
void changeToMaster();
private: // functions
- void updateFileName(const Utils::FileName &oldFileName, const Utils::FileName &newFileName);
+ void updateFileName(const Utils::FilePath &oldFileName, const Utils::FilePath &newFileName);
void changeToInFileComponentModel(ComponentTextModifier *textModifer);
diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp
index 089a540b8f..8739ec64a8 100644
--- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp
+++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp
@@ -137,7 +137,7 @@ static QRect drawText(QPainter *painter,
displayString = styleOption.fontMetrics.elidedText(displayString, Qt::ElideMiddle, styleOption.rect.width() - extraSpace);
displayStringOffset = QPoint(5 + iconOffset, -5);
- width = styleOption.fontMetrics.width(displayString);
+ width = styleOption.fontMetrics.horizontalAdvance(displayString);
QPoint textPosition = styleOption.rect.bottomLeft() + displayStringOffset;
painter->drawText(textPosition, displayString);
diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
index 126fed8e68..8c89acb7c8 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
+++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
@@ -246,14 +246,29 @@ Qt::ItemFlags NavigatorTreeModel::flags(const QModelIndex &index) const
| Qt::ItemNeverHasChildren;
}
+void static appendForcedNodes(const NodeListProperty &property, QList<ModelNode> &list)
+{
+ const QStringList visibleProperties = NodeHints::fromModelNode(property.parentModelNode()).visibleNonDefaultProperties();
+ for (const ModelNode &node : property.parentModelNode().directSubModelNodes()) {
+ if (!list.contains(node) && visibleProperties.contains(QString::fromUtf8(node.parentProperty().name())))
+ list.append(node);
+ }
+}
+
QList<ModelNode> filteredList(const NodeListProperty &property, bool filter)
{
if (!filter)
return property.toModelNodeList();
- return Utils::filtered(property.toModelNodeList(), [] (const ModelNode &arg) {
+ QList<ModelNode> list;
+
+ list.append(Utils::filtered(property.toModelNodeList(), [] (const ModelNode &arg) {
return QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator();
- });
+ }));
+
+ appendForcedNodes(property, list);
+
+ return list;
}
QModelIndex NavigatorTreeModel::index(int row, int column,
@@ -431,7 +446,8 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
static bool findTargetProperty(const QModelIndex &rowModelIndex,
NavigatorTreeModel *navigatorTreeModel,
NodeAbstractProperty *targetProperty,
- int *targetRowNumber)
+ int *targetRowNumber,
+ const PropertyName &propertyName = {})
{
QModelIndex targetItemIndex;
PropertyName targetPropertyName;
@@ -445,7 +461,10 @@ static bool findTargetProperty(const QModelIndex &rowModelIndex,
if (!targetNode.metaInfo().hasDefaultProperty())
return false;
- targetPropertyName = targetNode.metaInfo().defaultPropertyName();
+ if (propertyName.isEmpty() || !targetNode.metaInfo().hasProperty(propertyName))
+ targetPropertyName = targetNode.metaInfo().defaultPropertyName();
+ else
+ targetPropertyName = propertyName;
}
// Disallow dropping items between properties, which are listed first.
@@ -494,26 +513,30 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
int targetRowNumber = rowNumber;
NodeAbstractProperty targetProperty;
- bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber);
+ const ItemLibraryEntry itemLibraryEntry =
+ createItemLibraryEntryFromMimeData(mimeData->data("application/vnd.bauhaus.itemlibraryinfo"));
- if (foundTarget) {
- const ItemLibraryEntry itemLibraryEntry =
- createItemLibraryEntryFromMimeData(mimeData->data("application/vnd.bauhaus.itemlibraryinfo"));
+ const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry);
+
+ const QString targetPropertyName = hints.forceNonDefaultProperty();
+ bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber, targetPropertyName.toUtf8());
+
+ if (foundTarget) {
if (!NodeHints::fromItemLibraryEntry(itemLibraryEntry).canBeDroppedInNavigator())
return;
- const QmlItemNode newQmlItemNode = QmlItemNode::createQmlItemNode(m_view, itemLibraryEntry, QPointF(), targetProperty);
+ const QmlObjectNode newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty);
- if (newQmlItemNode.isValid() && targetProperty.isNodeListProperty()) {
+ if (newQmlObjectNode.isValid() && targetProperty.isNodeListProperty()) {
QList<ModelNode> newModelNodeList;
- newModelNodeList.append(newQmlItemNode);
+ newModelNodeList.append(newQmlObjectNode);
moveNodesInteractive(targetProperty, newModelNodeList, targetRowNumber);
}
- if (newQmlItemNode.isValid())
- m_view->selectModelNode(newQmlItemNode.modelNode());
+ if (newQmlObjectNode.isValid())
+ m_view->selectModelNode(newQmlObjectNode.modelNode());
}
}
@@ -545,10 +568,9 @@ void NavigatorTreeModel::handleItemLibraryImageDrop(const QMimeData *mimeData, i
void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProperty, const QList<ModelNode> &modelNodes, int targetIndex)
{
QTC_ASSERT(m_view, return);
- try {
- const TypeName propertyQmlType = parentProperty.parentModelNode().metaInfo().propertyTypeName(parentProperty.name());
- RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("NavigatorTreeModel::moveNodesInteractive"));
+ m_view->executeInTransaction("NavigatorTreeModel::moveNodesInteractive",[this, &parentProperty, modelNodes, targetIndex](){
+ const TypeName propertyQmlType = parentProperty.parentModelNode().metaInfo().propertyTypeName(parentProperty.name());
foreach (const ModelNode &modelNode, modelNodes) {
if (modelNode.isValid()
&& modelNode != parentProperty.parentModelNode()
@@ -565,10 +587,7 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper
}
}
}
- transaction.commit();
- } catch (const RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
}
Qt::DropActions NavigatorTreeModel::supportedDropActions() const
diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreeview.h b/src/plugins/qmldesigner/components/navigator/navigatortreeview.h
index cc5a0e344d..1001977b8f 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatortreeview.h
+++ b/src/plugins/qmldesigner/components/navigator/navigatortreeview.h
@@ -29,7 +29,6 @@
namespace QmlDesigner {
-
class NavigatorTreeView : public QTreeView
{
Q_OBJECT
diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
index 07434102fc..57578d866f 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
+++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
@@ -198,16 +198,10 @@ void NavigatorView::handleChangedExport(const ModelNode &modelNode, bool exporte
if (rootNode.hasProperty(modelNodeId))
rootNode.removeProperty(modelNodeId);
if (exported) {
- try {
- RewriterTransaction transaction =
- beginRewriterTransaction(QByteArrayLiteral("NavigatorTreeModel:exportItem"));
-
+ executeInTransaction("NavigatorTreeModel:exportItem", [this, modelNode](){
QmlObjectNode qmlObjectNode(modelNode);
qmlObjectNode.ensureAliasExport();
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
}
}
@@ -400,7 +394,8 @@ void NavigatorView::upButtonClicked()
index--;
if (index < 0)
index = node.parentProperty().count() - 1; //wrap around
- node.parentProperty().toNodeListProperty().slide(oldIndex, index);
+ if (oldIndex != index)
+ node.parentProperty().toNodeListProperty().slide(oldIndex, index);
}
}
updateItemSelection();
@@ -417,7 +412,8 @@ void NavigatorView::downButtonClicked()
index++;
if (index >= node.parentProperty().count())
index = 0; //wrap around
- node.parentProperty().toNodeListProperty().slide(oldIndex, index);
+ if (oldIndex != index)
+ node.parentProperty().toNodeListProperty().slide(oldIndex, index);
}
}
updateItemSelection();
diff --git a/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp b/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp
new file mode 100644
index 0000000000..d2a8bf75c3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp
@@ -0,0 +1,174 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "controlpoint.h"
+
+#include <QtDebug>
+
+#include <variantproperty.h>
+
+#include <rewritertransaction.h>
+
+namespace QmlDesigner {
+
+ControlPoint::ControlPoint() = default;
+
+ControlPoint::ControlPoint(const ControlPoint &other) = default;
+
+ControlPoint::ControlPoint(const QPointF &coordinate)
+ : d(new ControlPointData)
+{
+ d->coordinate = coordinate;
+}
+
+ControlPoint::ControlPoint(double x, double y)
+ : d(new ControlPointData)
+{
+ d->coordinate = QPointF(x, y);
+}
+
+ControlPoint::~ControlPoint() = default;
+
+ControlPoint &ControlPoint::operator =(const ControlPoint &other)
+{
+ if (d != other.d)
+ d = other.d;
+
+ return *this;
+}
+
+void ControlPoint::setX(double x)
+{
+ d->coordinate.setX(x);
+}
+
+void ControlPoint::setY(double y)
+{
+ d->coordinate.setY(y);
+}
+
+void ControlPoint::setCoordinate(const QPointF &coordinate)
+{
+ d->coordinate = coordinate;
+}
+
+void ControlPoint::setPathElementModelNode(const ModelNode &modelNode)
+{
+ d->pathElementModelNode = modelNode;
+}
+
+ModelNode ControlPoint::pathElementModelNode() const
+{
+ return d->pathElementModelNode;
+}
+
+void ControlPoint::setPathModelNode(const ModelNode &pathModelNode)
+{
+ d->pathModelNode = pathModelNode;
+}
+
+ModelNode ControlPoint::pathModelNode() const
+{
+ return d->pathModelNode;
+}
+
+void ControlPoint::setPointType(PointType pointType)
+{
+ d->pointType = pointType;
+}
+
+PointType ControlPoint::pointType() const
+{
+ return d->pointType;
+}
+
+QPointF ControlPoint::coordinate() const
+{
+ return d->coordinate;
+}
+
+bool ControlPoint::isValid() const
+{
+ return d.data();
+}
+
+bool ControlPoint::isEditPoint() const
+{
+ return isValid() && (pointType() == StartPoint || pointType() == EndPoint);
+}
+
+bool ControlPoint::isControlVertex() const
+{
+ return isValid() && (pointType() == FirstControlPoint || pointType() == SecondControlPoint);
+}
+
+void ControlPoint::updateModelNode()
+{
+ switch (pointType()) {
+ case StartPoint:
+ d->pathModelNode.variantProperty("startX").setValue(coordinate().x());
+ d->pathModelNode.variantProperty("startY").setValue(coordinate().y());
+ break;
+ case FirstControlPoint:
+ d->pathElementModelNode.variantProperty("control1X").setValue(coordinate().x());
+ d->pathElementModelNode.variantProperty("control1Y").setValue(coordinate().y());
+ break;
+ case SecondControlPoint:
+ d->pathElementModelNode.variantProperty("control2X").setValue(coordinate().x());
+ d->pathElementModelNode.variantProperty("control2Y").setValue(coordinate().y());
+ break;
+ case EndPoint:
+ d->pathElementModelNode.variantProperty("x").setValue(coordinate().x());
+ d->pathElementModelNode.variantProperty("y").setValue(coordinate().y());
+ break;
+ case StartAndEndPoint:
+ d->pathElementModelNode.variantProperty("x").setValue(coordinate().x());
+ d->pathElementModelNode.variantProperty("y").setValue(coordinate().y());
+ d->pathModelNode.variantProperty("startX").setValue(coordinate().x());
+ d->pathModelNode.variantProperty("startY").setValue(coordinate().y());
+ break;
+ }
+}
+
+bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint)
+{
+ return firstControlPoint.d.data() == secondControlPoint.d.data() && firstControlPoint.d.data();
+}
+
+QDebug operator<<(QDebug debug, const ControlPoint &controlPoint)
+{
+ if (controlPoint.isValid()) {
+ debug.nospace() << "ControlPoint("
+ << controlPoint.coordinate().x() << ", "
+ << controlPoint.coordinate().y() << ", "
+ << controlPoint.pointType() << ')';
+ } else {
+ debug.nospace() << "ControlPoint(invalid)";
+ }
+
+ return debug.space();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/controlpoint.h b/src/plugins/qmldesigner/components/pathtool/controlpoint.h
new file mode 100644
index 0000000000..39dc184978
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/controlpoint.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <modelnode.h>
+
+#include <QPointF>
+#include <QExplicitlySharedDataPointer>
+
+namespace QmlDesigner {
+
+enum PointType {
+ StartPoint,
+ FirstControlPoint,
+ SecondControlPoint,
+ EndPoint,
+ StartAndEndPoint
+};
+
+class ControlPointData : public QSharedData
+{
+public:
+ ModelNode pathElementModelNode;
+ ModelNode pathModelNode;
+ QPointF coordinate;
+ PointType pointType;
+};
+
+class ControlPoint
+{
+ friend bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint);
+
+public:
+ ControlPoint();
+ ControlPoint(const ControlPoint &other);
+ ControlPoint(const QPointF &coordinate);
+ ControlPoint(double x, double y);
+
+ ~ControlPoint();
+
+ ControlPoint &operator =(const ControlPoint &other);
+
+ void setX(double x);
+ void setY(double y);
+ void setCoordinate(const QPointF &coordinate);
+ QPointF coordinate() const;
+
+ void setPathElementModelNode(const ModelNode &pathElementModelNode);
+ ModelNode pathElementModelNode() const;
+
+ void setPathModelNode(const ModelNode &pathModelNode);
+ ModelNode pathModelNode() const;
+
+ void setPointType(PointType pointType);
+ PointType pointType() const;
+
+ bool isValid() const;
+ bool isEditPoint() const;
+ bool isControlVertex() const;
+
+ void updateModelNode();
+
+private:
+ QExplicitlySharedDataPointer<ControlPointData> d;
+};
+
+bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint);
+QDebug operator<<(QDebug debug, const ControlPoint &controlPoint);
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp b/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp
new file mode 100644
index 0000000000..0005514339
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp
@@ -0,0 +1,367 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "cubicsegment.h"
+
+#include <qmath.h>
+#include <QtDebug>
+
+
+namespace QmlDesigner {
+
+CubicSegment::CubicSegment() = default;
+
+CubicSegment CubicSegment::create()
+{
+ CubicSegment cubicSegment;
+ cubicSegment.d = new CubicSegmentData;
+
+ return cubicSegment;
+}
+
+void CubicSegment::setModelNode(const ModelNode &modelNode)
+{
+ d->modelNode = modelNode;
+}
+
+ModelNode CubicSegment::modelNode() const
+{
+ return d->modelNode;
+}
+
+void CubicSegment::setFirstControlPoint(const ControlPoint &firstControlPoint)
+{
+ d->firstControllPoint = firstControlPoint;
+}
+
+void CubicSegment::setFirstControlPoint(double x, double y)
+{
+ d->firstControllPoint.setX(x);
+ d->firstControllPoint.setY(y);
+}
+
+void CubicSegment::setFirstControlPoint(const QPointF &coordiante)
+{
+ d->firstControllPoint.setCoordinate(coordiante);
+}
+
+void CubicSegment::setSecondControlPoint(const ControlPoint &secondControlPoint)
+{
+ d->secondControllPoint = secondControlPoint;
+ d->secondControllPoint.setPathElementModelNode(d->modelNode);
+ d->secondControllPoint.setPointType(FirstControlPoint);
+}
+
+void CubicSegment::setSecondControlPoint(double x, double y)
+{
+ d->secondControllPoint.setX(x);
+ d->secondControllPoint.setY(y);
+ d->secondControllPoint.setPathElementModelNode(d->modelNode);
+ d->secondControllPoint.setPointType(FirstControlPoint);
+}
+
+void CubicSegment::setSecondControlPoint(const QPointF &coordiante)
+{
+ d->secondControllPoint.setCoordinate(coordiante);
+ d->secondControllPoint.setPathElementModelNode(d->modelNode);
+ d->secondControllPoint.setPointType(FirstControlPoint);
+}
+
+void CubicSegment::setThirdControlPoint(const ControlPoint &thirdControlPoint)
+{
+ d->thirdControllPoint = thirdControlPoint;
+ d->thirdControllPoint.setPathElementModelNode(d->modelNode);
+ d->thirdControllPoint.setPointType(SecondControlPoint);
+}
+
+void CubicSegment::setThirdControlPoint(double x, double y)
+{
+ d->thirdControllPoint.setX(x);
+ d->thirdControllPoint.setY(y);
+ d->thirdControllPoint.setPathElementModelNode(d->modelNode);
+ d->thirdControllPoint.setPointType(SecondControlPoint);
+}
+
+void CubicSegment::setThirdControlPoint(const QPointF &coordiante)
+{
+ d->thirdControllPoint.setCoordinate(coordiante);
+ d->thirdControllPoint.setPathElementModelNode(d->modelNode);
+ d->thirdControllPoint.setPointType(SecondControlPoint);
+}
+
+void CubicSegment::setFourthControlPoint(const ControlPoint &fourthControlPoint)
+{
+ d->fourthControllPoint = fourthControlPoint;
+ d->fourthControllPoint.setPathElementModelNode(d->modelNode);
+ d->fourthControllPoint.setPointType(EndPoint);
+}
+
+void CubicSegment::setFourthControlPoint(double x, double y)
+{
+ d->fourthControllPoint.setX(x);
+ d->fourthControllPoint.setY(y);
+ d->fourthControllPoint.setPathElementModelNode(d->modelNode);
+ d->fourthControllPoint.setPointType(EndPoint);
+}
+
+void CubicSegment::setFourthControlPoint(const QPointF &coordiante)
+{
+ d->fourthControllPoint.setCoordinate(coordiante);
+ d->fourthControllPoint.setPathElementModelNode(d->modelNode);
+ d->fourthControllPoint.setPointType(EndPoint);
+}
+
+void CubicSegment::setAttributes(const QMap<QString, QVariant> &attributes)
+{
+ d->attributes = attributes;
+}
+
+void CubicSegment::setPercent(double percent)
+{
+ d->percent = percent;
+}
+
+ControlPoint CubicSegment::firstControlPoint() const
+{
+ return d->firstControllPoint;
+}
+
+ControlPoint CubicSegment::secondControlPoint() const
+{
+ return d->secondControllPoint;
+}
+
+ControlPoint CubicSegment::thirdControlPoint() const
+{
+ return d->thirdControllPoint;
+}
+
+ControlPoint CubicSegment::fourthControlPoint() const
+{
+ return d->fourthControllPoint;
+}
+
+const QMap<QString, QVariant> CubicSegment::attributes() const
+{
+ return d->attributes;
+}
+
+double CubicSegment::percent() const
+{
+ return d->percent;
+}
+
+QList<ControlPoint> CubicSegment::controlPoints() const
+{
+ QList<ControlPoint> controlPointList;
+
+ controlPointList.reserve(4);
+
+ controlPointList.append(firstControlPoint());
+ controlPointList.append(secondControlPoint());
+ controlPointList.append(thirdControlPoint());
+ controlPointList.append(fourthControlPoint());
+
+ return controlPointList;
+}
+
+double CubicSegment::firstControlX() const
+{
+ return firstControlPoint().coordinate().x();
+}
+
+double CubicSegment::firstControlY() const
+{
+ return firstControlPoint().coordinate().y();
+}
+
+double CubicSegment::secondControlX() const
+{
+ return secondControlPoint().coordinate().x();
+}
+
+double CubicSegment::secondControlY() const
+{
+ return secondControlPoint().coordinate().y();
+}
+
+double CubicSegment::thirdControlX() const
+{
+ return thirdControlPoint().coordinate().x();
+}
+
+double CubicSegment::thirdControlY() const
+{
+ return thirdControlPoint().coordinate().y();
+}
+
+double CubicSegment::fourthControlX() const
+{
+ return fourthControlPoint().coordinate().x();
+}
+
+double CubicSegment::fourthControlY() const
+{
+ return fourthControlPoint().coordinate().y();
+}
+
+double CubicSegment::quadraticControlX() const
+{
+ return -0.25 * firstControlX() + 0.75 * secondControlX() + 0.75 * thirdControlX() - 0.25 * fourthControlX();
+}
+
+double CubicSegment::quadraticControlY() const
+{
+ return -0.25 * firstControlY() + 0.75 * secondControlY() + 0.75 * thirdControlY() - 0.25 * fourthControlY();
+}
+
+bool CubicSegment::isValid() const
+{
+ return d.data();
+}
+
+bool CubicSegment::canBeConvertedToLine() const
+{
+ return canBeConvertedToQuad()
+ && qFuzzyIsNull(((3. * d->firstControllPoint.coordinate())
+ - (6. * d->secondControllPoint.coordinate())
+ + (3. * d->thirdControllPoint.coordinate())).manhattanLength());;
+}
+
+bool CubicSegment::canBeConvertedToQuad() const
+{
+ return qFuzzyIsNull(((3. * d->secondControllPoint.coordinate())
+ - (3 * d->thirdControllPoint.coordinate())
+ + d->fourthControllPoint.coordinate()
+ - d->firstControllPoint.coordinate()).manhattanLength());
+}
+
+QPointF CubicSegment::sample(double t) const
+{
+ return qPow(1.-t, 3.) * firstControlPoint().coordinate()
+ + 3 * qPow(1.-t, 2.) * t * secondControlPoint().coordinate()
+ + 3 * qPow(t, 2.) * (1. - t) * thirdControlPoint().coordinate()
+ + qPow(t, 3.) * fourthControlPoint().coordinate();
+}
+
+double CubicSegment::minimumDistance(const QPointF &pickPoint, double &tReturnValue) const
+{
+ double actualMinimumDistance = 10000000.;
+ for (double t = 0.0; t <= 1.0; t += 0.1) {
+ QPointF samplePoint = sample(t);
+ QPointF distanceVector = pickPoint - samplePoint;
+ if (distanceVector.manhattanLength() < actualMinimumDistance) {
+ actualMinimumDistance = distanceVector.manhattanLength();
+ tReturnValue = t;
+ }
+ }
+
+ return actualMinimumDistance;
+}
+
+static QPointF interpolatedPoint(double t, const QPointF &firstPoint, const QPointF &secondPoint)
+{
+ return (secondPoint - firstPoint) * t + firstPoint;
+}
+
+QPair<CubicSegment, CubicSegment> CubicSegment::split(double t)
+{
+ // first pass
+ QPointF secondPointFirstSegment = interpolatedPoint(t, firstControlPoint().coordinate(), secondControlPoint().coordinate());
+ QPointF firstIntermediatPoint = interpolatedPoint(t, secondControlPoint().coordinate(), thirdControlPoint().coordinate());
+ QPointF thirdPointSecondSegment = interpolatedPoint(t, thirdControlPoint().coordinate(), fourthControlPoint().coordinate());
+
+ // second pass
+ QPointF thirdPointFirstSegment = interpolatedPoint(t, secondPointFirstSegment, firstIntermediatPoint);
+ QPointF secondPointSecondSegment = interpolatedPoint(t, firstIntermediatPoint, thirdPointSecondSegment);
+
+ // third pass
+ QPointF midPoint = interpolatedPoint(t, thirdPointFirstSegment, secondPointSecondSegment);
+ ControlPoint midControlPoint(midPoint);
+
+
+ CubicSegment firstCubicSegment = CubicSegment::create();
+ firstCubicSegment.setFirstControlPoint(firstControlPoint().coordinate());
+ firstCubicSegment.setSecondControlPoint(secondPointFirstSegment);
+ firstCubicSegment.setThirdControlPoint(thirdPointFirstSegment);
+ firstCubicSegment.setFourthControlPoint(midControlPoint);
+
+ CubicSegment secondCubicSegment = CubicSegment::create();
+ secondCubicSegment.setFirstControlPoint(midControlPoint);
+ secondCubicSegment.setSecondControlPoint(secondPointSecondSegment);
+ secondCubicSegment.setThirdControlPoint(thirdPointSecondSegment);
+ secondCubicSegment.setFourthControlPoint(fourthControlPoint().coordinate());
+
+ qDebug() << firstCubicSegment << secondCubicSegment;
+
+ return {firstCubicSegment, secondCubicSegment};
+}
+
+void CubicSegment::makeStraightLine()
+{
+ QPointF lineVector = fourthControlPoint().coordinate() - firstControlPoint().coordinate();
+ QPointF newSecondControlPoint = firstControlPoint().coordinate() + (lineVector * 0.3);
+ QPointF newThirdControlPoint = fourthControlPoint().coordinate() - (lineVector * 0.3);
+ setSecondControlPoint(newSecondControlPoint);
+ setThirdControlPoint(newThirdControlPoint);
+}
+
+void CubicSegment::updateModelNode()
+{
+ firstControlPoint().updateModelNode();
+ secondControlPoint().updateModelNode();
+ thirdControlPoint().updateModelNode();
+ fourthControlPoint().updateModelNode();
+}
+
+CubicSegmentData::CubicSegmentData()
+ : firstControllPoint(0., 0.),
+ secondControllPoint(0., 0.),
+ thirdControllPoint(0., 0.),
+ fourthControllPoint(0., 0.),
+ percent(-1.0)
+{
+}
+
+bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment)
+{
+ return firstCubicSegment.d.data() == secondCubicSegment.d.data();
+}
+
+QDebug operator<<(QDebug debug, const CubicSegment &cubicSegment)
+{
+ if (cubicSegment.isValid()) {
+ debug.nospace() << "CubicSegment("
+ << cubicSegment.firstControlPoint() << ", "
+ << cubicSegment.secondControlPoint() << ", "
+ << cubicSegment.thirdControlPoint() << ", "
+ << cubicSegment.fourthControlPoint() << ')';
+ } else {
+ debug.nospace() << "CubicSegment(invalid)";
+ }
+
+ return debug.space();
+}
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/cubicsegment.h b/src/plugins/qmldesigner/components/pathtool/cubicsegment.h
new file mode 100644
index 0000000000..e22b4e1aa3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/cubicsegment.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "controlpoint.h"
+
+#include <modelnode.h>
+
+#include <QMap>
+
+#include <QPointF>
+#include <QExplicitlySharedDataPointer>
+
+namespace QmlDesigner {
+
+class CubicSegmentData : public QSharedData
+{
+public:
+ CubicSegmentData();
+ ModelNode modelNode;
+ ControlPoint firstControllPoint;
+ ControlPoint secondControllPoint;
+ ControlPoint thirdControllPoint;
+ ControlPoint fourthControllPoint;
+ QMap<QString, QVariant> attributes;
+ double percent;
+};
+
+class CubicSegment
+{
+ friend bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment);
+
+public:
+ CubicSegment();
+
+ static CubicSegment create();
+
+ void setModelNode(const ModelNode &modelNode);
+ ModelNode modelNode() const;
+
+ void setFirstControlPoint(const ControlPoint &firstControlPoint);
+ void setFirstControlPoint(double x, double y);
+ void setFirstControlPoint(const QPointF &coordiante);
+
+ void setSecondControlPoint(const ControlPoint &secondControlPoint);
+ void setSecondControlPoint(double x, double y);
+ void setSecondControlPoint(const QPointF &coordiante);
+
+ void setThirdControlPoint(const ControlPoint &thirdControlPoint);
+ void setThirdControlPoint(double x, double y);
+ void setThirdControlPoint(const QPointF &coordiante);
+
+ void setFourthControlPoint(const ControlPoint &fourthControlPoint);
+ void setFourthControlPoint(double x, double y);
+ void setFourthControlPoint(const QPointF &coordiante);
+
+ void setAttributes(const QMap<QString, QVariant> &attributes);
+
+ void setPercent(double percent);
+
+ ControlPoint firstControlPoint() const;
+ ControlPoint secondControlPoint() const;
+ ControlPoint thirdControlPoint() const;
+ ControlPoint fourthControlPoint() const;
+
+ const QMap<QString, QVariant> attributes() const;
+
+ double percent() const;
+
+ QList<ControlPoint> controlPoints() const;
+
+ double firstControlX() const;
+ double firstControlY() const;
+ double secondControlX() const;
+ double secondControlY() const;
+ double thirdControlX() const;
+ double thirdControlY() const;
+ double fourthControlX() const;
+ double fourthControlY() const;
+ double quadraticControlX() const;
+ double quadraticControlY() const;
+
+ bool isValid() const;
+ bool canBeConvertedToLine() const;
+ bool canBeConvertedToQuad() const;
+
+ QPointF sample(double t) const;
+ double minimumDistance(const QPointF &pickPoint, double &t) const;
+
+ QPair<CubicSegment, CubicSegment> split(double t);
+
+ void makeStraightLine();
+
+ void updateModelNode();
+
+private:
+ QExplicitlySharedDataPointer<CubicSegmentData> d;
+};
+
+bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment);
+QDebug operator<<(QDebug debug, const CubicSegment &cubicSegment);
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/pathitem.cpp b/src/plugins/qmldesigner/components/pathtool/pathitem.cpp
new file mode 100644
index 0000000000..76fe6f0b90
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathitem.cpp
@@ -0,0 +1,971 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pathitem.h"
+
+#include <exception.h>
+#include <nodeproperty.h>
+#include <variantproperty.h>
+#include <nodelistproperty.h>
+#include <rewritingexception.h>
+#include <rewritertransaction.h>
+#include <formeditorscene.h>
+#include <formeditorview.h>
+#include <theme.h>
+
+#include <QPainter>
+#include <QMenu>
+#include <QtDebug>
+#include <QGraphicsSceneMouseEvent>
+
+namespace QmlDesigner {
+
+PathItem::PathItem(FormEditorScene* scene)
+ : m_selectionManipulator(this),
+ m_lastPercent(-1.),
+ m_formEditorItem(nullptr),
+ m_dontUpdatePath(false)
+{
+ scene->addItem(this);
+ setFlag(QGraphicsItem::ItemIsMovable, false);
+}
+
+PathItem::~PathItem()
+{
+ m_formEditorItem = nullptr;
+}
+
+static ModelNode pathModelNode(FormEditorItem *formEditorItem)
+{
+ ModelNode modelNode = formEditorItem->qmlItemNode().modelNode();
+
+ return modelNode.nodeProperty("path").modelNode();
+}
+
+using PropertyPair = QPair<PropertyName, QVariant>;
+
+void PathItem::writeLinePath(const ModelNode &pathNode, const CubicSegment &cubicSegment)
+{
+ QList<PropertyPair> propertyList;
+ propertyList.append(PropertyPair("x", cubicSegment.fourthControlX()));
+ propertyList.append(PropertyPair("y", cubicSegment.fourthControlY()));
+
+ ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathLine", pathNode.majorVersion(), pathNode.minorVersion(), propertyList);
+ pathNode.nodeListProperty("pathElements").reparentHere(lineNode);
+}
+
+void PathItem::writeQuadPath(const ModelNode &pathNode, const CubicSegment &cubicSegment)
+{
+ QList<QPair<PropertyName, QVariant> > propertyList;
+ propertyList.append(PropertyPair("controlX", cubicSegment.quadraticControlX()));
+ propertyList.append(PropertyPair("controlY", cubicSegment.quadraticControlY()));
+ propertyList.append(PropertyPair("x", cubicSegment.fourthControlX()));
+ propertyList.append(PropertyPair("y", cubicSegment.fourthControlY()));
+
+ ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathQuad", pathNode.majorVersion(), pathNode.minorVersion(), propertyList);
+ pathNode.nodeListProperty("pathElements").reparentHere(lineNode);
+}
+
+void PathItem::writeCubicPath(const ModelNode &pathNode, const CubicSegment &cubicSegment)
+{
+ QList<QPair<PropertyName, QVariant> > propertyList;
+ propertyList.append(PropertyPair("control1X", cubicSegment.secondControlX()));
+ propertyList.append(PropertyPair("control1Y", cubicSegment.secondControlY()));
+ propertyList.append(PropertyPair("control2X", cubicSegment.thirdControlX()));
+ propertyList.append(PropertyPair("control2Y", cubicSegment.thirdControlY()));
+ propertyList.append(PropertyPair("x", cubicSegment.fourthControlX()));
+ propertyList.append(PropertyPair("y", cubicSegment.fourthControlY()));
+
+ ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathCubic", pathNode.majorVersion(), pathNode.minorVersion(), propertyList);
+ pathNode.nodeListProperty("pathElements").reparentHere(lineNode);
+}
+
+void PathItem::writePathAttributes(const ModelNode &pathNode, const QMap<QString, QVariant> &attributes)
+{
+ QMapIterator<QString, QVariant> attributesIterator(attributes);
+ while (attributesIterator.hasNext()) {
+ attributesIterator.next();
+ QList<QPair<PropertyName, QVariant> > propertyList;
+ propertyList.append(PropertyPair("name", attributesIterator.key()));
+ propertyList.append(PropertyPair("value", attributesIterator.value()));
+
+ ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathAttribute", pathNode.majorVersion(), pathNode.minorVersion(), propertyList);
+ pathNode.nodeListProperty("pathElements").reparentHere(lineNode);
+ }
+}
+
+void PathItem::writePathPercent(const ModelNode& pathNode, double percent)
+{
+ if (percent >= 0.0) {
+ QList<QPair<PropertyName, QVariant> > propertyList;
+ propertyList.append(PropertyPair("value", percent));
+
+ ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathPercent", pathNode.majorVersion(), pathNode.minorVersion(), propertyList);
+ pathNode.nodeListProperty("pathElements").reparentHere(lineNode);
+ }
+}
+
+void PathItem::writePathToProperty()
+{
+ PathUpdateDisabler pathUpdateDisable(this);
+
+ ModelNode pathNode = pathModelNode(formEditorItem());
+
+ pathNode.view()->executeInTransaction("PathItem::writePathToProperty", [this, &pathNode](){
+ QList<ModelNode> pathSegmentNodes = pathNode.nodeListProperty("pathElements").toModelNodeList();
+
+ foreach (ModelNode pathSegment, pathSegmentNodes)
+ pathSegment.destroy();
+
+ if (!m_cubicSegments.isEmpty()) {
+ pathNode.variantProperty("startX").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().x());
+ pathNode.variantProperty("startY").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().y());
+
+ foreach (const CubicSegment &cubicSegment, m_cubicSegments) {
+ writePathAttributes(pathNode, cubicSegment.attributes());
+ writePathPercent(pathNode, cubicSegment.percent());
+
+ if (cubicSegment.canBeConvertedToLine())
+ writeLinePath(pathNode, cubicSegment);
+ else if (cubicSegment.canBeConvertedToQuad())
+ writeQuadPath(pathNode, cubicSegment);
+ else
+ writeCubicPath(pathNode, cubicSegment);
+ }
+
+ writePathAttributes(pathNode, m_lastAttributes);
+ writePathPercent(pathNode, m_lastPercent);
+ }
+ });
+}
+
+void PathItem::writePathAsCubicSegmentsOnly()
+{
+ PathUpdateDisabler pathUpdateDisabler(this);
+
+ ModelNode pathNode = pathModelNode(formEditorItem());
+ pathNode.view()->executeInTransaction("PathItem::writePathAsCubicSegmentsOnly", [this, &pathNode](){
+
+ QList<ModelNode> pathSegmentNodes = pathNode.nodeListProperty("pathElements").toModelNodeList();
+
+ foreach (ModelNode pathSegment, pathSegmentNodes)
+ pathSegment.destroy();
+
+ if (!m_cubicSegments.isEmpty()) {
+ pathNode.variantProperty("startX").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().x());
+ pathNode.variantProperty("startY").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().y());
+
+
+ foreach (const CubicSegment &cubicSegment, m_cubicSegments) {
+ writePathAttributes(pathNode, cubicSegment.attributes());
+ writePathPercent(pathNode, cubicSegment.percent());
+ writeCubicPath(pathNode, cubicSegment);
+ }
+
+ writePathAttributes(pathNode, m_lastAttributes);
+ writePathPercent(pathNode, m_lastPercent);
+ }
+ });
+}
+
+void PathItem::setFormEditorItem(FormEditorItem *formEditorItem)
+{
+ m_formEditorItem = formEditorItem;
+ setTransform(formEditorItem->sceneTransform());
+ updatePath();
+
+// m_textEdit->setPlainText(m_formEditorItem->qmlItemNode().modelValue("path").toString());
+}
+
+static bool hasPath(FormEditorItem *formEditorItem)
+{
+ ModelNode modelNode = formEditorItem->qmlItemNode().modelNode();
+
+ return modelNode.hasProperty("path") && modelNode.property("path").isNodeProperty();
+}
+
+QPointF startPoint(const ModelNode &modelNode)
+{
+ QPointF point;
+
+ if (modelNode.hasProperty("startX"))
+ point.setX(modelNode.variantProperty("startX").value().toDouble());
+
+ if (modelNode.hasProperty("startY"))
+ point.setY(modelNode.variantProperty("startY").value().toDouble());
+
+ return point;
+}
+
+static void addCubicSegmentToPainterPath(const CubicSegment &cubicSegment, QPainterPath &painterPath)
+{
+ painterPath.cubicTo(cubicSegment.secondControlPoint().coordinate(),
+ cubicSegment.thirdControlPoint().coordinate(),
+ cubicSegment.fourthControlPoint().coordinate());
+
+}
+
+static void drawCubicSegments(const QList<CubicSegment> &cubicSegments, QPainter *painter)
+{
+ painter->save();
+
+ QPainterPath curvePainterPath(cubicSegments.constFirst().firstControlPoint().coordinate());
+
+ foreach (const CubicSegment &cubicSegment, cubicSegments)
+ addCubicSegmentToPainterPath(cubicSegment, curvePainterPath);
+
+ painter->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ painter->drawPath(curvePainterPath);
+
+ painter->restore();
+}
+
+static void drawControlLine(const CubicSegment &cubicSegment, QPainter *painter)
+{
+ static const QPen solidPen(QColor(104, 183, 214), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
+ painter->setPen(solidPen);
+ painter->drawLine(cubicSegment.firstControlPoint().coordinate(),
+ cubicSegment.secondControlPoint().coordinate());
+
+ QVector<double> dashVector;
+ dashVector.append(4);
+ dashVector.append(4);
+ QPen dashedPen(QColor(104, 183, 214), 1, Qt::CustomDashLine, Qt::FlatCap, Qt::MiterJoin);
+ dashedPen.setDashPattern(dashVector);
+ painter->setPen(dashedPen);
+ painter->drawLine(cubicSegment.secondControlPoint().coordinate(),
+ cubicSegment.thirdControlPoint().coordinate());
+
+ painter->setPen(QPen(QColor(104, 183, 214), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ painter->drawLine(cubicSegment.thirdControlPoint().coordinate(),
+ cubicSegment.fourthControlPoint().coordinate());
+}
+
+static void drawControlLines(const QList<CubicSegment> &cubicSegments, QPainter *painter)
+{
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, false);
+
+ foreach (const CubicSegment &cubicSegment, cubicSegments)
+ drawControlLine(cubicSegment, painter);
+
+ painter->restore();
+}
+
+static QRectF controlPointShape(-2, -2, 5, 5);
+
+static void drawControlPoint(const ControlPoint &controlPoint, const QList<ControlPoint> &selectionPoints, QPainter *painter)
+{
+ static const QColor editPointColor(0, 110, 255);
+ static const QColor controlVertexColor(0, 110, 255);
+ static const QColor selectionPointColor(0, 255, 0);
+
+ double originX = controlPoint.coordinate().x();
+ double originY = controlPoint.coordinate().y();
+
+ if (controlPoint.isEditPoint()) {
+ if (selectionPoints.contains(controlPoint)) {
+ painter->setBrush(selectionPointColor);
+ painter->setPen(selectionPointColor);
+ } else {
+ painter->setBrush(editPointColor);
+ painter->setPen(editPointColor);
+ }
+ painter->setRenderHint(QPainter::Antialiasing, false);
+ painter->drawRect(controlPointShape.adjusted(originX -1, originY - 1, originX - 1, originY - 1));
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ } else {
+ if (selectionPoints.contains(controlPoint)) {
+ painter->setBrush(selectionPointColor);
+ painter->setPen(selectionPointColor);
+ } else {
+ painter->setBrush(controlVertexColor);
+ painter->setPen(controlVertexColor);
+ }
+ painter->drawEllipse(controlPointShape.adjusted(originX, originY, originX, originY));
+ }
+}
+
+static void drawControlPoints(const QList<ControlPoint> &controlPoints, const QList<ControlPoint> &selectionPoints, QPainter *painter)
+{
+ painter->save();
+
+ foreach (const ControlPoint &controlPoint, controlPoints)
+ drawControlPoint(controlPoint, selectionPoints, painter);
+
+ painter->restore();
+}
+
+static void drawPositionOverlay(const ControlPoint &controlPoint, QPainter *painter)
+{
+ QPoint position = controlPoint.coordinate().toPoint();
+ position.rx() += 3;
+ position.ry() -= 3;
+
+ QString postionText(QString(QLatin1String("x: %1 y: %2")).arg(controlPoint.coordinate().x()).arg(controlPoint.coordinate().y()));
+ painter->drawText(position, postionText);
+}
+
+static void drawPostionOverlays(const QList<SelectionPoint> &selectedPoints, QPainter *painter)
+{
+ painter->save();
+ QFont font = painter->font();
+ font.setPixelSize(Theme::instance()->smallFontPixelSize());
+ painter->setFont(font);
+ painter->setPen(QColor(0, 0, 0));
+
+ foreach (const SelectionPoint &selectedPoint, selectedPoints)
+ drawPositionOverlay(selectedPoint.controlPoint, painter);
+
+ painter->restore();
+}
+
+static void drawMultiSelectionRectangle(const QRectF &selectionRectangle, QPainter *painter)
+{
+ painter->save();
+ static QColor selectionBrush(painter->pen().color());
+ selectionBrush.setAlpha(50);
+ painter->setRenderHint(QPainter::Antialiasing, false);
+ painter->setBrush(selectionBrush);
+ painter->drawRect(selectionRectangle);
+ painter->restore();
+}
+
+void PathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
+{
+ painter->save();
+
+ painter->setRenderHint(QPainter::Antialiasing, true);
+
+ if (!m_cubicSegments.isEmpty()) {
+ drawCubicSegments(m_cubicSegments, painter);
+ drawControlLines(m_cubicSegments, painter);
+ drawControlPoints(controlPoints(), m_selectionManipulator.allControlPoints(), painter);
+ drawPostionOverlays(m_selectionManipulator.singleSelectedPoints(), painter);
+ if (m_selectionManipulator.isMultiSelecting())
+ drawMultiSelectionRectangle(m_selectionManipulator.multiSelectionRectangle(), painter);
+ }
+
+ painter->restore();
+}
+
+FormEditorItem *PathItem::formEditorItem() const
+{
+ return m_formEditorItem;
+}
+
+static CubicSegment createCubicSegmentForLine(const ModelNode &lineNode, const ControlPoint &startControlPoint)
+{
+ CubicSegment cubicSegment = CubicSegment::create();
+ cubicSegment.setModelNode(lineNode);
+
+ if (lineNode.hasProperty("x")
+ && lineNode.hasProperty("y")) {
+
+ QPointF controlPoint0Line = startControlPoint.coordinate();
+ QPointF controlPoint1Line(lineNode.variantProperty("x").value().toDouble(),
+ lineNode.variantProperty("y").value().toDouble());
+
+ QPointF controlPoint1Cubic = controlPoint0Line + (1./3.) * (controlPoint1Line - controlPoint0Line);
+ QPointF controlPoint2Cubic = controlPoint0Line + (2./3.) * (controlPoint1Line - controlPoint0Line);
+
+ cubicSegment.setFirstControlPoint(startControlPoint);
+ cubicSegment.setSecondControlPoint(controlPoint1Cubic);
+ cubicSegment.setThirdControlPoint(controlPoint2Cubic);
+ cubicSegment.setFourthControlPoint(controlPoint1Line);
+ } else {
+ qWarning() << "PathLine has not all entries!";
+ }
+
+ return cubicSegment;
+}
+
+static CubicSegment createCubicSegmentForQuad(const ModelNode &quadNode, const ControlPoint &startControlPoint)
+{
+ CubicSegment cubicSegment = CubicSegment::create();
+ cubicSegment.setModelNode(quadNode);
+
+ if (quadNode.hasProperty("controlX")
+ && quadNode.hasProperty("controlY")
+ && quadNode.hasProperty("x")
+ && quadNode.hasProperty("y")) {
+ QPointF controlPoint0Quad = startControlPoint.coordinate();
+ QPointF controlPoint1Quad(quadNode.variantProperty("controlX").value().toDouble(),
+ quadNode.variantProperty("controlY").value().toDouble());
+ QPointF controlPoint2Quad(quadNode.variantProperty("x").value().toDouble(),
+ quadNode.variantProperty("y").value().toDouble());
+
+ QPointF controlPoint1Cubic = controlPoint0Quad + (2./3.) * (controlPoint1Quad - controlPoint0Quad);
+ QPointF controlPoint2Cubic = controlPoint2Quad + (2./3.) * (controlPoint1Quad - controlPoint2Quad);
+
+ cubicSegment.setFirstControlPoint(startControlPoint);
+ cubicSegment.setSecondControlPoint(controlPoint1Cubic);
+ cubicSegment.setThirdControlPoint(controlPoint2Cubic);
+ cubicSegment.setFourthControlPoint(controlPoint2Quad);
+ } else {
+ qWarning() << "PathQuad has not all entries!";
+ }
+
+ return cubicSegment;
+}
+
+static CubicSegment createCubicSegmentForCubic(const ModelNode &cubicNode, const ControlPoint &startControlPoint)
+{
+ CubicSegment cubicSegment = CubicSegment::create();
+ cubicSegment.setModelNode(cubicNode);
+
+ if (cubicNode.hasProperty("control1X")
+ && cubicNode.hasProperty("control1Y")
+ && cubicNode.hasProperty("control2X")
+ && cubicNode.hasProperty("control2Y")
+ && cubicNode.hasProperty("x")
+ && cubicNode.hasProperty("y")) {
+
+ cubicSegment.setFirstControlPoint(startControlPoint);
+ cubicSegment.setSecondControlPoint(cubicNode.variantProperty("control1X").value().toDouble(),
+ cubicNode.variantProperty("control1Y").value().toDouble());
+ cubicSegment.setThirdControlPoint(cubicNode.variantProperty("control2X").value().toDouble(),
+ cubicNode.variantProperty("control2Y").value().toDouble());
+ cubicSegment.setFourthControlPoint(cubicNode.variantProperty("x").value().toDouble(),
+ cubicNode.variantProperty("y").value().toDouble());
+ } else {
+ qWarning() << "PathCubic has not all entries!";
+ }
+
+ return cubicSegment;
+}
+
+static QRectF boundingRectForPath(const QList<ControlPoint> &controlPoints)
+{
+ double xMinimum = 0.;
+ double xMaximum = 0.;
+ double yMinimum = 0.;
+ double yMaximum = 0.;
+
+ foreach (const ControlPoint & controlPoint, controlPoints) {
+ xMinimum = qMin(xMinimum, controlPoint.coordinate().x());
+ xMaximum = qMax(xMaximum, controlPoint.coordinate().x());
+ yMinimum = qMin(yMinimum, controlPoint.coordinate().y());
+ yMaximum = qMax(yMaximum, controlPoint.coordinate().y());
+ }
+
+ return QRect(xMinimum, yMinimum, xMaximum - xMinimum, yMaximum - yMinimum);
+}
+
+void PathItem::updateBoundingRect()
+{
+ QRectF controlBoundingRect = boundingRectForPath(controlPoints()).adjusted(-100, -100, 200, 100);
+
+ if (m_selectionManipulator.isMultiSelecting())
+ controlBoundingRect = controlBoundingRect.united(m_selectionManipulator.multiSelectionRectangle());
+
+ setBoundingRect(instanceBoundingRect().united(controlBoundingRect));
+}
+
+QRectF PathItem::instanceBoundingRect() const
+{
+ if (formEditorItem())
+ return formEditorItem()->qmlItemNode().instanceBoundingRect();
+
+ return {};
+}
+
+void PathItem::readControlPoints()
+{
+ ModelNode pathNode = pathModelNode(formEditorItem());
+
+ m_cubicSegments.clear();
+
+ if (pathNode.hasNodeListProperty("pathElements")) {
+ ControlPoint firstControlPoint(startPoint(pathNode));
+ firstControlPoint.setPathModelNode(pathNode);
+ firstControlPoint.setPointType(StartPoint);
+
+ QMap<QString, QVariant> actualAttributes;
+ double percent = -1.0;
+
+ foreach (const ModelNode &childNode, pathNode.nodeListProperty("pathElements").toModelNodeList()) {
+
+ if (childNode.type() == "QtQuick.PathAttribute") {
+ actualAttributes.insert(childNode.variantProperty("name").value().toString(), childNode.variantProperty("value").value());
+ } else if (childNode.type() == "QtQuick.PathPercent") {
+ percent = childNode.variantProperty("value").value().toDouble();
+ } else {
+ CubicSegment newCubicSegement;
+
+ if (childNode.type() == "QtQuick.PathLine")
+ newCubicSegement = createCubicSegmentForLine(childNode, firstControlPoint);
+ else if (childNode.type() == "QtQuick.PathQuad")
+ newCubicSegement = createCubicSegmentForQuad(childNode, firstControlPoint);
+ else if (childNode.type() == "QtQuick.PathCubic")
+ newCubicSegement = createCubicSegmentForCubic(childNode, firstControlPoint);
+ else
+ continue;
+
+ newCubicSegement.setPercent(percent);
+ newCubicSegement.setAttributes(actualAttributes);
+
+ firstControlPoint = newCubicSegement.fourthControlPoint();
+ qDebug() << "Can be converted to quad" << newCubicSegement.canBeConvertedToQuad();
+ qDebug() << "Can be converted to line" << newCubicSegement.canBeConvertedToLine();
+ m_cubicSegments.append(newCubicSegement);
+ actualAttributes.clear();
+ percent = -1.0;
+ }
+ }
+
+ m_lastAttributes = actualAttributes;
+ m_lastPercent = percent;
+
+ if (m_cubicSegments.constFirst().firstControlPoint().coordinate() == m_cubicSegments.constLast().fourthControlPoint().coordinate()) {
+ CubicSegment lastCubicSegment = m_cubicSegments.constLast();
+ lastCubicSegment.setFourthControlPoint(m_cubicSegments.constFirst().firstControlPoint());
+ lastCubicSegment.fourthControlPoint().setPathModelNode(pathNode);
+ lastCubicSegment.fourthControlPoint().setPointType(StartAndEndPoint);
+ }
+ }
+}
+
+static CubicSegment getMinimumDistanceSegment(const QPointF &pickPoint, const QList<CubicSegment> &cubicSegments, double maximumDistance, double *t = nullptr)
+{
+ CubicSegment minimumDistanceSegment;
+ double actualMinimumDistance = maximumDistance;
+
+ foreach (const CubicSegment &cubicSegment, cubicSegments) {
+ double tSegment = 0.;
+ double cubicSegmentMinimumDistance = cubicSegment.minimumDistance(pickPoint, tSegment);
+ if (cubicSegmentMinimumDistance < actualMinimumDistance) {
+ minimumDistanceSegment = cubicSegment;
+ actualMinimumDistance = cubicSegmentMinimumDistance;
+ if (t)
+ *t = tSegment;
+ }
+ }
+
+ return minimumDistanceSegment;
+}
+
+void PathItem::splitCubicSegment(CubicSegment &cubicSegment, double t)
+{
+ QPair<CubicSegment, CubicSegment> newCubicSegmentPair = cubicSegment.split(t);
+ int indexOfOldCubicSegment = m_cubicSegments.indexOf(cubicSegment);
+
+ m_cubicSegments.removeAt(indexOfOldCubicSegment);
+ m_cubicSegments.insert(indexOfOldCubicSegment, newCubicSegmentPair.first);
+ m_cubicSegments.insert(indexOfOldCubicSegment + 1, newCubicSegmentPair.second);
+}
+
+void PathItem::closePath()
+{
+ if (!m_cubicSegments.isEmpty()) {
+ const CubicSegment &firstCubicSegment = m_cubicSegments.constFirst();
+ CubicSegment lastCubicSegment = m_cubicSegments.constLast();
+ lastCubicSegment.setFourthControlPoint(firstCubicSegment.firstControlPoint());
+ writePathAsCubicSegmentsOnly();
+ }
+}
+
+void PathItem::openPath()
+{
+ if (!m_cubicSegments.isEmpty()) {
+ const CubicSegment &firstCubicSegment = m_cubicSegments.constFirst();
+ CubicSegment lastCubicSegment = m_cubicSegments.constLast();
+ QPointF newEndPoint = firstCubicSegment.firstControlPoint().coordinate();
+ newEndPoint.setX(newEndPoint.x() + 10.);
+ lastCubicSegment.setFourthControlPoint(ControlPoint(newEndPoint));
+ writePathAsCubicSegmentsOnly();
+ }
+}
+
+QAction *PathItem::createClosedPathAction(QMenu *contextMenu) const
+{
+ auto closedPathAction = new QAction(contextMenu);
+ closedPathAction->setCheckable(true);
+ closedPathAction->setChecked(isClosedPath());
+ closedPathAction->setText(tr("Closed Path"));
+ contextMenu->addAction(closedPathAction);
+
+ if (m_cubicSegments.count() == 1)
+ closedPathAction->setDisabled(true);
+
+ return closedPathAction;
+}
+
+void PathItem::createGlobalContextMenu(const QPoint &menuPosition)
+{
+ QMenu contextMenu;
+
+ QAction *closedPathAction = createClosedPathAction(&contextMenu);
+
+ QAction *activatedAction = contextMenu.exec(menuPosition);
+
+ if (activatedAction == closedPathAction)
+ makePathClosed(closedPathAction->isChecked());
+}
+
+void PathItem::createCubicSegmentContextMenu(CubicSegment &cubicSegment, const QPoint &menuPosition, double t)
+{
+ QMenu contextMenu;
+
+ auto splitSegmentAction = new QAction(&contextMenu);
+ splitSegmentAction->setText(tr("Split Segment"));
+ contextMenu.addAction(splitSegmentAction);
+
+ auto straightLinePointAction = new QAction(&contextMenu);
+ straightLinePointAction->setText(tr("Make Curve Segment Straight"));
+ contextMenu.addAction(straightLinePointAction);
+
+ if (m_cubicSegments.count() == 1 && isClosedPath())
+ straightLinePointAction->setDisabled(true);
+
+ QAction *closedPathAction = createClosedPathAction(&contextMenu);
+
+ QAction *activatedAction = contextMenu.exec(menuPosition);
+
+ if (activatedAction == straightLinePointAction) {
+ cubicSegment.makeStraightLine();
+ PathUpdateDisabler pathItemDisabler(this, PathUpdateDisabler::DontUpdatePath);
+ RewriterTransaction rewriterTransaction =
+ cubicSegment.modelNode().view()->beginRewriterTransaction(QByteArrayLiteral("PathItem::createCubicSegmentContextMenu"));
+ cubicSegment.updateModelNode();
+ rewriterTransaction.commit();
+ } else if (activatedAction == splitSegmentAction) {
+ splitCubicSegment(cubicSegment, t);
+ writePathAsCubicSegmentsOnly();
+ } else if (activatedAction == closedPathAction) {
+ makePathClosed(closedPathAction->isChecked());
+ }
+}
+
+
+void PathItem::createEditPointContextMenu(const ControlPoint &controlPoint, const QPoint &menuPosition)
+{
+ QMenu contextMenu;
+ auto removeEditPointAction = new QAction(&contextMenu);
+ removeEditPointAction->setText(tr("Remove Edit Point"));
+ contextMenu.addAction(removeEditPointAction);
+
+ QAction *closedPathAction = createClosedPathAction(&contextMenu);
+
+ if (m_cubicSegments.count() <= 1 || (m_cubicSegments.count() == 2 && isClosedPath()))
+ removeEditPointAction->setDisabled(true);
+
+ QAction *activatedAction = contextMenu.exec(menuPosition);
+
+ if (activatedAction == removeEditPointAction)
+ removeEditPoint(controlPoint);
+ else if (activatedAction == closedPathAction)
+ makePathClosed(closedPathAction->isChecked());
+}
+
+const QList<ControlPoint> PathItem::controlPoints() const
+{
+ QList<ControlPoint> controlPointList;
+ controlPointList.reserve((m_cubicSegments.count() * 4));
+
+ if (!m_cubicSegments.isEmpty())
+ controlPointList.append(m_cubicSegments.constFirst().firstControlPoint());
+
+ foreach (const CubicSegment &cubicSegment, m_cubicSegments) {
+ controlPointList.append(cubicSegment.secondControlPoint());
+ controlPointList.append(cubicSegment.thirdControlPoint());
+ controlPointList.append(cubicSegment.fourthControlPoint());
+ }
+
+ if (isClosedPath())
+ controlPointList.pop_back();
+
+ return controlPointList;
+}
+
+bool hasLineOrQuadPathElements(const QList<ModelNode> &modelNodes)
+{
+ foreach (const ModelNode &modelNode, modelNodes) {
+ if (modelNode.type() == "QtQuick.PathLine"
+ || modelNode.type() == "QtQuick.PathQuad")
+ return true;
+ }
+
+ return false;
+}
+
+void PathItem::updatePath()
+{
+ if (m_dontUpdatePath)
+ return;
+
+ if (hasPath(formEditorItem())) {
+ readControlPoints();
+
+ ModelNode pathNode = pathModelNode(formEditorItem());
+
+ if (hasLineOrQuadPathElements(pathNode.nodeListProperty("pathElements").toModelNodeList()))
+ writePathAsCubicSegmentsOnly();
+ }
+
+ updateBoundingRect();
+ update();
+}
+
+QRectF PathItem::boundingRect() const
+{
+ return m_boundingRect;
+}
+
+void PathItem::setBoundingRect(const QRectF &boundingRect)
+{
+ m_boundingRect = boundingRect;
+}
+
+static bool controlPointIsNearMousePosition(const ControlPoint &controlPoint, const QPointF &mousePosition)
+{
+ QPointF distanceVector = controlPoint.coordinate() - mousePosition;
+
+ if (distanceVector.manhattanLength() < 10)
+ return true;
+
+ return false;
+}
+
+static bool controlPointsAreNearMousePosition(const QList<ControlPoint> &controlPoints, const QPointF &mousePosition)
+{
+ foreach (const ControlPoint &controlPoint, controlPoints) {
+ if (controlPointIsNearMousePosition(controlPoint, mousePosition))
+ return true;
+ }
+
+ return false;
+}
+
+static ControlPoint pickControlPoint(const QList<ControlPoint> &controlPoints, const QPointF &mousePosition)
+{
+ foreach (const ControlPoint &controlPoint, controlPoints) {
+ if (controlPointIsNearMousePosition(controlPoint, mousePosition))
+ return controlPoint;
+ }
+
+ return ControlPoint();
+}
+
+void PathItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton) {
+ if (m_selectionManipulator.hasMultiSelection()) {
+ m_selectionManipulator.setStartPoint(event->pos());
+ } else {
+ ControlPoint pickedControlPoint = pickControlPoint(controlPoints(), event->pos());
+
+ if (pickedControlPoint.isValid()) {
+ m_selectionManipulator.addSingleControlPointSmartly(pickedControlPoint);
+ m_selectionManipulator.startMoving(event->pos());
+ } else {
+ m_selectionManipulator.startMultiSelection(event->pos());
+ }
+ }
+ }
+}
+
+bool hasMoveStartDistance(const QPointF &startPoint, const QPointF &updatePoint)
+{
+ return (startPoint - updatePoint).manhattanLength() > 10;
+}
+
+void PathItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (controlPointsAreNearMousePosition(controlPoints(), event->pos()))
+ setCursor(Qt::SizeAllCursor);
+ else
+ setCursor(Qt::ArrowCursor);
+
+ PathUpdateDisabler pathUpdateDisabler(this, PathUpdateDisabler::DontUpdatePath);
+ if (event->buttons().testFlag(Qt::LeftButton)) {
+ if (m_selectionManipulator.isMultiSelecting()) {
+ m_selectionManipulator.updateMultiSelection(event->pos());
+ update();
+ } else if (m_selectionManipulator.hasSingleSelection()) {
+ setCursor(Qt::SizeAllCursor);
+ m_selectionManipulator.updateMoving(event->pos(), event->modifiers());
+ updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints());
+ updateBoundingRect();
+ update();
+ } else if (m_selectionManipulator.hasMultiSelection()) {
+ setCursor(Qt::SizeAllCursor);
+ if (m_selectionManipulator.isMoving()) {
+ m_selectionManipulator.updateMoving(event->pos(), event->modifiers());
+ updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints());
+ updateBoundingRect();
+ update();
+ } else if (hasMoveStartDistance(m_selectionManipulator.startPoint(), event->pos())) {
+ m_selectionManipulator.startMoving(m_selectionManipulator.startPoint());
+ m_selectionManipulator.updateMoving(event->pos(), event->modifiers());
+ updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints());
+ updateBoundingRect();
+ update();
+ }
+ }
+ }
+}
+
+void PathItem::updatePathModelNodes(const QList<SelectionPoint> &changedPoints)
+{
+ PathUpdateDisabler pathUpdateDisabler(this, PathUpdateDisabler::DontUpdatePath);
+
+ try {
+ RewriterTransaction rewriterTransaction =
+ formEditorItem()->qmlItemNode().view()->beginRewriterTransaction(QByteArrayLiteral("PathItem::createCubicSegmentContextMenu"));
+
+ foreach (SelectionPoint changedPoint, changedPoints)
+ changedPoint.controlPoint.updateModelNode();
+
+ rewriterTransaction.commit();
+ } catch (const Exception &e) {
+ e.showException();
+ }
+}
+
+void PathItem::disablePathUpdates()
+{
+ m_dontUpdatePath = true;
+}
+
+void PathItem::enablePathUpdates()
+{
+ m_dontUpdatePath = false;
+}
+
+bool PathItem::pathUpdatesDisabled() const
+{
+ return m_dontUpdatePath;
+}
+
+void PathItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton) {
+ if (m_selectionManipulator.isMultiSelecting()) {
+ m_selectionManipulator.updateMultiSelection(event->pos());
+ m_selectionManipulator.endMultiSelection();
+ } else if (m_selectionManipulator.hasSingleSelection()) {
+ m_selectionManipulator.updateMoving(event->pos(), event->modifiers());
+ updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints());
+ updateBoundingRect();
+ m_selectionManipulator.clearSingleSelection();
+ } else if (m_selectionManipulator.hasMultiSelection()) {
+ if (m_selectionManipulator.isMoving()) {
+ m_selectionManipulator.updateMoving(event->pos(), event->modifiers());
+ m_selectionManipulator.endMoving();
+ updatePathModelNodes(m_selectionManipulator.multiSelectedPoints());
+ updateBoundingRect();
+ } else {
+ m_selectionManipulator.clearMultiSelection();
+ }
+ }
+ } else if (event->button() == Qt::RightButton) {
+ ControlPoint pickedControlPoint = pickControlPoint(controlPoints(), event->pos());
+ if (pickedControlPoint.isEditPoint()) {
+ createEditPointContextMenu(pickedControlPoint, event->screenPos());
+ } else {
+ double t = 0.0;
+ CubicSegment minimumDistanceSegment = getMinimumDistanceSegment(event->pos(), m_cubicSegments, 20., &t);
+ if (minimumDistanceSegment.isValid())
+ createCubicSegmentContextMenu(minimumDistanceSegment, event->screenPos(), t);
+ else
+ createGlobalContextMenu(event->screenPos());
+ }
+ }
+
+ update();
+
+}
+
+bool PathItem::isClosedPath() const
+{
+ if (m_cubicSegments.isEmpty())
+ return false;
+
+ ControlPoint firstControlPoint = m_cubicSegments.constFirst().firstControlPoint();
+ ControlPoint lastControlPoint = m_cubicSegments.constLast().fourthControlPoint();
+
+ return firstControlPoint == lastControlPoint;
+}
+
+void PathItem::makePathClosed(bool pathShoudlBeClosed)
+{
+ if (pathShoudlBeClosed && !isClosedPath())
+ closePath();
+ else if (!pathShoudlBeClosed && isClosedPath())
+ openPath();
+}
+
+QList<CubicSegment> cubicSegmentsContainingControlPoint(const ControlPoint &controlPoint, const QList<CubicSegment> &allCubicSegments)
+{
+ QList<CubicSegment> cubicSegmentsHasControlPoint;
+
+ foreach (const CubicSegment &cubicSegment, allCubicSegments) {
+ if (cubicSegment.controlPoints().contains(controlPoint))
+ cubicSegmentsHasControlPoint.append(cubicSegment);
+ }
+
+ return cubicSegmentsHasControlPoint;
+}
+
+void PathItem::removeEditPoint(const ControlPoint &controlPoint)
+{
+ QList<CubicSegment> cubicSegments = cubicSegmentsContainingControlPoint(controlPoint, m_cubicSegments);
+
+ if (cubicSegments.count() == 1) {
+ m_cubicSegments.removeOne(cubicSegments.constFirst());
+ } else if (cubicSegments.count() == 2){
+ CubicSegment mergedCubicSegment = CubicSegment::create();
+ const CubicSegment &firstCubicSegment = cubicSegments.at(0);
+ const CubicSegment &secondCubicSegment = cubicSegments.at(1);
+ mergedCubicSegment.setFirstControlPoint(firstCubicSegment.firstControlPoint());
+ mergedCubicSegment.setSecondControlPoint(firstCubicSegment.secondControlPoint());
+ mergedCubicSegment.setThirdControlPoint(secondCubicSegment.thirdControlPoint());
+ mergedCubicSegment.setFourthControlPoint(secondCubicSegment.fourthControlPoint());
+
+ int indexOfFirstCubicSegment = m_cubicSegments.indexOf(firstCubicSegment);
+ m_cubicSegments.removeAt(indexOfFirstCubicSegment);
+ m_cubicSegments.removeAt(indexOfFirstCubicSegment);
+ m_cubicSegments.insert(indexOfFirstCubicSegment, mergedCubicSegment);
+ }
+
+ writePathAsCubicSegmentsOnly();
+}
+
+PathUpdateDisabler::PathUpdateDisabler(PathItem *pathItem, PathUpdate updatePath)
+ : m_pathItem(pathItem),
+ m_updatePath(updatePath)
+{
+ pathItem->disablePathUpdates();
+}
+
+PathUpdateDisabler::~PathUpdateDisabler()
+{
+ m_pathItem->enablePathUpdates();
+ if (m_updatePath == UpdatePath)
+ m_pathItem->updatePath();
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/pathtool/pathitem.h b/src/plugins/qmldesigner/components/pathtool/pathitem.h
new file mode 100644
index 0000000000..17981283a6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathitem.h
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QGraphicsObject>
+#include <QWeakPointer>
+#include <QMap>
+#include <QVariant>
+#include <qmldesignercorelib_global.h>
+
+#include "cubicsegment.h"
+#include "pathselectionmanipulator.h"
+
+QT_BEGIN_NAMESPACE
+class QTextEdit;
+class QAction;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class FormEditorScene;
+class FormEditorItem;
+class PathItem;
+
+class PathUpdateDisabler
+{
+public:
+ enum PathUpdate
+ {
+ UpdatePath,
+ DontUpdatePath
+ };
+
+ PathUpdateDisabler(PathItem *pathItem, PathUpdate updatePath = UpdatePath);
+ ~PathUpdateDisabler();
+
+private:
+ PathItem *m_pathItem;
+ PathUpdate m_updatePath;
+};
+
+class PathItem : public QGraphicsObject
+{
+ Q_OBJECT
+ friend class PathUpdateDisabler;
+public:
+ enum
+ {
+ Type = 0xEAAC
+ };
+ PathItem(FormEditorScene* scene);
+ ~PathItem() override;
+ int type() const override;
+
+ void setFormEditorItem(FormEditorItem *formEditorItem);
+ FormEditorItem *formEditorItem() const;
+
+ QList<QGraphicsItem*> findAllChildItems() const;
+
+ void updatePath();
+ void writePathToProperty();
+ void writePathAsCubicSegmentsOnly();
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ QRectF boundingRect() const override;
+ void setBoundingRect(const QRectF &boundingRect);
+
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+
+ bool isClosedPath() const;
+ const QList<ControlPoint> controlPoints() const;
+
+protected:
+ void updateBoundingRect();
+ QRectF instanceBoundingRect() const;
+ void writeLinePath(const ModelNode &pathNode, const CubicSegment &cubicSegment);
+ void writeQuadPath(const ModelNode &pathNode, const CubicSegment &cubicSegment);
+ void writeCubicPath(const ModelNode &pathNode, const CubicSegment &cubicSegment);
+ void writePathAttributes(const ModelNode &pathNode, const QMap<QString, QVariant> &attributes);
+ void writePathPercent(const ModelNode &pathNode, double percent);
+ void readControlPoints();
+ void splitCubicSegment(CubicSegment &cubicSegment, double t);
+ void closePath();
+ void openPath();
+ void createGlobalContextMenu(const QPoint &menuPosition);
+ void createCubicSegmentContextMenu(CubicSegment &cubicSegment, const QPoint &menuPosition, double t);
+ void createEditPointContextMenu(const ControlPoint &controlPoint, const QPoint &menuPosition);
+ void makePathClosed(bool pathShoudlBeClosed);
+ void removeEditPoint(const ControlPoint &controlPoint);
+ void updatePathModelNodes(const QList<SelectionPoint> &changedPoints);
+ void disablePathUpdates();
+ void enablePathUpdates();
+ bool pathUpdatesDisabled() const;
+ QAction *createClosedPathAction(QMenu *contextMenu) const;
+
+signals:
+ void textChanged(const QString &text);
+
+private:
+ PathSelectionManipulator m_selectionManipulator;
+ QList<CubicSegment> m_cubicSegments;
+ QPointF m_startPoint;
+ QRectF m_boundingRect;
+ QMap<QString, QVariant> m_lastAttributes;
+ double m_lastPercent;
+ FormEditorItem *m_formEditorItem;
+ bool m_dontUpdatePath;
+};
+
+inline int PathItem::type() const
+{
+ return Type;
+}
+}
diff --git a/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp
new file mode 100644
index 0000000000..6df6976233
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp
@@ -0,0 +1,299 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pathselectionmanipulator.h"
+
+#include "pathitem.h"
+
+#include <QtDebug>
+
+namespace QmlDesigner {
+
+PathSelectionManipulator::PathSelectionManipulator(PathItem *pathItem)
+ : m_pathItem(pathItem),
+ m_isMultiSelecting(false),
+ m_isMoving(false)
+{
+}
+
+void PathSelectionManipulator::clear()
+{
+ clearSingleSelection();
+ clearMultiSelection();
+ m_isMultiSelecting = false;
+ m_isMoving = false;
+}
+
+void PathSelectionManipulator::clearSingleSelection()
+{
+ m_singleSelectedPoints.clear();
+ m_automaticallyAddedSinglePoints.clear();
+}
+
+void PathSelectionManipulator::clearMultiSelection()
+{
+ m_multiSelectedPoints.clear();
+}
+
+static SelectionPoint createSelectionPoint(const ControlPoint &controlPoint)
+{
+ SelectionPoint selectionPoint;
+ selectionPoint.controlPoint = controlPoint;
+ selectionPoint.startPosition = controlPoint.coordinate();
+
+ return selectionPoint;
+}
+
+void PathSelectionManipulator::addMultiSelectionControlPoint(const ControlPoint &controlPoint)
+{
+ m_multiSelectedPoints.append(createSelectionPoint(controlPoint));
+}
+
+static ControlPoint getControlPoint(const QList<ControlPoint> &selectedPoints, const ControlPoint &controlPoint, int indexOffset, bool isClosedPath)
+{
+ int controlPointIndex = selectedPoints.indexOf(controlPoint);
+ if (controlPointIndex >= 0) {
+ int offsetIndex = controlPointIndex + indexOffset;
+ if (offsetIndex >= 0 && offsetIndex < selectedPoints.count())
+ return selectedPoints.at(offsetIndex);
+ else if (isClosedPath) {
+ if (offsetIndex == -1)
+ return selectedPoints.constLast();
+ else if (offsetIndex < selectedPoints.count())
+ return selectedPoints.at(1);
+ }
+ }
+
+ return ControlPoint();
+}
+
+QList<SelectionPoint> PathSelectionManipulator::singleSelectedPoints()
+{
+ return m_singleSelectedPoints;
+}
+
+QList<SelectionPoint> PathSelectionManipulator::automaticallyAddedSinglePoints()
+{
+ return m_automaticallyAddedSinglePoints;
+}
+
+QList<SelectionPoint> PathSelectionManipulator::allSelectionSinglePoints()
+{
+
+ return m_singleSelectedPoints + m_automaticallyAddedSinglePoints;
+}
+
+QList<SelectionPoint> PathSelectionManipulator::multiSelectedPoints()
+{
+ return m_multiSelectedPoints;
+}
+
+QList<SelectionPoint> PathSelectionManipulator::allSelectionPoints()
+{
+ return m_singleSelectedPoints + m_multiSelectedPoints + m_automaticallyAddedSinglePoints;
+}
+
+QList<ControlPoint> PathSelectionManipulator::allControlPoints()
+{
+ QList<ControlPoint> controlPoints;
+
+ foreach (const SelectionPoint &selectionPoint, m_singleSelectedPoints)
+ controlPoints.append(selectionPoint.controlPoint);
+
+ foreach (const SelectionPoint &selectionPoint, m_automaticallyAddedSinglePoints)
+ controlPoints.append(selectionPoint.controlPoint);
+
+ foreach (const SelectionPoint &selectionPoint, m_multiSelectedPoints)
+ controlPoints.append(selectionPoint.controlPoint);
+
+ return controlPoints;
+}
+
+bool PathSelectionManipulator::hasSingleSelection() const
+{
+ return !m_singleSelectedPoints.isEmpty();
+}
+
+bool PathSelectionManipulator::hasMultiSelection() const
+{
+ return !m_multiSelectedPoints.isEmpty();
+}
+
+void PathSelectionManipulator::startMultiSelection(const QPointF &startPoint)
+{
+ m_startPoint = startPoint;
+ m_isMultiSelecting = true;
+}
+
+void PathSelectionManipulator::updateMultiSelection(const QPointF &updatePoint)
+{
+ clearMultiSelection();
+
+ m_updatePoint = updatePoint;
+
+ QRectF selectionRect(m_startPoint, updatePoint);
+
+ foreach (const ControlPoint &controlPoint, m_pathItem->controlPoints()) {
+ if (selectionRect.contains(controlPoint.coordinate()))
+ addMultiSelectionControlPoint(controlPoint);
+ }
+}
+
+void PathSelectionManipulator::endMultiSelection()
+{
+ m_isMultiSelecting = false;
+}
+
+SelectionPoint::SelectionPoint() = default;
+
+SelectionPoint::SelectionPoint(const ControlPoint &controlPoint)
+ : controlPoint(controlPoint)
+{
+}
+
+bool operator ==(const SelectionPoint &firstSelectionPoint, const SelectionPoint &secondSelectionPoint)
+{
+ return firstSelectionPoint.controlPoint == secondSelectionPoint.controlPoint;
+}
+
+QPointF PathSelectionManipulator::multiSelectionStartPoint() const
+{
+ return m_startPoint;
+}
+
+bool PathSelectionManipulator::isMultiSelecting() const
+{
+ return m_isMultiSelecting;
+}
+
+QRectF PathSelectionManipulator::multiSelectionRectangle() const
+{
+ return QRectF(m_startPoint, m_updatePoint);
+}
+
+void PathSelectionManipulator::setStartPoint(const QPointF &startPoint)
+{
+ m_startPoint = startPoint;
+}
+
+QPointF PathSelectionManipulator::startPoint() const
+{
+ return m_startPoint;
+}
+
+double snapFactor(Qt::KeyboardModifiers keyboardModifier)
+{
+ if (keyboardModifier.testFlag(Qt::ControlModifier))
+ return 10.;
+
+ return 1.;
+}
+
+QPointF roundedVector(const QPointF &vector, double factor = 1.)
+{
+ QPointF roundedPosition;
+
+ roundedPosition.setX(qRound(vector.x() / factor) * factor);
+ roundedPosition.setY(qRound(vector.y() / factor) * factor);
+
+ return roundedPosition;
+}
+
+QPointF manipulatedVector(const QPointF &vector, Qt::KeyboardModifiers keyboardModifier)
+{
+ QPointF manipulatedVector = roundedVector(vector, snapFactor(keyboardModifier));
+
+ if (keyboardModifier.testFlag(Qt::ShiftModifier))
+ manipulatedVector.setX(0.);
+
+ if (keyboardModifier.testFlag(Qt::AltModifier))
+ manipulatedVector.setY(0.);
+
+ return manipulatedVector;
+}
+
+static void moveControlPoints(const QList<SelectionPoint> &movePoints, const QPointF &offsetVector)
+{
+ foreach (SelectionPoint movePoint, movePoints)
+ movePoint.controlPoint.setCoordinate(movePoint.startPosition + offsetVector);
+}
+
+void PathSelectionManipulator::startMoving(const QPointF &startPoint)
+{
+ m_isMoving = true;
+ m_startPoint = startPoint;
+}
+
+void PathSelectionManipulator::updateMoving(const QPointF &updatePoint, Qt::KeyboardModifiers keyboardModifier)
+{
+ m_updatePoint = updatePoint;
+ QPointF offsetVector = manipulatedVector(updatePoint - m_startPoint, keyboardModifier) ;
+ moveControlPoints(allSelectionPoints(), offsetVector);
+}
+
+void PathSelectionManipulator::endMoving()
+{
+ updateMultiSelectedStartPoint();
+ m_isMoving = false;
+}
+
+bool PathSelectionManipulator::isMoving() const
+{
+ return m_isMoving;
+}
+
+void PathSelectionManipulator::updateMultiSelectedStartPoint()
+{
+ QList<SelectionPoint> oldSelectionPoints = m_multiSelectedPoints;
+
+ m_multiSelectedPoints.clear();
+
+ foreach (SelectionPoint selectionPoint, oldSelectionPoints) {
+ selectionPoint.startPosition = selectionPoint.controlPoint.coordinate();
+ m_multiSelectedPoints.append(selectionPoint);
+ }
+}
+
+void PathSelectionManipulator::addSingleControlPoint(const ControlPoint &controlPoint)
+{
+ m_singleSelectedPoints.append(createSelectionPoint(controlPoint));
+}
+
+void PathSelectionManipulator::addSingleControlPointSmartly(const ControlPoint &editPoint)
+{
+ m_singleSelectedPoints.append(createSelectionPoint(editPoint));
+
+ if (editPoint.isEditPoint()) {
+ ControlPoint previousControlPoint = getControlPoint(m_pathItem->controlPoints(), editPoint, -1, m_pathItem->isClosedPath());
+ if (previousControlPoint.isValid())
+ m_automaticallyAddedSinglePoints.append(createSelectionPoint(previousControlPoint));
+
+ ControlPoint nextControlPoint= getControlPoint(m_pathItem->controlPoints(), editPoint, 1, m_pathItem->isClosedPath());
+ if (nextControlPoint.isValid())
+ m_automaticallyAddedSinglePoints.append(createSelectionPoint(nextControlPoint));
+ }
+}
+
+} // namespace QMlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h
new file mode 100644
index 0000000000..def0148367
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QList>
+#include <QPair>
+#include <QPoint>
+
+#include "controlpoint.h"
+
+namespace QmlDesigner {
+
+class PathItem;
+
+struct SelectionPoint
+{
+ SelectionPoint();
+ SelectionPoint(const ControlPoint &controlPoint);
+ ControlPoint controlPoint;
+ QPointF startPosition;
+};
+
+class PathSelectionManipulator
+{
+public:
+ PathSelectionManipulator(PathItem *pathItem);
+
+ void clear();
+ void clearSingleSelection();
+ void clearMultiSelection();
+
+ void addMultiSelectionControlPoint(const ControlPoint &controlPoint);
+ void addSingleControlPoint(const ControlPoint &controlPoint);
+ void addSingleControlPointSmartly(const ControlPoint &editPoint);
+
+ QList<SelectionPoint> singleSelectedPoints();
+ QList<SelectionPoint> automaticallyAddedSinglePoints();
+ QList<SelectionPoint> allSelectionSinglePoints();
+ QList<SelectionPoint> multiSelectedPoints();
+ QList<SelectionPoint> allSelectionPoints();
+
+ QList<ControlPoint> allControlPoints();
+
+ bool hasSingleSelection() const;
+ bool hasMultiSelection() const;
+
+ void startMultiSelection(const QPointF &startPoint);
+ void updateMultiSelection(const QPointF &updatePoint);
+ void endMultiSelection();
+ QPointF multiSelectionStartPoint() const;
+ bool isMultiSelecting() const;
+
+ QRectF multiSelectionRectangle() const;
+
+ void setStartPoint(const QPointF &startPoint);
+ QPointF startPoint() const;
+ void startMoving(const QPointF &startPoint);
+ void updateMoving(const QPointF &updatePoint, Qt::KeyboardModifiers keyboardModifier);
+ void endMoving();
+ bool isMoving() const;
+
+ void updateMultiSelectedStartPoint();
+
+private:
+ QList<SelectionPoint> m_singleSelectedPoints;
+ QList<SelectionPoint> m_automaticallyAddedSinglePoints;
+ QList<SelectionPoint> m_multiSelectedPoints;
+ QPointF m_startPoint;
+ QPointF m_updatePoint;
+ PathItem *m_pathItem;
+ bool m_isMultiSelecting;
+ bool m_isMoving;
+};
+
+bool operator ==(const SelectionPoint& firstSelectionPoint, const SelectionPoint& secondSelectionPoint);
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.cpp b/src/plugins/qmldesigner/components/pathtool/pathtool.cpp
new file mode 100644
index 0000000000..a01ae050ac
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathtool.cpp
@@ -0,0 +1,315 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pathtool.h"
+
+#include <formeditorscene.h>
+#include <formeditorview.h>
+#include <formeditorwidget.h>
+#include <itemutilfunctions.h>
+#include <formeditoritem.h>
+
+#include "pathitem.h"
+
+#include <nodemetainfo.h>
+#include <qmlitemnode.h>
+#include <nodeproperty.h>
+#include <nodelistproperty.h>
+#include <qmldesignerplugin.h>
+
+#include <abstractaction.h>
+#include <designeractionmanager.h>
+
+#include <QApplication>
+#include <QGraphicsSceneMouseEvent>
+#include <QAction>
+#include <QDebug>
+#include <QPair>
+
+namespace QmlDesigner {
+
+static bool isNonSupportedPathElement(const ModelNode &pathElement)
+{
+ if (pathElement.type() == "QtQuick.PathCubic")
+ return false;
+
+ if (pathElement.type() == "QtQuick.PathAttribute")
+ return false;
+
+ if (pathElement.type() == "QtQuick.PathPercent")
+ return false;
+
+ if (pathElement.type() == "QtQuick.PathAttribute")
+ return false;
+
+ if (pathElement.type() == "QtQuick.PathQuad")
+ return false;
+
+ if (pathElement.type() == "QtQuick.PathLine")
+ return false;
+
+ return true;
+}
+
+
+static int pathRankForModelNode(const ModelNode &modelNode) {
+ if (modelNode.metaInfo().hasProperty("path")) {
+ if (modelNode.hasNodeProperty("path")) {
+ ModelNode pathNode = modelNode.nodeProperty("path").modelNode();
+ if (pathNode.metaInfo().isSubclassOf("QtQuick.Path") && pathNode.hasNodeListProperty("pathElements")) {
+ QList<ModelNode> pathElements = pathNode.nodeListProperty("pathElements").toModelNodeList();
+ if (pathElements.isEmpty())
+ return 0;
+
+ foreach (const ModelNode &pathElement, pathElements) {
+ if (isNonSupportedPathElement(pathElement))
+ return 0;
+ }
+ }
+ }
+
+ return 20;
+ }
+
+ return 0;
+}
+
+class PathToolAction : public AbstractAction
+{
+public:
+ PathToolAction() : AbstractAction(QCoreApplication::translate("PathToolAction","Edit Path")) {}
+
+ QByteArray category() const override
+ {
+ return QByteArray();
+ }
+
+ QByteArray menuId() const override
+ {
+ return "PathTool";
+ }
+
+ int priority() const override
+ {
+ return CustomActionsPriority;
+ }
+
+ Type type() const override
+ {
+ return ContextMenuAction;
+ }
+
+protected:
+ bool isVisible(const SelectionContext &selectionContext) const override
+ {
+ if (selectionContext.scenePosition().isNull())
+ return false;
+
+ if (selectionContext.singleNodeIsSelected())
+ return pathRankForModelNode(selectionContext.currentSingleSelectedNode()) > 0;
+
+ return false;
+ }
+
+ bool isEnabled(const SelectionContext &selectionContext) const override
+ {
+ return isVisible(selectionContext);
+ }
+};
+
+PathTool::PathTool()
+ : m_pathToolView(this)
+{
+ auto textToolAction = new PathToolAction;
+ QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(textToolAction);
+ connect(textToolAction->action(), &QAction::triggered, [=]() {
+ if (m_pathToolView.model())
+ m_pathToolView.model()->detachView(&m_pathToolView);
+ view()->changeCurrentToolTo(this);
+ });
+
+
+}
+
+PathTool::~PathTool() = default;
+
+void PathTool::clear()
+{
+ if (m_pathItem)
+ m_pathItem->deleteLater();
+
+ AbstractFormEditorTool::clear();
+}
+
+void PathTool::mousePressEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * event)
+{
+ event->setPos(m_pathItem->mapFromScene(event->scenePos()));
+ event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(m_pathItem.data(), event);
+}
+
+void PathTool::mouseMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent *event)
+{
+ event->setPos(m_pathItem->mapFromScene(event->scenePos()));
+ event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(m_pathItem.data(), event);
+}
+
+void PathTool::hoverMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * event)
+{
+ event->setPos(m_pathItem->mapFromScene(event->scenePos()));
+ event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(m_pathItem.data(), event);
+}
+
+void PathTool::keyPressEvent(QKeyEvent *keyEvent)
+{
+ if (keyEvent->key() == Qt::Key_Escape) {
+ m_pathItem->writePathToProperty();
+ keyEvent->accept();
+ }
+}
+
+void PathTool::keyReleaseEvent(QKeyEvent * keyEvent)
+{
+ if (keyEvent->key() == Qt::Key_Escape) {
+ keyEvent->accept();
+ if (m_pathToolView.model())
+ m_pathToolView.model()->detachView(&m_pathToolView);
+ view()->changeToSelectionTool();
+ }
+}
+
+void PathTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+
+}
+
+void PathTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+
+}
+
+void PathTool::mouseReleaseEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent *event)
+{
+ event->setPos(m_pathItem->mapFromScene(event->scenePos()));
+ event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(m_pathItem.data(), event);
+}
+
+
+void PathTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> & /*itemList*/, QGraphicsSceneMouseEvent *event)
+{
+ if (m_pathItem.data() && !m_pathItem->boundingRect().contains(m_pathItem->mapFromScene(event->scenePos()))) {
+ m_pathItem->writePathToProperty();
+ view()->changeToSelectionTool();
+ event->accept();
+ }
+}
+
+void PathTool::itemsAboutToRemoved(const QList<FormEditorItem*> &removedItemList)
+{
+ if (m_pathItem == nullptr)
+ return;
+
+ if (removedItemList.contains(m_pathItem->formEditorItem()))
+ view()->changeToSelectionTool();
+}
+
+static bool hasPathProperty(FormEditorItem *formEditorItem)
+{
+ return formEditorItem->qmlItemNode().modelNode().metaInfo().hasProperty("path");
+}
+
+void PathTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList)
+{
+ if (m_pathItem.data() && itemList.contains(m_pathItem->formEditorItem()))
+ m_pathItem->writePathToProperty();
+
+ delete m_pathItem.data();
+ if (!itemList.isEmpty() && hasPathProperty(itemList.constFirst())) {
+ FormEditorItem *formEditorItem = itemList.constFirst();
+ m_pathItem = new PathItem(scene());
+ m_pathItem->setParentItem(scene()->manipulatorLayerItem());
+ m_pathItem->setFormEditorItem(formEditorItem);
+ formEditorItem->qmlItemNode().modelNode().model()->attachView(&m_pathToolView);
+ } else {
+ if (m_pathToolView.model())
+ m_pathToolView.model()->detachView(&m_pathToolView);
+ view()->changeToSelectionTool();
+ }
+}
+
+void PathTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+void PathTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/)
+{
+}
+
+void PathTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList)
+{
+ using ModelNodePropertyNamePair = QPair<ModelNode, PropertyName>;
+ foreach (const ModelNodePropertyNamePair &propertyPair, propertyList) {
+ if (propertyPair.first == m_pathItem->formEditorItem()->qmlItemNode().modelNode()
+ && propertyPair.second == "path")
+ m_pathItem->updatePath();
+ }
+}
+
+void PathTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+int PathTool::wantHandleItem(const ModelNode &modelNode) const
+{
+ return pathRankForModelNode(modelNode);
+}
+
+QString PathTool::name() const
+{
+ return QCoreApplication::translate("PathTool", "Path Tool");
+}
+
+ModelNode PathTool::editingPathViewModelNode() const
+{
+ if (m_pathItem)
+ return m_pathItem->formEditorItem()->qmlItemNode().modelNode();
+
+ return ModelNode();
+}
+
+void PathTool::pathChanged()
+{
+ if (m_pathItem)
+ m_pathItem->updatePath();
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.h b/src/plugins/qmldesigner/components/pathtool/pathtool.h
new file mode 100644
index 0000000000..a2a65db27d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathtool.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "abstractcustomtool.h"
+#include "selectionindicator.h"
+
+#include <QHash>
+#include <QPointer>
+#include <QColorDialog>
+
+#include "pathtoolview.h"
+
+namespace QmlDesigner {
+
+class PathItem;
+
+class PathTool : public QObject, public AbstractCustomTool
+{
+ Q_OBJECT
+public:
+ PathTool();
+ ~PathTool() override;
+
+ void mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+ void dragLeaveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+ void dragMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+
+ void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override;
+
+ void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ void instancesCompleted(const QList<FormEditorItem*> &itemList) override;
+ void instancesParentChanged(const QList<FormEditorItem *> &itemList) override;
+ void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override;
+
+ void clear() override;
+
+ void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ int wantHandleItem(const ModelNode &modelNode) const override;
+
+ QString name() const override;
+
+ ModelNode editingPathViewModelNode() const;
+
+ void pathChanged();
+
+private:
+ PathToolView m_pathToolView;
+ QPointer<PathItem> m_pathItem;
+};
+
+}
diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.pri b/src/plugins/qmldesigner/components/pathtool/pathtool.pri
new file mode 100644
index 0000000000..eba35315fe
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathtool.pri
@@ -0,0 +1,13 @@
+HEADERS += $$PWD/pathtool.h
+HEADERS += $$PWD/pathselectionmanipulator.h
+HEADERS += $$PWD/pathtoolview.h
+HEADERS += $$PWD/controlpoint.h
+HEADERS += $$PWD/cubicsegment.h
+HEADERS += $$PWD/pathitem.h
+
+SOURCES += $$PWD/pathtool.cpp
+SOURCES += $$PWD/pathselectionmanipulator.cpp
+SOURCES += $$PWD/pathtoolview.cpp
+SOURCES += $$PWD/controlpoint.cpp
+SOURCES += $$PWD/cubicsegment.cpp
+SOURCES += $$PWD/pathitem.cpp
diff --git a/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp
new file mode 100644
index 0000000000..ea1e28832a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pathtoolview.h"
+
+#include <nodeproperty.h>
+#include <variantproperty.h>
+#include <modelnode.h>
+#include <metainfo.h>
+
+#include "pathtool.h"
+
+#include <QtDebug>
+
+namespace QmlDesigner {
+
+PathToolView::PathToolView(PathTool *pathTool)
+ : m_pathTool(pathTool)
+{
+}
+
+static bool isInEditedPath(const NodeAbstractProperty &propertyParent, const ModelNode &editingPathViewModelNode)
+{
+ if (editingPathViewModelNode.isValid()) {
+ if (editingPathViewModelNode.hasNodeProperty("path")) {
+ ModelNode pathModelNode = editingPathViewModelNode.nodeProperty("path").modelNode();
+ if (pathModelNode.metaInfo().isSubclassOf("QtQuick.Path")) {
+ if (propertyParent.name() == "pathElements" && propertyParent.parentModelNode() == pathModelNode)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void PathToolView::nodeReparented(const ModelNode & /*node*/,
+ const NodeAbstractProperty & newPropertyParent,
+ const NodeAbstractProperty & /*oldPropertyParent*/,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ if (isInEditedPath(newPropertyParent, m_pathTool->editingPathViewModelNode()))
+ m_pathTool->pathChanged();
+}
+
+bool variantPropertyInEditedPath(const VariantProperty &variantProperty, const ModelNode &editingPathViewModelNode)
+{
+ ModelNode pathElementModelNode = variantProperty.parentModelNode();
+ if (pathElementModelNode.hasParentProperty()) {
+ if (isInEditedPath(pathElementModelNode.parentProperty(), editingPathViewModelNode))
+ return true;
+ }
+
+ return false;
+}
+
+bool changesEditedPath(const QList<VariantProperty> &propertyList, const ModelNode &editingPathViewModelNode)
+{
+ foreach (const VariantProperty variantProperty, propertyList) {
+ if (variantPropertyInEditedPath(variantProperty, editingPathViewModelNode))
+ return true;
+ }
+
+ return false;
+}
+
+void PathToolView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ if (changesEditedPath(propertyList, m_pathTool->editingPathViewModelNode()))
+ m_pathTool->pathChanged();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/pathtool/pathtoolview.h b/src/plugins/qmldesigner/components/pathtool/pathtoolview.h
new file mode 100644
index 0000000000..46b688923e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/pathtool/pathtoolview.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <abstractview.h>
+
+namespace QmlDesigner {
+
+class PathTool;
+
+class PathToolView : public AbstractView
+{
+ Q_OBJECT
+public:
+ PathToolView(PathTool *pathTool);
+
+ void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override;
+ void variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange) override;
+
+private:
+ PathTool *m_pathTool;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
index 5d7b1aeaaf..245d604707 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
@@ -85,6 +85,11 @@ QUrl FileResourcesModel::path() const
return m_path;
}
+QUrl FileResourcesModel::dirPath() const
+{
+ return QUrl::fromLocalFile(m_dirPath.path());
+}
+
void FileResourcesModel::setFilter(const QString &filter)
{
if (m_filter != filter) {
@@ -162,16 +167,14 @@ void FileResourcesModel::setupModel()
m_lock = true;
m_model.clear();
- QDir dir;
-
- dir = QFileInfo(m_path.toLocalFile()).dir();
+ m_dirPath = QFileInfo(m_path.toLocalFile()).dir();
QStringList filterList = m_filter.split(QLatin1Char(' '));
- QDirIterator it(dir.absolutePath(), filterList, QDir::Files, QDirIterator::Subdirectories);
+ QDirIterator it(m_dirPath.absolutePath(), filterList, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString absolutePath = it.next();
- m_model.append(dir.relativeFilePath(absolutePath));
+ m_model.append(m_dirPath.relativeFilePath(absolutePath));
}
m_lock = false;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h
index e0d6fc9725..f686631079 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h
@@ -27,6 +27,7 @@
#include <qmlitemnode.h>
+#include <QDir>
#include <QObject>
#include <QStringList>
#include <QUrl>
@@ -40,6 +41,7 @@ class FileResourcesModel : public QObject
Q_PROPERTY(QString filter READ filter WRITE setFilter)
Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend NOTIFY modelNodeBackendChanged)
Q_PROPERTY(QUrl path READ path WRITE setPath)
+ Q_PROPERTY(QUrl dirPath READ dirPath)
Q_PROPERTY(QStringList fileModel READ fileModel NOTIFY fileModelChanged)
public:
@@ -51,6 +53,7 @@ public:
void setFileNameStr(const QString &fileName);
void setPath(const QUrl &url);
QUrl path() const;
+ QUrl dirPath() const;
void setFilter(const QString &filter);
QString filter() const;
QStringList fileModel() const;
@@ -71,6 +74,7 @@ private:
private:
QUrl m_fileName;
QUrl m_path;
+ QDir m_dirPath;
QString m_filter;
bool m_lock;
QString m_currentPath;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp
index 6bb3153bea..d4b1f47184 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp
@@ -27,6 +27,8 @@
#include "qmlanchorbindingproxy.h"
#include "propertyeditorview.h"
+#include "gradientpresetitem.h"
+#include "gradientpresetcustomlistmodel.h"
#include <exception.h>
#include <nodeproperty.h>
@@ -34,14 +36,14 @@
#include <variantproperty.h>
#include <abstractview.h>
#include <nodemetainfo.h>
-#include <rewritertransaction.h>
+#include <exception.h>
#include <utils/qtcassert.h>
#include <QTimer>
GradientModel::GradientModel(QObject *parent) :
- QAbstractListModel(parent), m_gradientTypeName("Gradient"), m_locked(false)
+ QAbstractListModel(parent)
{
}
@@ -145,18 +147,16 @@ void GradientModel::addGradient()
return;
if (!m_itemNode.modelNode().hasNodeProperty(gradientPropertyName().toUtf8())) {
- try {
+ if (m_gradientTypeName != "Gradient")
+ ensureShapesImport();
+
+ view()->executeInTransaction("GradientModel::addGradient", [this](){
QColor color = m_itemNode.instanceValue("color").value<QColor>();
if (!color.isValid())
color = QColor(Qt::white);
- if (m_gradientTypeName != "Gradient")
- ensureShapesImport();
-
- QmlDesigner::RewriterTransaction transaction = view()->beginRewriterTransaction(QByteArrayLiteral("GradientModel::addGradient"));
-
QmlDesigner::ModelNode gradientNode = createGradientNode();
m_itemNode.modelNode().nodeProperty(gradientPropertyName().toUtf8()).reparentHere(gradientNode);
@@ -170,16 +170,12 @@ void GradientModel::addGradient()
gradientStopNode.variantProperty("position").setValue(1.0);
gradientStopNode.variantProperty("color").setValue(QColor(Qt::black));
gradientNode.nodeListProperty("stops").reparentHere(gradientStopNode);
-
- } catch (const QmlDesigner::Exception &e) {
- e.showException();
- }
-
+ });
}
setupModel();
if (m_gradientTypeName != "Gradient")
- QTimer::singleShot(100, [this](){ view()->resetPuppet(); }); /*Unfortunately required */
+ resetPuppet(); /*Unfortunately required */
emit hasGradientChanged();
emit gradientTypeChanged();
}
@@ -242,18 +238,18 @@ qreal GradientModel::getPosition(int index) const
void GradientModel::removeStop(int index)
{
if (index < rowCount() - 1 && index != 0) {
- QmlDesigner::RewriterTransaction transaction = view()->beginRewriterTransaction(QByteArrayLiteral("GradientModel::removeStop"));
- QmlDesigner::ModelNode gradientNode = m_itemNode.modelNode().nodeProperty(gradientPropertyName().toUtf8()).modelNode();
- QmlDesigner::QmlObjectNode stop = gradientNode.nodeListProperty("stops").at(index);
- if (stop.isValid()) {
- stop.destroy();
- setupModel();
- }
+ view()->executeInTransaction("GradientModel::removeStop", [this, index](){
+ QmlDesigner::ModelNode gradientNode = m_itemNode.modelNode().nodeProperty(gradientPropertyName().toUtf8()).modelNode();
+ QmlDesigner::QmlObjectNode stop = gradientNode.nodeListProperty("stops").at(index);
+ if (stop.isValid()) {
+ stop.destroy();
+ setupModel();
+ }
+ });
}
qWarning() << Q_FUNC_INFO << "invalid index";
}
-
void GradientModel::deleteGradient()
{
if (!m_itemNode.isValid())
@@ -262,16 +258,7 @@ void GradientModel::deleteGradient()
if (!m_itemNode.modelNode().metaInfo().hasProperty(gradientPropertyName().toUtf8()))
return;
- QmlDesigner::ModelNode modelNode = m_itemNode.modelNode();
-
- if (m_itemNode.isInBaseState()) {
- if (modelNode.hasProperty(gradientPropertyName().toUtf8())) {
- QmlDesigner::RewriterTransaction transaction = view()->beginRewriterTransaction(QByteArrayLiteral("GradientModel::deleteGradient"));
- QmlDesigner::ModelNode gradientNode = modelNode.nodeProperty(gradientPropertyName().toUtf8()).modelNode();
- if (QmlDesigner::QmlObjectNode(gradientNode).isValid())
- QmlDesigner::QmlObjectNode(gradientNode).destroy();
- }
- }
+ deleteGradientNode(true);
emit hasGradientChanged();
emit gradientTypeChanged();
@@ -392,7 +379,11 @@ void GradientModel::ensureShapesImport()
{
if (!hasShapesImport()) {
QmlDesigner::Import timelineImport = QmlDesigner::Import::createLibraryImport("QtQuick.Shapes", "1.0");
- model()->changeImports({timelineImport}, {});
+ try {
+ model()->changeImports({timelineImport}, {});
+ } catch (const QmlDesigner::Exception &) {
+ QTC_ASSERT(false, return);
+ }
}
}
@@ -444,6 +435,11 @@ QmlDesigner::AbstractView *GradientModel::view() const
return m_itemNode.view();
}
+void GradientModel::resetPuppet()
+{
+ QTimer::singleShot(1000, [this]() { view()->resetPuppet(); });
+}
+
QmlDesigner::ModelNode GradientModel::createGradientNode()
{
QByteArray fullTypeName = m_gradientTypeName.toUtf8();
@@ -477,6 +473,23 @@ QmlDesigner::ModelNode GradientModel::createGradientStopNode()
return view()->createModelNode(fullTypeName, majorVersion, minorVersion);
}
+void GradientModel::deleteGradientNode(bool saveTransaction)
+{
+ QmlDesigner::ModelNode modelNode = m_itemNode.modelNode();
+
+ if (m_itemNode.isInBaseState()) {
+ if (modelNode.hasProperty(gradientPropertyName().toUtf8())) {
+ if (saveTransaction)
+ QmlDesigner::RewriterTransaction transaction = view()->beginRewriterTransaction(
+ QByteArrayLiteral("GradientModel::deleteGradient"));
+ QmlDesigner::ModelNode gradientNode
+ = modelNode.nodeProperty(gradientPropertyName().toUtf8()).modelNode();
+ if (QmlDesigner::QmlObjectNode(gradientNode).isValid())
+ QmlDesigner::QmlObjectNode(gradientNode).destroy();
+ }
+ }
+}
+
void GradientModel::setGradientProperty(const QString &propertyName, qreal value)
{
QTC_ASSERT(m_itemNode.isValid(), return);
@@ -494,3 +507,102 @@ void GradientModel::setGradientProperty(const QString &propertyName, qreal value
e.showException();
}
}
+
+void GradientModel::setPresetByID(int presetID)
+{
+ const QGradient gradient(GradientPresetItem::createGradientFromPreset(
+ static_cast<GradientPresetItem::Preset>(presetID)));
+ const QList<QGradientStop> gradientStops = gradient.stops().toList();
+
+ QList<qreal> stopsPositions;
+ QList<QString> stopsColors;
+ for (const QGradientStop &stop : gradientStops) {
+ stopsPositions.append(stop.first);
+ stopsColors.append(stop.second.name());
+ }
+
+ setPresetByStops(stopsPositions, stopsColors, gradientStops.size());
+}
+
+void GradientModel::setPresetByStops(const QList<qreal> &stopsPositions,
+ const QList<QString> &stopsColors,
+ int stopsCount)
+{
+ if (m_locked)
+ return;
+
+ if (!m_itemNode.isValid() || gradientPropertyName().isEmpty())
+ return;
+
+ QmlDesigner::RewriterTransaction transaction = view()->beginRewriterTransaction(
+ QByteArrayLiteral("GradientModel::setCustomPreset"));
+
+ deleteGradientNode(false);
+
+ if (!m_itemNode.modelNode().hasNodeProperty(gradientPropertyName().toUtf8())) {
+ try {
+
+ if (m_gradientTypeName != "Gradient")
+ ensureShapesImport();
+
+ QmlDesigner::ModelNode gradientNode = createGradientNode();
+
+ m_itemNode.modelNode()
+ .nodeProperty(gradientPropertyName().toUtf8())
+ .reparentHere(gradientNode);
+
+ for (int i = 0; i < stopsCount; i++) {
+ QmlDesigner::ModelNode gradientStopNode = createGradientStopNode();
+ gradientStopNode.variantProperty("position").setValue(stopsPositions.at(i));
+ gradientStopNode.variantProperty("color").setValue(stopsColors.at(i));
+ gradientNode.nodeListProperty("stops").reparentHere(gradientStopNode);
+ }
+
+ } catch (const QmlDesigner::Exception &e) {
+ e.showException();
+ }
+ }
+ setupModel();
+
+ if (m_gradientTypeName != "Gradient")
+ resetPuppet(); /*Unfortunately required */
+
+ emit hasGradientChanged();
+ emit gradientTypeChanged();
+}
+
+void GradientModel::savePreset()
+{
+ //preparing qgradient:
+ QGradient currentGradient;
+ QGradientStops currentStops;
+ QGradientStop stop; //double, color
+
+ for (int i = 0; i < rowCount(); i++) {
+ stop.first = getPosition(i);
+ stop.second = getColor(i);
+ currentStops.append(stop);
+ }
+ currentGradient.setStops(currentStops);
+ const GradientPresetItem item(currentGradient, "Custom Gradient");
+
+ //reading the custom gradient file
+ //filling the file with old data + new data
+ const QString filename(GradientPresetCustomListModel::getFilename());
+ QList<GradientPresetItem> items = GradientPresetCustomListModel::storedPresets(filename);
+ items.append(item);
+ GradientPresetCustomListModel::storePresets(filename, items);
+}
+
+void GradientModel::updateGradient()
+{
+ QList<qreal> stops;
+ QList<QString> colors;
+ int stopsCount = rowCount();
+ for (int i = 0; i < stopsCount; i++) {
+ stops.append(getPosition(i));
+ colors.append(getColor(i).name(QColor::HexArgb));
+ }
+
+ setPresetByStops(stops, colors, stopsCount);
+}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.h b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.h
index 48514ae688..c54526838e 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.h
@@ -70,6 +70,15 @@ public:
Q_INVOKABLE void setGradientProperty(const QString &propertyName, qreal value);
+ Q_INVOKABLE void setPresetByID(int presetID);
+ Q_INVOKABLE void setPresetByStops(const QList<qreal> &stopsPositions,
+ const QList<QString> &stopsColors,
+ int stopsCount);
+
+ Q_INVOKABLE void savePreset();
+
+ Q_INVOKABLE void updateGradient();
+
signals:
void anchorBackendChanged();
void hasGradientChanged();
@@ -87,17 +96,19 @@ private:
bool locked() const;
QmlDesigner::ModelNode createGradientNode();
QmlDesigner::ModelNode createGradientStopNode();
+ void deleteGradientNode(bool saveTransaction);
private:
QmlDesigner::QmlItemNode m_itemNode;
QString m_gradientPropertyName;
- QString m_gradientTypeName;
- bool m_locked;
+ QString m_gradientTypeName = {"Gradient"};
+ bool m_locked = false;
bool hasShapesImport() const;
void ensureShapesImport();
void setupGradientProperties(const QmlDesigner::ModelNode &gradient);
QmlDesigner::Model *model() const;
QmlDesigner::AbstractView *view() const;
+ void resetPuppet();
};
QML_DECLARE_TYPE(GradientModel)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.cpp
new file mode 100644
index 0000000000..a1599a7099
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.cpp
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "gradientpresetcustomlistmodel.h"
+#include "gradientpresetitem.h"
+
+#include <coreplugin/icore.h>
+#include <utils/qtcassert.h>
+#include <utils/algorithm.h>
+
+#include <QHash>
+#include <QByteArray>
+#include <QDebug>
+#include <QSettings>
+#include <QFile>
+
+namespace Internal {
+
+static const char settingsKey[] = "GradientPresetCustomList";
+static const char settingsFileName[] = "/GradientPresets.ini";
+
+QString settingsFullFilePath(const QSettings::Scope &scope)
+{
+ if (scope == QSettings::SystemScope)
+ return Core::ICore::installerResourcePath() + settingsFileName;
+
+ return Core::ICore::userResourcePath() + settingsFileName;
+}
+
+} // namespace Internal
+
+GradientPresetCustomListModel::GradientPresetCustomListModel(QObject *parent)
+ : GradientPresetListModel(parent)
+ , m_filename(getFilename())
+{
+ qRegisterMetaTypeStreamOperators<GradientPresetItem>("GradientPresetItem");
+ readPresets();
+}
+
+GradientPresetCustomListModel::~GradientPresetCustomListModel() {}
+
+void GradientPresetCustomListModel::registerDeclarativeType()
+{
+ qmlRegisterType<GradientPresetCustomListModel>("HelperWidgets",
+ 2,
+ 0,
+ "GradientPresetCustomListModel");
+}
+
+QString GradientPresetCustomListModel::getFilename()
+{
+ return Internal::settingsFullFilePath(QSettings::UserScope);
+}
+
+void GradientPresetCustomListModel::storePresets(const QString &filename,
+ const QList<GradientPresetItem> &items)
+{
+ const QList<QVariant> presets
+ = Utils::transform<QList<QVariant>>(items, [](const GradientPresetItem &item) {
+ return QVariant::fromValue(item);
+ });
+
+ QSettings settings(filename, QSettings::IniFormat);
+ settings.clear();
+ settings.setValue(Internal::settingsKey, QVariant::fromValue(presets));
+}
+
+QList<GradientPresetItem> GradientPresetCustomListModel::storedPresets(const QString &filename)
+{
+ const QSettings settings(filename, QSettings::IniFormat);
+ const QVariant presetSettings = settings.value(Internal::settingsKey);
+
+ if (!presetSettings.isValid())
+ return {};
+
+ const QList<QVariant> presets = presetSettings.toList();
+
+ QList<GradientPresetItem> out;
+ for (const QVariant &preset : presets) {
+ if (preset.isValid()) {
+ out.append(preset.value<GradientPresetItem>());
+ }
+ }
+
+ return out;
+}
+
+void GradientPresetCustomListModel::addGradient(const QList<qreal> &stopsPositions,
+ const QList<QString> &stopsColors,
+ int stopsCount)
+{
+ QGradient tempGradient;
+ QGradientStops gradientStops;
+ QGradientStop gradientStop;
+ for (int i = 0; i < stopsCount; i++) {
+ gradientStop.first = stopsPositions.at(i);
+ gradientStop.second = stopsColors.at(i);
+ gradientStops.push_back(gradientStop);
+ }
+
+ tempGradient.setStops(gradientStops);
+
+ addItem(GradientPresetItem(tempGradient));
+}
+
+void GradientPresetCustomListModel::changePresetName(int id, const QString &newName)
+{
+ QTC_ASSERT(id >= 0, return);
+ QTC_ASSERT(id < m_items.size(), return);
+ m_items[id].setPresetName(newName);
+ writePresets();
+}
+
+void GradientPresetCustomListModel::deletePreset(int id)
+{
+ QTC_ASSERT(id >= 0, return);
+ QTC_ASSERT(id < m_items.size(), return);
+ beginResetModel();
+ m_items.removeAt(id);
+ writePresets();
+ endResetModel();
+}
+
+void GradientPresetCustomListModel::writePresets()
+{
+ storePresets(m_filename, m_items);
+}
+
+void GradientPresetCustomListModel::readPresets()
+{
+ const QList<GradientPresetItem> presets = storedPresets(m_filename);
+ beginResetModel();
+ m_items.clear();
+
+ for (const GradientPresetItem &preset : presets) {
+ addItem(preset);
+ }
+ endResetModel();
+}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.h b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.h
new file mode 100644
index 0000000000..382b651e3b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetcustomlistmodel.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "gradientpresetlistmodel.h"
+
+#include <QObject>
+#include <QAbstractListModel>
+#include <QtQml/qqml.h>
+
+class GradientPresetCustomListModel : public GradientPresetListModel
+{
+ Q_OBJECT
+
+public:
+ explicit GradientPresetCustomListModel(QObject *parent = nullptr);
+ ~GradientPresetCustomListModel() override;
+
+ static void registerDeclarativeType();
+
+ static QString getFilename();
+ static void storePresets(const QString &filename, const QList<GradientPresetItem> &items);
+ static QList<GradientPresetItem> storedPresets(const QString &filename);
+
+ Q_INVOKABLE void addGradient(const QList<qreal> &stopsPositions,
+ const QList<QString> &stopsColors,
+ int stopsCount);
+
+ Q_INVOKABLE void changePresetName(int id, const QString &newName);
+ Q_INVOKABLE void deletePreset(int id);
+
+ Q_INVOKABLE void writePresets();
+ Q_INVOKABLE void readPresets();
+
+private:
+ QString m_filename;
+};
+
+QML_DECLARE_TYPE(GradientPresetCustomListModel)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.cpp
new file mode 100644
index 0000000000..8237390de9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "gradientpresetdefaultlistmodel.h"
+#include "gradientpresetitem.h"
+
+#include <QHash>
+#include <QByteArray>
+#include <QDebug>
+#include <QFile>
+
+GradientPresetDefaultListModel::GradientPresetDefaultListModel(QObject *parent)
+ : GradientPresetListModel(parent)
+{
+ addAllPresets();
+}
+
+GradientPresetDefaultListModel::~GradientPresetDefaultListModel() {}
+
+void GradientPresetDefaultListModel::registerDeclarativeType()
+{
+ qmlRegisterType<GradientPresetDefaultListModel>("HelperWidgets",
+ 2,
+ 0,
+ "GradientPresetDefaultListModel");
+}
+
+void GradientPresetDefaultListModel::addAllPresets()
+{
+ const QMetaObject &metaObj = QGradient::staticMetaObject;
+ const QMetaEnum metaEnum = metaObj.enumerator(metaObj.indexOfEnumerator("Preset"));
+
+ if (!metaEnum.isValid())
+ return;
+
+ for (int i = 0; i < metaEnum.keyCount(); i++) {
+ addItem(GradientPresetItem(GradientPresetItem::Preset(metaEnum.value(i))));
+ }
+}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.h b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.h
new file mode 100644
index 0000000000..831135e052
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetdefaultlistmodel.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "gradientpresetlistmodel.h"
+
+#include <QObject>
+#include <QAbstractListModel>
+#include <QtQml/qqml.h>
+
+class GradientPresetDefaultListModel : public GradientPresetListModel
+{
+ Q_OBJECT
+
+public:
+ explicit GradientPresetDefaultListModel(QObject *parent = nullptr);
+ ~GradientPresetDefaultListModel() override;
+
+ static void registerDeclarativeType();
+
+private:
+ void addAllPresets();
+};
+
+QML_DECLARE_TYPE(GradientPresetDefaultListModel)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.cpp
new file mode 100644
index 0000000000..9d2454c4e9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.cpp
@@ -0,0 +1,206 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "gradientpresetitem.h"
+
+#include <utils/qtcassert.h>
+#include <utils/algorithm.h>
+
+#include <QVariant>
+#include <QMetaObject>
+#include <QMetaEnum>
+#include <algorithm>
+#include <QDebug>
+
+
+GradientPresetItem::GradientPresetItem()
+ : m_gradientVal(QGradient())
+ , m_gradientID(Preset(0))
+ , m_presetName(QString())
+{}
+
+GradientPresetItem::GradientPresetItem(const QGradient &value, const QString &name)
+ : m_gradientVal(value)
+ , m_gradientID(Preset(0))
+ , m_presetName(name)
+{}
+
+GradientPresetItem::GradientPresetItem(const Preset value)
+ : m_gradientVal(createGradientFromPreset(value))
+ , m_gradientID(value)
+ , m_presetName(getNameByPreset(value))
+{}
+
+GradientPresetItem::~GradientPresetItem() = default;
+
+QVariant GradientPresetItem::getProperty(GradientPresetItem::Property id) const
+{
+ QVariant out;
+
+ switch (id) {
+ case objectNameRole:
+ out.setValue(QString());
+ break;
+ case stopsPosListRole:
+ out.setValue(stopsPosList());
+ break;
+ case stopsColorListRole:
+ out.setValue(stopsColorList());
+ break;
+ case stopListSizeRole:
+ out.setValue(stopListSize());
+ break;
+ case presetNameRole:
+ out.setValue(presetName());
+ break;
+ case presetIDRole:
+ out.setValue(presetID());
+ break;
+ default:
+ qWarning() << "GradientPresetItem Property switch default case";
+ break; //replace with assert before switch?
+ }
+
+ return out;
+}
+
+QGradient GradientPresetItem::gradientVal() const
+{
+ return m_gradientVal;
+}
+
+void GradientPresetItem::setGradient(const QGradient &value)
+{
+ m_gradientVal = value;
+ m_gradientID = Preset(0);
+ m_presetName = QString();
+}
+
+void GradientPresetItem::setGradient(const Preset value)
+{
+ m_gradientID = value;
+ m_gradientVal = createGradientFromPreset(value);
+ m_presetName = getNameByPreset(value);
+}
+
+QList<qreal> GradientPresetItem::stopsPosList() const
+{
+ const QList<QPair<qreal, QColor>> subres = m_gradientVal.stops().toList();
+ const QList<qreal> result = Utils::transform<QList<qreal>>(subres,
+ [](const QPair<qreal, QColor> &item) {
+ return item.first;
+ });
+ return result;
+}
+
+QList<QString> GradientPresetItem::stopsColorList() const
+{
+ const QList<QPair<qreal, QColor>> subres = m_gradientVal.stops().toList();
+ const QList<QString> result
+ = Utils::transform<QList<QString>>(subres, [](const QPair<qreal, QColor> &item) {
+ return item.second.name();
+ });
+ return result;
+}
+
+int GradientPresetItem::stopListSize() const
+{
+ return m_gradientVal.stops().size();
+}
+
+void GradientPresetItem::setPresetName(const QString &value)
+{
+ m_presetName = value;
+}
+
+QString GradientPresetItem::presetName() const
+{
+ return m_presetName;
+}
+
+int GradientPresetItem::presetID() const
+{
+ return static_cast<int>(m_gradientID);
+}
+
+QString GradientPresetItem::getNameByPreset(Preset value)
+{
+ const QMetaObject &metaObj = QGradient::staticMetaObject;
+ const QMetaEnum metaEnum = metaObj.enumerator(metaObj.indexOfEnumerator("Preset"));
+
+ if (!metaEnum.isValid())
+ return QString("Custom");
+
+ QString enumName = QString::fromUtf8(metaEnum.valueToKey(static_cast<int>(value)));
+
+ const QStringList sl = enumName.split(QRegExp("(?=[A-Z])"), QString::SkipEmptyParts);
+
+ enumName.clear();
+ std::for_each(sl.begin(), sl.end(), [&enumName](const QString &s) { enumName += (s + " "); });
+ enumName.chop(1); //let's remove the last empty space
+
+ return (enumName.isEmpty() ? QString("Custom") : enumName);
+}
+
+QGradient GradientPresetItem::createGradientFromPreset(Preset value)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
+ return QGradient(value);
+#else
+ Q_UNUSED(value);
+ return {};
+#endif
+}
+
+QDebug &operator<<(QDebug &stream, const GradientPresetItem &gradient)
+{
+ stream << "\"stops:" << gradient.m_gradientVal.stops() << "\"";
+ stream << "\"preset:" << gradient.m_gradientID << "\"";
+ stream << "\"name:" << gradient.m_presetName << "\"";
+ return stream;
+}
+
+QDataStream &operator<<(QDataStream &stream, const GradientPresetItem &gradient)
+{
+ stream << gradient.m_gradientVal.stops();
+
+ stream << static_cast<int>(gradient.m_gradientID);
+ stream << gradient.m_presetName;
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, GradientPresetItem &gradient)
+{
+ QGradientStops stops;
+ stream >> stops;
+ gradient.m_gradientVal.setStops(stops);
+
+ int gradientID;
+ stream >> gradientID;
+ gradient.m_gradientID = static_cast<GradientPresetItem::Preset>(gradientID);
+
+ stream >> gradient.m_presetName;
+ return stream;
+}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.h b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.h
new file mode 100644
index 0000000000..cd0f0017e0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetitem.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QGradient>
+
+class GradientPresetItem
+{
+ Q_GADGET
+
+ Q_PROPERTY(QList<qreal> stopsPosList READ stopsPosList FINAL)
+ Q_PROPERTY(QList<QString> stopsColorList READ stopsColorList FINAL)
+ Q_PROPERTY(int stopListSize READ stopListSize FINAL)
+ Q_PROPERTY(QString presetName READ presetName FINAL)
+ Q_PROPERTY(int presetID READ presetID FINAL)
+
+public:
+#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
+ using Preset = QGradient::Preset;
+#else
+ enum Preset {};
+#endif
+
+ explicit GradientPresetItem();
+ explicit GradientPresetItem(const QGradient &value, const QString &name = QString());
+ explicit GradientPresetItem(const Preset number);
+ ~GradientPresetItem();
+
+ enum Property {
+ objectNameRole = 0,
+ stopsPosListRole = 1,
+ stopsColorListRole = 2,
+ stopListSizeRole = 3,
+ presetNameRole = 4,
+ presetIDRole = 5
+ };
+
+ QVariant getProperty(Property id) const;
+
+ QGradient gradientVal() const;
+
+ void setGradient(const QGradient &value);
+ void setGradient(const Preset value);
+
+ QList<qreal> stopsPosList() const;
+ QList<QString> stopsColorList() const;
+ int stopListSize() const;
+
+ void setPresetName(const QString &value);
+ QString presetName() const;
+ int presetID() const;
+
+ static QString getNameByPreset(Preset value);
+
+ friend QDebug &operator<<(QDebug &stream, const GradientPresetItem &gradient);
+
+ friend QDataStream &operator<<(QDataStream &stream, const GradientPresetItem &gradient);
+ friend QDataStream &operator>>(QDataStream &stream, GradientPresetItem &gradient);
+
+ static QGradient createGradientFromPreset(Preset value);
+
+private:
+ QGradient m_gradientVal;
+ Preset m_gradientID;
+ QString m_presetName;
+};
+
+Q_DECLARE_METATYPE(GradientPresetItem)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.cpp
new file mode 100644
index 0000000000..1ed95f8719
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "gradientpresetlistmodel.h"
+#include "gradientpresetitem.h"
+
+#include <QHash>
+#include <QByteArray>
+#include <QDebug>
+#include <QSettings>
+
+GradientPresetListModel::GradientPresetListModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ m_roleNames
+ = {{static_cast<int>(GradientPresetItem::Property::objectNameRole), "objectName"},
+ {static_cast<int>(GradientPresetItem::Property::stopsPosListRole), "stopsPosList"},
+ {static_cast<int>(GradientPresetItem::Property::stopsColorListRole), "stopsColorList"},
+ {static_cast<int>(GradientPresetItem::Property::stopListSizeRole), "stopListSize"},
+ {static_cast<int>(GradientPresetItem::Property::presetNameRole), "presetName"},
+ {static_cast<int>(GradientPresetItem::Property::presetIDRole), "presetID"}};
+}
+
+GradientPresetListModel::~GradientPresetListModel()
+{
+ clearItems();
+}
+
+int GradientPresetListModel::rowCount(const QModelIndex & /*parent*/) const
+{
+ return m_items.count();
+}
+
+QVariant GradientPresetListModel::data(const QModelIndex &index, int role) const
+{
+ if (index.isValid() && (index.row() >= 0) && (index.row() < m_items.count())) {
+ if (m_roleNames.contains(role)) {
+ QVariant value = m_items.at(index.row())
+ .getProperty(static_cast<GradientPresetItem::Property>(role));
+
+ if (auto model = qobject_cast<GradientPresetListModel *>(value.value<QObject *>()))
+ return QVariant::fromValue(model);
+
+ return value;
+ }
+
+ qWarning() << Q_FUNC_INFO << "invalid role requested";
+ return QVariant();
+ }
+
+ qWarning() << Q_FUNC_INFO << "invalid index requested";
+ return QVariant();
+}
+
+QHash<int, QByteArray> GradientPresetListModel::roleNames() const
+{
+ return m_roleNames;
+}
+
+void GradientPresetListModel::clearItems()
+{
+ beginResetModel();
+ m_items.clear();
+ endResetModel();
+}
+
+void GradientPresetListModel::addItem(const GradientPresetItem &element)
+{
+ beginResetModel();
+ m_items.append(element);
+ endResetModel();
+}
+
+const QList<GradientPresetItem> &GradientPresetListModel::items() const
+{
+ return m_items;
+}
+
+void GradientPresetListModel::sortItems()
+{
+ auto itemSort = [](const GradientPresetItem &first, const GradientPresetItem &second) {
+ return (static_cast<int>(first.presetID()) < static_cast<int>(second.presetID()));
+ };
+
+ std::sort(m_items.begin(), m_items.end(), itemSort);
+}
+
+void GradientPresetListModel::registerDeclarativeType()
+{
+ qmlRegisterType<GradientPresetListModel>("HelperWidgets", 2, 0, "GradientPresetListModel");
+}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.h b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.h
new file mode 100644
index 0000000000..7fce2243dd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/gradientpresetlistmodel.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QAbstractListModel>
+#include <QtQml/qqml.h>
+
+class GradientPresetItem;
+
+class GradientPresetListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit GradientPresetListModel(QObject *parent = nullptr);
+ ~GradientPresetListModel() override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ void clearItems();
+ void addItem(const GradientPresetItem &element);
+
+ const QList<GradientPresetItem> &items() const;
+
+ void sortItems();
+
+ static void registerDeclarativeType();
+
+protected:
+ QList<GradientPresetItem> m_items;
+ QHash<int, QByteArray> m_roleNames;
+};
+
+//QML_DECLARE_TYPE(GradientPresetListModel)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditor.pri b/src/plugins/qmldesigner/components/propertyeditor/propertyeditor.pri
index d822fbb70d..b32a744016 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditor.pri
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditor.pri
@@ -11,7 +11,14 @@ SOURCES += propertyeditorview.cpp \
propertyeditorwidget.cpp \
fileresourcesmodel.cpp \
gradientmodel.cpp \
- qmlmodelnodeproxy.cpp
+ qmlmodelnodeproxy.cpp \
+ gradientpresetitem.cpp \
+ gradientpresetlistmodel.cpp \
+ gradientpresetdefaultlistmodel.cpp \
+ gradientpresetcustomlistmodel.cpp \
+ simplecolorpalette.cpp \
+ simplecolorpalettemodel.cpp \
+ simplecolorpalettesingleton.cpp
HEADERS += propertyeditorview.h \
qmlanchorbindingproxy.h \
@@ -24,6 +31,13 @@ HEADERS += propertyeditorview.h \
propertyeditorwidget.h \
fileresourcesmodel.h \
gradientmodel.h \
- qmlmodelnodeproxy.h
+ qmlmodelnodeproxy.h \
+ gradientpresetitem.h \
+ gradientpresetlistmodel.h \
+ gradientpresetdefaultlistmodel.h \
+ gradientpresetcustomlistmodel.h \
+ simplecolorpalette.h \
+ simplecolorpalettemodel.h \
+ simplecolorpalettesingleton.h
QT += qml quick
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
index dc8243e1c8..8fdab5a821 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
@@ -152,22 +152,14 @@ void PropertyEditorContextObject::toogleExportAlias()
PropertyName modelNodeId = selectedNode.id().toUtf8();
ModelNode rootModelNode = rewriterView->rootModelNode();
- try {
- RewriterTransaction transaction =
- rewriterView->beginRewriterTransaction(QByteArrayLiteral("PropertyEditorContextObject:toogleExportAlias"));
-
+ rewriterView->executeInTransaction("PropertyEditorContextObject:toogleExportAlias", [&objectNode, &rootModelNode, modelNodeId](){
if (!objectNode.isAliasExported())
objectNode.ensureAliasExport();
else
if (rootModelNode.hasProperty(modelNodeId))
rootModelNode.removeProperty(modelNodeId);
-
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
+ });
}
-
}
void PropertyEditorContextObject::changeTypeName(const QString &typeName)
@@ -181,11 +173,8 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
QTC_ASSERT(!rewriterView->selectedModelNodes().isEmpty(), return);
- ModelNode selectedNode = rewriterView->selectedModelNodes().constFirst();
-
- try {
- RewriterTransaction transaction =
- rewriterView->beginRewriterTransaction(QByteArrayLiteral("PropertyEditorContextObject:changeTypeName"));
+ rewriterView->executeInTransaction("PropertyEditorContextObject:changeTypeName", [this, rewriterView, typeName](){
+ ModelNode selectedNode = rewriterView->selectedModelNodes().constFirst();
NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1());
if (!metaInfo.isValid()) {
@@ -193,16 +182,10 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
return;
}
if (selectedNode.isRootNode())
- rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
+ rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
else
selectedNode.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
-
- transaction.commit();
- } catch (RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
- exception.showException();
- }
-
-
+ });
}
void PropertyEditorContextObject::insertKeyframe(const QString &propertyName)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
index 284b6948ee..12f12ed2bc 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
@@ -40,6 +40,7 @@
#include <coreplugin/icore.h>
#include <qmljs/qmljssimplereader.h>
+#include <utils/qtcassert.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h>
@@ -281,13 +282,17 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
setupLayoutAttachedProperties(qmlObjectNode, propertyEditor);
+ // model node
+ m_backendModelNode.setup(qmlObjectNode.modelNode());
+ context()->setContextProperty(QLatin1String("modelNodeBackend"), &m_backendModelNode);
+
// className
auto valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("className"))));
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName("className");
valueObject->setModelNode(qmlObjectNode.modelNode());
- valueObject->setValue(qmlObjectNode.modelNode().simplifiedTypeName());
+ valueObject->setValue(m_backendModelNode.simplifiedTypeName());
QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(QLatin1String("className"), QVariant::fromValue(valueObject));
@@ -296,7 +301,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName("id");
- valueObject->setValue(qmlObjectNode.id());
+ valueObject->setValue(m_backendModelNode.nodeId());
QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject));
@@ -310,10 +315,6 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
qCInfo(propertyEditorBenchmark) << "anchors:" << time.elapsed();
- // model node
- m_backendModelNode.setup(qmlObjectNode.modelNode());
- context()->setContextProperty(QLatin1String("modelNodeBackend"), &m_backendModelNode);
-
qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed();
contextObject()->setSpecificsUrl(qmlSpecificsFile);
@@ -402,23 +403,49 @@ QString PropertyEditorQmlBackend::propertyEditorResourcesPath() {
QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
const NodeMetaInfo &superType,
- const QmlObjectNode &objectNode)
+ const QmlObjectNode &node)
{
if (!templateConfiguration() || !templateConfiguration()->isValid())
return QString();
+ const auto nodes = templateConfiguration()->children();
+
+ QStringList sectorTypes;
+
+ for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
+ if (node->propertyNames().contains("separateSection"))
+ sectorTypes.append(variantToStringList(node->property("typeNames")));
+ }
+
QStringList imports = variantToStringList(templateConfiguration()->property(QStringLiteral("imports")));
QString qmlTemplate = imports.join(QLatin1Char('\n')) + QLatin1Char('\n');
- qmlTemplate += QStringLiteral("Section {\n");
- qmlTemplate += QStringLiteral("caption: \"%1\"\n").arg(objectNode.modelNode().simplifiedTypeName());
- qmlTemplate += QStringLiteral("SectionLayout {\n");
+
+ qmlTemplate += "Column {\n";
+ qmlTemplate += "anchors.left: parent.left\n";
+ qmlTemplate += "anchors.right: parent.right\n";
QList<PropertyName> orderedList = type.propertyNames();
- Utils::sort(orderedList);
+ Utils::sort(orderedList, [type, &sectorTypes](const PropertyName &left, const PropertyName &right){
+ const QString typeNameLeft = QString::fromLatin1(type.propertyTypeName(left));
+ const QString typeNameRight = QString::fromLatin1(type.propertyTypeName(right));
+ if (typeNameLeft == typeNameRight)
+ return left > right;
+
+ if (sectorTypes.contains(typeNameLeft)) {
+ if (sectorTypes.contains(typeNameRight))
+ return left > right;
+ return true;
+ } else if (sectorTypes.contains(typeNameRight)) {
+ return false;
+ }
+ return left > right;
+ });
bool emptyTemplate = true;
+ bool sectionStarted = false;
+
foreach (const PropertyName &name, orderedList) {
if (name.startsWith("__"))
@@ -429,18 +456,38 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
TypeName typeName = type.propertyTypeName(name);
//alias resolution only possible with instance
- if (typeName == "alias" && objectNode.isValid())
- typeName = objectNode.instanceType(name);
+ if (typeName == "alias" && node.isValid())
+ typeName = node.instanceType(name);
+
+ auto nodes = templateConfiguration()->children();
if (!superType.hasProperty(name) && type.propertyIsWritable(name) && !name.contains(".")) {
- foreach (const QmlJS::SimpleReaderNode::Ptr &node, templateConfiguration()->children())
+
+ foreach (const QmlJS::SimpleReaderNode::Ptr &node, nodes)
if (variantToStringList(node->property(QStringLiteral("typeNames"))).contains(QString::fromLatin1(typeName))) {
const QString fileName = propertyTemplatesPath() + node->property(QStringLiteral("sourceFile")).toString();
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
QString source = QString::fromUtf8(file.readAll());
file.close();
+ const bool section = node->propertyNames().contains("separateSection");
+ if (section) {
+ qmlTemplate += "Section {\n";
+ qmlTemplate += "anchors.left: parent.left\n";
+ qmlTemplate += "anchors.right: parent.right\n";
+ qmlTemplate += QString("caption: \"%1\"\n").arg(QString::fromUtf8(properName));
+ } else if (!sectionStarted) {
+ qmlTemplate += QStringLiteral("Section {\n");
+ qmlTemplate += QStringLiteral("caption: \"%1\"\n").arg(QString::fromUtf8(type.simplifiedTypeName()));
+ qmlTemplate += "anchors.left: parent.left\n";
+ qmlTemplate += "anchors.right: parent.right\n";
+ qmlTemplate += QStringLiteral("SectionLayout {\n");
+ sectionStarted = true;
+ }
+
qmlTemplate += source.arg(QString::fromUtf8(name)).arg(QString::fromUtf8(properName));
+ if (section)
+ qmlTemplate += "}\n";
emptyTemplate = false;
} else {
qWarning().nospace() << "template definition source file not found:" << fileName;
@@ -448,8 +495,12 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
}
}
}
- qmlTemplate += QStringLiteral("}\n"); //Section
- qmlTemplate += QStringLiteral("}\n"); //SectionLayout
+ if (sectionStarted) {
+ qmlTemplate += QStringLiteral("}\n"); //Section
+ qmlTemplate += QStringLiteral("}\n"); //SectionLayout
+ }
+
+ qmlTemplate += "}\n";
if (emptyTemplate)
return QString();
@@ -469,6 +520,36 @@ TypeName PropertyEditorQmlBackend::fixTypeNameForPanes(const TypeName &typeName)
return fixedTypeName;
}
+static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second)
+{
+ for (const NodeMetaInfo &info : first.superClasses()) {
+ if (second.isSubclassOf(info.typeName()))
+ return info;
+ }
+ return first;
+}
+
+NodeMetaInfo PropertyEditorQmlBackend::findCommonAncestor(const ModelNode &node)
+{
+ if (!node.isValid())
+ return {};
+
+ QTC_ASSERT(node.metaInfo().isValid(), return {});
+
+ AbstractView *view = node.view();
+
+ if (view->selectedModelNodes().count() > 1) {
+ NodeMetaInfo commonClass = node.metaInfo();
+ for (const ModelNode &currentNode : view->selectedModelNodes()) {
+ if (currentNode.metaInfo().isValid() && !currentNode.isSubclassOf(commonClass.typeName(), -1, -1))
+ commonClass = findCommonSuperClass(currentNode.metaInfo(), commonClass);
+ }
+ return commonClass;
+ }
+
+ return node.metaInfo();
+}
+
TypeName PropertyEditorQmlBackend::qmlFileName(const NodeMetaInfo &nodeInfo)
{
const TypeName fixedTypeName = fixTypeNameForPanes(nodeInfo.typeName());
@@ -526,10 +607,10 @@ void PropertyEditorQmlBackend::setValueforLayoutAttachedProperties(const QmlObje
setValue(qmlObjectNode, name, properDefaultLayoutAttachedProperties(qmlObjectNode, propertyName));
}
-QUrl PropertyEditorQmlBackend::getQmlUrlForModelNode(const ModelNode &modelNode, TypeName &className)
+QUrl PropertyEditorQmlBackend::getQmlUrlForMetaInfo(const NodeMetaInfo &metaInfo, TypeName &className)
{
- if (modelNode.isValid()) {
- foreach (const NodeMetaInfo &info, modelNode.metaInfo().classHierarchy()) {
+ if (metaInfo.isValid()) {
+ foreach (const NodeMetaInfo &info, metaInfo.classHierarchy()) {
QUrl fileUrl = fileToUrl(locateQmlFile(info, QString::fromUtf8(qmlFileName(info))));
if (fileUrl.isValid()) {
className = info.typeName();
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
index a0012a1cc1..51279a1fc6 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
@@ -68,11 +68,10 @@ public:
PropertyEditorValue *propertyValueForName(const QString &propertyName);
static QString propertyEditorResourcesPath();
- static QString templateGeneration(const NodeMetaInfo &type, const NodeMetaInfo &superType,
- const QmlObjectNode &objectNode);
+ static QString templateGeneration(const NodeMetaInfo &type, const NodeMetaInfo &superType, const QmlObjectNode &node);
static QUrl getQmlFileUrl(const TypeName &relativeTypeName, const NodeMetaInfo &info = NodeMetaInfo());
- static QUrl getQmlUrlForModelNode(const ModelNode &modelNode, TypeName &className);
+ static QUrl getQmlUrlForMetaInfo(const NodeMetaInfo &modelNode, TypeName &className);
static bool checkIfUrlExists(const QUrl &url);
@@ -83,6 +82,8 @@ public:
void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor);
+ static NodeMetaInfo findCommonAncestor(const ModelNode &node);
+
private:
void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
const PropertyName &name, const QVariant &value,
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
index 0467355bf4..9dd0a2da24 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
@@ -213,20 +213,13 @@ void PropertyEditorView::changeValue(const QString &name)
castedValue = QVariant(newColor);
}
- try {
- if (!value->value().isValid()) { //reset
- qmlObjectNode.removeProperty(propertyName);
- } else {
- if (castedValue.isValid() && !castedValue.isNull()) {
- m_locked = true;
- qmlObjectNode.setVariantProperty(propertyName, castedValue);
- m_locked = false;
- }
+ if (!value->value().isValid()) { //reset
+ removePropertyFromModel(propertyName);
+ } else {
+ if (castedValue.isValid() && !castedValue.isNull()) {
+ commitVariantValueToModel(propertyName, castedValue);
}
}
- catch (const RewritingException &e) {
- e.showException();
- }
}
void PropertyEditorView::changeExpression(const QString &propertyName)
@@ -242,9 +235,7 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
if (!m_selectedNode.isValid())
return;
- RewriterTransaction transaction = beginRewriterTransaction(QByteArrayLiteral("PropertyEditorView::changeExpression"));
-
- try {
+ executeInTransaction("PropertyEditorView::changeExpression", [this, name](){
PropertyName underscoreName(name);
underscoreName.replace('.', '_');
@@ -260,7 +251,6 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "QColor") {
if (QColor(value->expression().remove('"')).isValid()) {
qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
- transaction.commit(); //committing in the try block
return;
}
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "bool") {
@@ -270,7 +260,6 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
qmlObjectNode.setVariantProperty(name, true);
else
qmlObjectNode.setVariantProperty(name, false);
- transaction.commit(); //committing in the try block
return;
}
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "int") {
@@ -278,7 +267,6 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
int intValue = value->expression().toInt(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, intValue);
- transaction.commit(); //committing in the try block
return;
}
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "qreal") {
@@ -286,7 +274,6 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
qreal realValue = value->expression().toDouble(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, realValue);
- transaction.commit(); //committing in the try block
return;
}
}
@@ -298,12 +285,7 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name))
qmlObjectNode.setBindingProperty(name, value->expression());
- transaction.commit(); //committing in the try block
- }
-
- catch (const RewritingException &e) {
- e.showException();
- }
+ }); /* end of transaction */
}
void PropertyEditorView::exportPopertyAsAlias(const QString &name)
@@ -317,9 +299,7 @@ void PropertyEditorView::exportPopertyAsAlias(const QString &name)
if (!m_selectedNode.isValid())
return;
- RewriterTransaction transaction = beginRewriterTransaction(QByteArrayLiteral("PropertyEditorView::exportPopertyAsAlias"));
-
- try {
+ executeInTransaction("PropertyEditorView::exportPopertyAsAlias", [this, name](){
const QString id = m_selectedNode.validId();
QString upperCasePropertyName = name;
upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper());
@@ -333,11 +313,7 @@ void PropertyEditorView::exportPopertyAsAlias(const QString &name)
return;
}
rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name);
-
- transaction.commit(); //committing in the try block
- } catch (const RewritingException &e) {
- e.showException();
- }
+ });
}
void PropertyEditorView::removeAliasExport(const QString &name)
@@ -351,9 +327,7 @@ void PropertyEditorView::removeAliasExport(const QString &name)
if (!m_selectedNode.isValid())
return;
- RewriterTransaction transaction = beginRewriterTransaction(QByteArrayLiteral("PropertyEditorView::exportPopertyAsAlias"));
-
- try {
+ executeInTransaction("PropertyEditorView::exportPopertyAsAlias", [this, name](){
const QString id = m_selectedNode.validId();
for (const BindingProperty &property : rootModelNode().bindingProperties())
@@ -361,10 +335,7 @@ void PropertyEditorView::removeAliasExport(const QString &name)
rootModelNode().removeProperty(property.name());
break;
}
- transaction.commit(); //committing in the try block
- } catch (const RewritingException &e) {
- e.showException();
- }
+ });
}
bool PropertyEditorView::locked() const
@@ -446,13 +417,16 @@ void PropertyEditorView::resetView()
void PropertyEditorView::setupQmlBackend()
{
TypeName specificsClassName;
- QUrl qmlFile(PropertyEditorQmlBackend::getQmlUrlForModelNode(m_selectedNode, specificsClassName));
+
+ const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(m_selectedNode);
+
+ const QUrl qmlFile(PropertyEditorQmlBackend::getQmlUrlForMetaInfo(commonAncestor, specificsClassName));
QUrl qmlSpecificsFile;
TypeName diffClassName;
- if (m_selectedNode.isValid()) {
- diffClassName = m_selectedNode.metaInfo().typeName();
- foreach (const NodeMetaInfo &metaInfo, m_selectedNode.metaInfo().classHierarchy()) {
+ if (commonAncestor.isValid()) {
+ diffClassName = commonAncestor.typeName();
+ foreach (const NodeMetaInfo &metaInfo, commonAncestor.classHierarchy()) {
if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsFile))
break;
qmlSpecificsFile = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() + "Specifics", metaInfo);
@@ -465,8 +439,8 @@ void PropertyEditorView::setupQmlBackend()
QString specificQmlData;
- if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type())
- specificQmlData = PropertyEditorQmlBackend::templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode);
+ if (commonAncestor.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type())
+ specificQmlData = PropertyEditorQmlBackend::templateGeneration(commonAncestor, model()->metaInfo(diffClassName), m_selectedNode);
PropertyEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlFile.toString());
@@ -515,14 +489,51 @@ void PropertyEditorView::setupQmlBackend()
}
+void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value)
+{
+ m_locked = true;
+ try {
+ RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::commitVariantValueToMode");
+
+ for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) {
+ if (QmlObjectNode::isValidQmlObjectNode(node))
+ QmlObjectNode(node).setVariantProperty(propertyName, value);
+ }
+ transaction.commit();
+ }
+ catch (const RewritingException &e) {
+ e.showException();
+ }
+ m_locked = false;
+}
+
+void PropertyEditorView::removePropertyFromModel(const PropertyName &propertyName)
+{
+ m_locked = true;
+ try {
+ RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::removePropertyFromModel");
+
+ for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) {
+ if (QmlObjectNode::isValidQmlObjectNode(node))
+ QmlObjectNode(node).removeProperty(propertyName);
+ }
+
+ transaction.commit();
+ }
+ catch (const RewritingException &e) {
+ e.showException();
+ }
+ m_locked = false;
+}
+
void PropertyEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList)
{
Q_UNUSED(lastSelectedNodeList);
- if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
+ if (selectedNodeList.isEmpty())
select(ModelNode());
- else if (m_selectedNode != selectedNodeList.constFirst())
+ else
select(selectedNodeList.constFirst());
}
@@ -542,10 +553,11 @@ void PropertyEditorView::modelAttached(Model *model)
m_locked = true;
if (!m_setupCompleted) {
- m_singleShotTimer->setSingleShot(true);
- m_singleShotTimer->setInterval(100);
- connect(m_singleShotTimer, &QTimer::timeout, this, &PropertyEditorView::setupPanes);
- m_singleShotTimer->start();
+ QTimer::singleShot(50, this, [this]{
+ PropertyEditorView::setupPanes();
+ /* workaround for QTBUG-75847 */
+ reloadQml();
+ });
}
m_locked = false;
@@ -648,6 +660,9 @@ void PropertyEditorView::instanceInformationsChanged(const QMultiHash<ModelNode,
if (!m_selectedNode.isValid())
return;
+ if (!m_qmlBackEndForCurrentType)
+ return;
+
m_locked = true;
QList<InformationName> informationNameList = informationChangedHash.values(m_selectedNode);
if (informationNameList.contains(Anchor)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h
index 85bd8286f5..e7f57cf186 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h
@@ -110,6 +110,9 @@ private: //functions
void delayedResetView();
void setupQmlBackend();
+ void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value);
+ void removePropertyFromModel(const PropertyName &propertyName);
+
private: //variables
ModelNode m_selectedNode;
QWidget *m_parent;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp
index bc6b4376b6..7a38f74f34 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp
@@ -292,9 +292,9 @@ void QmlAnchorBindingProxy::setDefaultRelativeRightTarget()
}
}
-RewriterTransaction QmlAnchorBindingProxy::beginRewriterTransaction(const QByteArray &identifier)
+bool QmlAnchorBindingProxy::executeInTransaction(const QByteArray &identifier, const AbstractView::OperationBlock &lambda)
{
- return m_qmlItemNode.modelNode().view()->beginRewriterTransaction(identifier);
+ return m_qmlItemNode.modelNode().view()->executeInTransaction(identifier, lambda);
}
bool QmlAnchorBindingProxy::hasParent() const
@@ -361,20 +361,11 @@ void QmlAnchorBindingProxy::setTopTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setTopTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setTopTarget", [this, newTarget](){
m_topTarget = newTarget;
-
setDefaultRelativeTopTarget();
-
anchorTop();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit topTargetChanged();
}
@@ -393,18 +384,12 @@ void QmlAnchorBindingProxy::setBottomTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setBottomTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setBottomTarget", [this, newTarget](){
m_bottomTarget = newTarget;
setDefaultRelativeBottomTarget();
anchorBottom();
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit bottomTargetChanged();
}
@@ -422,18 +407,11 @@ void QmlAnchorBindingProxy::setLeftTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setLeftTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setLeftTarget", [this, newTarget](){
m_leftTarget = newTarget;
setDefaultRelativeLeftTarget();
anchorLeft();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit leftTargetChanged();
}
@@ -451,18 +429,11 @@ void QmlAnchorBindingProxy::setRightTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRightTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRightTarget", [this, newTarget](){
m_rightTarget = newTarget;
setDefaultRelativeRightTarget();
anchorRight();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit rightTargetChanged();
}
@@ -480,17 +451,10 @@ void QmlAnchorBindingProxy::setVerticalTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setVerticalTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setVerticalTarget", [this, newTarget](){
m_verticalTarget = newTarget;
anchorVertical();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit verticalTargetChanged();
}
@@ -508,17 +472,10 @@ void QmlAnchorBindingProxy::setHorizontalTarget(const QString &target)
if (!newTarget.isValid())
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setHorizontalTarget"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setHorizontalTarget", [this, newTarget](){
m_horizontalTarget = newTarget;
- anchorHorizontal();\
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ anchorHorizontal();
+ });
emit horizontalTargetChanged();
}
@@ -531,18 +488,10 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetTop(QmlAnchorBindingProxy::Re
if (target == m_relativeTopTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetTop"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetTop", [this, target](){
m_relativeTopTarget = target;
-
anchorTop();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetTopChanged();
}
@@ -555,19 +504,10 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetBottom(QmlAnchorBindingProxy:
if (target == m_relativeBottomTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetBottom"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetBottom", [this, target](){
m_relativeBottomTarget = target;
-
-
anchorBottom();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetBottomChanged();
}
@@ -580,18 +520,11 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetLeft(QmlAnchorBindingProxy::R
if (target == m_relativeLeftTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetLeft"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetLeft", [this, target](){
m_relativeLeftTarget = target;
-
anchorLeft();
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetLeftChanged();
}
@@ -604,18 +537,10 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetRight(QmlAnchorBindingProxy::
if (target == m_relativeRightTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetRight"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetRight", [this, target](){
m_relativeRightTarget = target;
-
anchorRight();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetRightChanged();
@@ -629,18 +554,11 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetVertical(QmlAnchorBindingProx
if (target == m_relativeVerticalTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetVertical"));
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetVertical", [this, target](){
m_relativeVerticalTarget = target;
-
anchorVertical();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetVerticalChanged();
}
@@ -653,18 +571,10 @@ void QmlAnchorBindingProxy::setRelativeAnchorTargetHorizontal(QmlAnchorBindingPr
if (target == m_relativeHorizontalTarget)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRelativeAnchorTargetHorizontal"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRelativeAnchorTargetHorizontal", [this, target](){
m_relativeHorizontalTarget = target;
-
anchorHorizontal();
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetHorizontalChanged();
}
@@ -709,12 +619,10 @@ int QmlAnchorBindingProxy::indexOfPossibleTargetItem(const QString &targetName)
return possibleTargetItems().indexOf(targetName);
}
-void QmlAnchorBindingProxy::resetLayout() {
-
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::resetLayout"));
+void QmlAnchorBindingProxy::resetLayout()
+{
+ executeInTransaction("QmlAnchorBindingProxy::resetLayout", [this](){
m_qmlItemNode.anchors().removeAnchors();
m_qmlItemNode.anchors().removeMargins();
@@ -722,11 +630,7 @@ void QmlAnchorBindingProxy::resetLayout() {
restoreProperty(modelNode(), "y");
restoreProperty(modelNode(), "width");
restoreProperty(modelNode(), "height");
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit topAnchorChanged();
emit bottomAnchorChanged();
@@ -743,10 +647,7 @@ void QmlAnchorBindingProxy::setBottomAnchor(bool anchor)
if (bottomAnchored() == anchor)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setBottomAnchor"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setBottomAnchor", [this, anchor](){
if (!anchor) {
removeBottomAnchor();
} else {
@@ -756,10 +657,7 @@ void QmlAnchorBindingProxy::setBottomAnchor(bool anchor)
backupPropertyAndRemove(modelNode(), "height");
}
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetBottomChanged();
emit bottomAnchorChanged();
@@ -776,10 +674,8 @@ void QmlAnchorBindingProxy::setLeftAnchor(bool anchor)
if (leftAnchored() == anchor)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setLeftAnchor"));
+ executeInTransaction("QmlAnchorBindingProxy::setLeftAnchor", [this, anchor](){
if (!anchor) {
removeLeftAnchor();
} else {
@@ -791,10 +687,7 @@ void QmlAnchorBindingProxy::setLeftAnchor(bool anchor)
backupPropertyAndRemove(modelNode(), "width");
}
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetLeftChanged();
emit leftAnchorChanged();
@@ -810,10 +703,7 @@ void QmlAnchorBindingProxy::setRightAnchor(bool anchor)
if (rightAnchored() == anchor)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setRightAnchor"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setRightAnchor", [this, anchor](){
if (!anchor) {
removeRightAnchor();
} else {
@@ -824,10 +714,7 @@ void QmlAnchorBindingProxy::setRightAnchor(bool anchor)
backupPropertyAndRemove(modelNode(), "width");
}
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetRightChanged();
emit rightAnchorChanged();
@@ -1026,10 +913,7 @@ void QmlAnchorBindingProxy::setTopAnchor(bool anchor)
if (topAnchored() == anchor)
return;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setTopAnchor"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setTopAnchor", [this, anchor](){
if (!anchor) {
removeTopAnchor();
} else {
@@ -1040,10 +924,7 @@ void QmlAnchorBindingProxy::setTopAnchor(bool anchor)
if (bottomAnchored())
backupPropertyAndRemove(modelNode(), "height");
}
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit relativeAnchorTargetTopChanged();
emit topAnchorChanged();
@@ -1052,70 +933,44 @@ void QmlAnchorBindingProxy::setTopAnchor(bool anchor)
}
void QmlAnchorBindingProxy::removeTopAnchor() {
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::removeTopAnchor"));
-
+ executeInTransaction("QmlAnchorBindingProxy::removeTopAnchor", [this](){
m_qmlItemNode.anchors().removeAnchor(AnchorLineTop);
m_qmlItemNode.anchors().removeMargin(AnchorLineTop);
restoreProperty(modelNode(), "y");
restoreProperty(modelNode(), "height");
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
}
-void QmlAnchorBindingProxy::removeBottomAnchor() {
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::removeBottomAnchor"));
-
+void QmlAnchorBindingProxy::removeBottomAnchor()
+{
+ executeInTransaction("QmlAnchorBindingProxy::removeBottomAnchor", [this](){
m_qmlItemNode.anchors().removeAnchor(AnchorLineBottom);
m_qmlItemNode.anchors().removeMargin(AnchorLineBottom);
-
restoreProperty(modelNode(), "height");
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
}
-void QmlAnchorBindingProxy::removeLeftAnchor() {
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::removeLeftAnchor"));
-
+void QmlAnchorBindingProxy::removeLeftAnchor()
+{
+ executeInTransaction("QmlAnchorBindingProxy::removeLeftAnchor", [this](){
m_qmlItemNode.anchors().removeAnchor(AnchorLineLeft);
m_qmlItemNode.anchors().removeMargin(AnchorLineLeft);
restoreProperty(modelNode(), "x");
restoreProperty(modelNode(), "width");
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
}
-void QmlAnchorBindingProxy::removeRightAnchor() {
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::removeRightAnchor"));
-
+void QmlAnchorBindingProxy::removeRightAnchor()
+{
+ executeInTransaction("QmlAnchorBindingProxy::removeRightAnchor", [this](){
m_qmlItemNode.anchors().removeAnchor(AnchorLineRight);
m_qmlItemNode.anchors().removeMargin(AnchorLineRight);
restoreProperty(modelNode(), "width");
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
}
void QmlAnchorBindingProxy::setVerticalCentered(bool centered)
@@ -1128,10 +983,7 @@ void QmlAnchorBindingProxy::setVerticalCentered(bool centered)
m_locked = true;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setVerticalCentered"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setVerticalCentered", [this, centered](){
if (!centered) {
m_qmlItemNode.anchors().removeAnchor(AnchorLineVerticalCenter);
m_qmlItemNode.anchors().removeMargin(AnchorLineVerticalCenter);
@@ -1141,10 +993,7 @@ void QmlAnchorBindingProxy::setVerticalCentered(bool centered)
anchorVertical();
}
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
m_locked = false;
emit relativeAnchorTargetVerticalChanged();
@@ -1161,10 +1010,7 @@ void QmlAnchorBindingProxy::setHorizontalCentered(bool centered)
m_locked = true;
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::setHorizontalCentered"));
-
+ executeInTransaction("QmlAnchorBindingProxy::setHorizontalCentered", [this, centered](){
if (!centered) {
m_qmlItemNode.anchors().removeAnchor(AnchorLineHorizontalCenter);
m_qmlItemNode.anchors().removeMargin(AnchorLineHorizontalCenter);
@@ -1173,11 +1019,7 @@ void QmlAnchorBindingProxy::setHorizontalCentered(bool centered)
anchorHorizontal();
}
-
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
m_locked = false;
emit relativeAnchorTargetHorizontalChanged();
@@ -1256,12 +1098,7 @@ bool QmlAnchorBindingProxy::horizontalCentered()
void QmlAnchorBindingProxy::fill()
{
-
- try {
- RewriterTransaction transaction = beginRewriterTransaction(
- QByteArrayLiteral("QmlAnchorBindingProxy::fill"));
-
-
+ executeInTransaction("QmlAnchorBindingProxy::fill", [this](){
backupPropertyAndRemove(modelNode(), "x");
backupPropertyAndRemove(modelNode(), "y");
backupPropertyAndRemove(modelNode(), "width");
@@ -1277,10 +1114,7 @@ void QmlAnchorBindingProxy::fill()
m_qmlItemNode.anchors().removeMargin(AnchorLineTop);
m_qmlItemNode.anchors().removeMargin(AnchorLineBottom);
- transaction.commit();
- } catch (const Exception &e) {
- e.showException();
- }
+ });
emit topAnchorChanged();
emit bottomAnchorChanged();
diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h
index cf42ea055a..0bd562add2 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h
@@ -210,7 +210,7 @@ private:
void setDefaultRelativeLeftTarget();
void setDefaultRelativeRightTarget();
- RewriterTransaction beginRewriterTransaction(const QByteArray &identifier);
+ bool executeInTransaction(const QByteArray &identifier, const AbstractView::OperationBlock &lambda);
QmlItemNode targetIdToNode(const QString &id) const;
QString idForNode(const QmlItemNode &qmlItemNode) const;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
index 6f56b055c8..934c284691 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
@@ -23,6 +23,7 @@
**
****************************************************************************/
+#include "abstractview.h"
#include "qmlmodelnodeproxy.h"
#include <QtQml>
@@ -66,4 +67,34 @@ ModelNode QmlModelNodeProxy::modelNode() const
return m_qmlItemNode.modelNode();
}
+bool QmlModelNodeProxy::multiSelection() const
+{
+ if (!m_qmlItemNode.isValid())
+ return false;
+
+ return m_qmlItemNode.view()->selectedModelNodes().count() > 1;
+}
+
+QString QmlModelNodeProxy::nodeId() const
+{
+ if (!m_qmlItemNode.isValid())
+ return {};
+
+ if (multiSelection())
+ return tr("multiselection");
+
+ return m_qmlItemNode.id();
+}
+
+QString QmlModelNodeProxy::simplifiedTypeName() const
+{
+ if (!m_qmlItemNode.isValid())
+ return {};
+
+ if (multiSelection())
+ return tr("multiselection");
+
+ return m_qmlItemNode.simplifiedTypeName();
+}
+
}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h
index 0a73583355..6037f32752 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h
@@ -35,7 +35,8 @@ class QmlModelNodeProxy : public QObject
{
Q_OBJECT
- Q_PROPERTY(QmlDesigner::ModelNode modelNode READ modelNode NOTIFY modelNodeChanged)
+ Q_PROPERTY(QmlDesigner::ModelNode modelNode READ modelNode NOTIFY modelNodeChanged)
+ Q_PROPERTY(bool multiSelection READ multiSelection NOTIFY modelNodeChanged)
public:
explicit QmlModelNodeProxy(QObject *parent = nullptr);
@@ -51,6 +52,12 @@ public:
ModelNode modelNode() const;
+ bool multiSelection() const;
+
+ QString nodeId() const;
+
+ QString simplifiedTypeName() const;
+
signals:
void modelNodeChanged();
void selectionToBeChanged();
diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp
index 4e58374759..862d16ba22 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp
@@ -28,6 +28,9 @@
#include "propertyeditorvalue.h"
#include "fileresourcesmodel.h"
#include "gradientmodel.h"
+#include "gradientpresetdefaultlistmodel.h"
+#include "gradientpresetcustomlistmodel.h"
+#include "simplecolorpalettemodel.h"
#include "qmlanchorbindingproxy.h"
#include "theme.h"
@@ -48,6 +51,9 @@ void Quick2PropertyEditorView::registerQmlTypes()
PropertyEditorValue::registerDeclarativeTypes();
FileResourcesModel::registerDeclarativeType();
GradientModel::registerDeclarativeType();
+ GradientPresetDefaultListModel::registerDeclarativeType();
+ GradientPresetCustomListModel::registerDeclarativeType();
+ SimpleColorPaletteModel::registerDeclarativeType();
Internal::QmlAnchorBindingProxy::registerDeclarativeType();
}
}
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.cpp b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.cpp
new file mode 100644
index 0000000000..c88d83aa0a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "simplecolorpalette.h"
+
+#include "designersettings.h"
+
+#include <QDebug>
+
+namespace QmlDesigner {
+
+PaletteColor::PaletteColor()
+ : m_color(QColor())
+ , m_colorCode(QColor().name())
+ , m_isFavorite(false)
+{}
+
+PaletteColor::PaletteColor(const QString &colorCode)
+ : m_color(colorCode)
+ , m_colorCode(colorCode)
+ , m_isFavorite(false)
+{}
+
+PaletteColor::PaletteColor(const QColor &color)
+ : m_color(color)
+ , m_colorCode(color.name(QColor::HexArgb))
+ , m_isFavorite(false)
+{}
+
+QVariant PaletteColor::getProperty(Property id) const
+{
+ QVariant out;
+
+ switch (id) {
+ case objectNameRole:
+ out.setValue(QString());
+ break;
+ case colorRole:
+ out.setValue(color());
+ break;
+ case colorCodeRole:
+ out.setValue(colorCode());
+ break;
+ case isFavoriteRole:
+ out.setValue(isFavorite());
+ break;
+ default:
+ qWarning() << "PaletteColor Property switch default case";
+ break; //replace with assert before switch?
+ }
+
+ return out;
+}
+
+QColor PaletteColor::color() const
+{
+ return m_color;
+}
+
+void PaletteColor::setColor(const QColor &value)
+{
+ m_color = value;
+ m_colorCode = m_color.name(QColor::HexArgb);
+}
+
+QString PaletteColor::colorCode() const
+{
+ return m_colorCode;
+}
+
+bool PaletteColor::isFavorite() const
+{
+ return m_isFavorite;
+}
+
+void PaletteColor::setFavorite(bool favorite)
+{
+ m_isFavorite = favorite;
+}
+
+bool PaletteColor::toggleFavorite()
+{
+ return m_isFavorite = !m_isFavorite;
+}
+
+bool PaletteColor::operator==(const PaletteColor &other) const
+{
+ return (m_color == other.m_color);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.h b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.h
new file mode 100644
index 0000000000..342c9832e7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalette.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QtQml/qqml.h>
+#include <QColor>
+
+namespace QmlDesigner {
+
+class PaletteColor
+{
+ Q_GADGET
+
+ Q_PROPERTY(QColor color READ color FINAL)
+ Q_PROPERTY(QString colorCode READ colorCode FINAL)
+ Q_PROPERTY(bool isFavorite READ isFavorite FINAL)
+public:
+ PaletteColor();
+ PaletteColor(const QString &colorCode);
+ PaletteColor(const QColor &value);
+ ~PaletteColor() = default;
+
+ enum Property {
+ objectNameRole = 0,
+ colorRole = 1,
+ colorCodeRole = 2,
+ isFavoriteRole = 3
+ };
+
+ QVariant getProperty(Property id) const;
+
+ QColor color() const;
+ void setColor(const QColor &value);
+
+ QString colorCode() const;
+
+ bool isFavorite() const;
+ void setFavorite(bool favorite);
+ bool toggleFavorite();
+
+ bool operator==(const PaletteColor &other) const;
+
+private:
+ QColor m_color;
+ QString m_colorCode;
+ bool m_isFavorite;
+};
+
+} // namespace QmlDesigner
+
+Q_DECLARE_METATYPE(QmlDesigner::PaletteColor)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.cpp
new file mode 100644
index 0000000000..b3207f0006
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "simplecolorpalettemodel.h"
+#include "simplecolorpalette.h"
+#include "simplecolorpalettesingleton.h"
+
+#include "designersettings.h"
+
+#include <QHash>
+#include <QByteArray>
+#include <QDebug>
+#include <QSettings>
+
+namespace QmlDesigner {
+
+SimpleColorPaletteModel::SimpleColorPaletteModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ connect(&SimpleColorPaletteSingleton::getInstance(),
+ &SimpleColorPaletteSingleton::paletteChanged,
+ this,
+ &SimpleColorPaletteModel::setPalette);
+ m_roleNames = {{static_cast<int>(PaletteColor::Property::objectNameRole), "objectName"},
+ {static_cast<int>(PaletteColor::Property::colorRole), "color"},
+ {static_cast<int>(PaletteColor::Property::colorCodeRole), "colorCode"},
+ {static_cast<int>(PaletteColor::Property::isFavoriteRole), "isFavorite"}};
+
+ setPalette();
+}
+
+SimpleColorPaletteModel::~SimpleColorPaletteModel()
+{
+ clearItems();
+}
+
+int SimpleColorPaletteModel::rowCount(const QModelIndex & /*parent*/) const
+{
+ return m_items.count();
+}
+
+QVariant SimpleColorPaletteModel::data(const QModelIndex &index, int role) const
+{
+ if (index.isValid() && (index.row() >= 0) && (index.row() < m_items.count())) {
+ if (m_roleNames.contains(role)) {
+ QVariant value = m_items.at(index.row())
+ .getProperty(static_cast<PaletteColor::Property>(role));
+ if (auto model = qobject_cast<SimpleColorPaletteModel *>(value.value<QObject *>()))
+ return QVariant::fromValue(model);
+
+ return value;
+ }
+
+ qWarning() << Q_FUNC_INFO << "invalid role requested";
+ return QVariant();
+ }
+
+ qWarning() << Q_FUNC_INFO << "invalid index requested";
+ return QVariant();
+}
+
+QHash<int, QByteArray> SimpleColorPaletteModel::roleNames() const
+{
+ return m_roleNames;
+}
+
+void SimpleColorPaletteModel::clearItems()
+{
+ beginResetModel();
+ m_items.clear();
+ endResetModel();
+}
+
+void SimpleColorPaletteModel::addItem(const QString &item)
+{
+ PaletteColor palette(item);
+ addItem(palette);
+}
+
+void SimpleColorPaletteModel::addItem(const PaletteColor &item)
+{
+ SimpleColorPaletteSingleton::getInstance().addItem(item);
+}
+
+const QList<PaletteColor> &SimpleColorPaletteModel::items() const
+{
+ return m_items;
+}
+
+void SimpleColorPaletteModel::sortItems()
+{
+ SimpleColorPaletteSingleton::getInstance().sortItems();
+}
+
+void SimpleColorPaletteModel::registerDeclarativeType()
+{
+ qmlRegisterType<SimpleColorPaletteModel>("HelperWidgets", 2, 0, "SimpleColorPaletteModel");
+}
+
+void SimpleColorPaletteModel::toggleFavorite(int id)
+{
+ SimpleColorPaletteSingleton::getInstance().toggleFavorite(id);
+}
+
+void SimpleColorPaletteModel::setPalette()
+{
+ beginResetModel();
+ m_items = SimpleColorPaletteSingleton::getInstance().getItems();
+ m_favoriteOffset = SimpleColorPaletteSingleton::getInstance().getFavoriteOffset();
+ m_paletteSize = SimpleColorPaletteSingleton::getInstance().getPaletteSize();
+ endResetModel();
+}
+
+bool SimpleColorPaletteModel::read()
+{
+ return SimpleColorPaletteSingleton::getInstance().readPalette();
+}
+
+void SimpleColorPaletteModel::write()
+{
+ SimpleColorPaletteSingleton::getInstance().writePalette();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.h b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.h
new file mode 100644
index 0000000000..3ed2cc8a31
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettemodel.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QtQml/qqml.h>
+#include <QList>
+
+namespace QmlDesigner {
+
+class PaletteColor;
+
+class SimpleColorPaletteModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ explicit SimpleColorPaletteModel(QObject *parent = nullptr);
+ ~SimpleColorPaletteModel() override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ void clearItems();
+ Q_INVOKABLE void addItem(const QString &item);
+ void addItem(const PaletteColor &item);
+
+ const QList<PaletteColor> &items() const;
+
+ void sortItems();
+
+ static void registerDeclarativeType();
+
+ Q_INVOKABLE void toggleFavorite(int id);
+
+ bool read();
+ void write();
+
+private slots:
+ void setPalette();
+
+private:
+ void enqueue(const PaletteColor &item);
+
+private:
+ int m_paletteSize;
+ int m_favoriteOffset;
+ QList<PaletteColor> m_items;
+ QHash<int, QByteArray> m_roleNames;
+};
+
+} // namespace QmlDesigner
+
+QML_DECLARE_TYPE(QmlDesigner::SimpleColorPaletteModel)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.cpp b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.cpp
new file mode 100644
index 0000000000..ccea50bbf2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.cpp
@@ -0,0 +1,185 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "simplecolorpalettesingleton.h"
+#include "simplecolorpalette.h"
+
+#include "designersettings.h"
+
+#include <QDebug>
+#include <QSettings>
+
+namespace QmlDesigner {
+
+SimpleColorPaletteSingleton::SimpleColorPaletteSingleton()
+ : m_items()
+ , m_favoriteOffset(0)
+{
+ if (!readPalette()) {
+ for (int i = 0; i < m_paletteSize; i++)
+ m_items.append(PaletteColor());
+ }
+}
+
+SimpleColorPaletteSingleton &SimpleColorPaletteSingleton::getInstance()
+{
+ static SimpleColorPaletteSingleton singleton;
+
+ return singleton;
+}
+
+void SimpleColorPaletteSingleton::addItem(const PaletteColor &item)
+{
+ if (m_favoriteOffset >= m_paletteSize)
+ return;
+
+ if (item.isFavorite()) {
+ int contains = m_items.indexOf(item);
+ if (contains != -1) {
+ if (m_items.at(contains).isFavorite())
+ return;
+ else
+ m_items.removeAt(contains);
+ }
+ m_items.insert(0, item);
+ m_favoriteOffset++;
+ } else if (m_items.contains(item))
+ return;
+ else
+ m_items.insert(m_favoriteOffset, item);
+
+ while (m_items.size() > m_paletteSize) {
+ m_items.removeLast();
+ }
+
+ writePalette();
+
+ emit paletteChanged();
+}
+
+QList<PaletteColor> SimpleColorPaletteSingleton::getItems() const
+{
+ return m_items;
+}
+
+int SimpleColorPaletteSingleton::getPaletteSize() const
+{
+ return m_paletteSize;
+}
+
+int SimpleColorPaletteSingleton::getFavoriteOffset() const
+{
+ return m_favoriteOffset;
+}
+
+void SimpleColorPaletteSingleton::sortItems()
+{
+ auto itemSort = [](const PaletteColor &first, const PaletteColor &second) {
+ return (static_cast<int>(first.isFavorite()) < static_cast<int>(second.isFavorite()));
+ };
+
+ std::sort(m_items.begin(), m_items.end(), itemSort);
+
+ emit paletteChanged();
+}
+
+void SimpleColorPaletteSingleton::toggleFavorite(int id)
+{
+ bool toggleResult = m_items[id].toggleFavorite();
+
+ if (toggleResult) {
+ m_favoriteOffset++;
+ m_items.move(id, 0);
+ } else {
+ m_favoriteOffset--;
+ m_items.move(id, m_favoriteOffset);
+ }
+
+ if (m_favoriteOffset < 0)
+ m_favoriteOffset = 0;
+ else if (m_favoriteOffset > m_paletteSize)
+ m_favoriteOffset = m_paletteSize;
+
+ emit paletteChanged();
+}
+
+bool SimpleColorPaletteSingleton::readPalette()
+{
+ QList<PaletteColor> proxy;
+ const QStringList stringData = QmlDesigner::DesignerSettings::getValue(
+ QmlDesigner::DesignerSettingsKey::SIMPLE_COLOR_PALETTE_CONTENT)
+ .toStringList();
+
+ int favCounter = 0;
+
+ for (int i = 0; i < stringData.size(); i++) {
+ const QStringList strsep = stringData.at(i).split(";");
+ if (strsep.size() != 2) {
+ continue;
+ }
+ PaletteColor colorItem(strsep.at(0));
+ bool isFav = static_cast<bool>(strsep.at(1).toInt());
+ colorItem.setFavorite(isFav);
+ if (isFav)
+ favCounter++;
+ proxy.append(colorItem);
+ }
+
+ if (proxy.size() == 0) {
+ return false;
+ }
+
+ while (proxy.size() > m_paletteSize) {
+ proxy.removeLast();
+ }
+ while (proxy.size() < m_paletteSize) {
+ proxy.append(PaletteColor());
+ }
+
+ m_items.clear();
+ m_items = proxy;
+ m_favoriteOffset = favCounter;
+
+ return true;
+}
+
+void SimpleColorPaletteSingleton::writePalette()
+{
+ QStringList output;
+ QString subres;
+
+ for (int i = 0; i < m_items.size(); i++) {
+ subres = m_items.at(i).color().name(QColor::HexArgb);
+ subres += ";";
+ subres += QString::number(static_cast<int>(m_items.at(i).isFavorite()));
+ output.push_back(subres);
+ subres.clear();
+ }
+
+ QmlDesigner::DesignerSettings::setValue(
+ QmlDesigner::DesignerSettingsKey::SIMPLE_COLOR_PALETTE_CONTENT, output);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.h b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.h
new file mode 100644
index 0000000000..77ad2b0732
--- /dev/null
+++ b/src/plugins/qmldesigner/components/propertyeditor/simplecolorpalettesingleton.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QAbstractListModel>
+#include <QtQml/qqml.h>
+#include <QList>
+#include <QColor>
+#include <simplecolorpalette.h>
+
+namespace QmlDesigner {
+
+class SimpleColorPaletteSingleton : public QObject
+{
+ Q_OBJECT
+public:
+ static SimpleColorPaletteSingleton &getInstance();
+
+ bool readPalette();
+ void writePalette();
+
+ void addItem(const PaletteColor &item);
+ QList<PaletteColor> getItems() const;
+
+ int getPaletteSize() const;
+ int getFavoriteOffset() const;
+
+ void sortItems();
+
+ void toggleFavorite(int id);
+
+ SimpleColorPaletteSingleton(const SimpleColorPaletteSingleton &) = delete;
+ void operator=(const SimpleColorPaletteSingleton &) = delete;
+
+signals:
+ void paletteChanged();
+
+private:
+ SimpleColorPaletteSingleton();
+
+private:
+ QList<PaletteColor> m_items;
+ const int m_paletteSize = 6;
+ int m_favoriteOffset;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp
new file mode 100644
index 0000000000..d121e60c73
--- /dev/null
+++ b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp
@@ -0,0 +1,252 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sourcetool.h"
+
+#include "formeditorscene.h"
+#include "formeditorview.h"
+#include "formeditorwidget.h"
+#include "itemutilfunctions.h"
+#include "formeditoritem.h"
+
+#include "resizehandleitem.h"
+
+#include "nodemetainfo.h"
+#include "qmlitemnode.h"
+#include <qmldesignerplugin.h>
+
+#include <abstractaction.h>
+
+#include <utils/icon.h>
+
+#include <QApplication>
+#include <QGraphicsSceneMouseEvent>
+#include <QAction>
+#include <QDebug>
+#include <QPair>
+#include <QUrl>
+
+namespace {
+
+bool modelNodeHasUrlSource(const QmlDesigner::ModelNode &modelNode)
+{
+ QmlDesigner::NodeMetaInfo metaInfo = modelNode.metaInfo();
+ if (metaInfo.isValid()) {
+ if (metaInfo.hasProperty("source")) {
+ if (metaInfo.propertyTypeName("source") == "QUrl")
+ return true;
+ if (metaInfo.propertyTypeName("source") == "url")
+ return true;
+ }
+ }
+ return false;
+}
+
+} //namespace
+
+namespace QmlDesigner {
+
+class SourceToolAction : public AbstractAction
+{
+public:
+ SourceToolAction() : AbstractAction(QCoreApplication::translate("SourceToolAction","Change Source URL..."))
+ {
+ const Utils::Icon prevIcon({
+ {QLatin1String(":/utils/images/fileopen.png"), Utils::Theme::OutputPanes_NormalMessageTextColor}}, Utils::Icon::MenuTintedStyle);
+
+ action()->setIcon(prevIcon.icon());
+ }
+
+ QByteArray category() const override
+ {
+ return QByteArray();
+ }
+
+ QByteArray menuId() const override
+ {
+ return "SourceTool";
+ }
+
+ int priority() const override
+ {
+ return CustomActionsPriority;
+ }
+
+ Type type() const override
+ {
+ return FormEditorAction;
+ }
+
+protected:
+ bool isVisible(const SelectionContext &selectionContext) const override
+ {
+ if (selectionContext.singleNodeIsSelected())
+ return modelNodeHasUrlSource(selectionContext.currentSingleSelectedNode());
+ return false;
+ }
+
+ bool isEnabled(const SelectionContext &selectionContext) const override
+ {
+ return isVisible(selectionContext);
+ }
+};
+
+
+SourceTool::SourceTool()
+{
+ auto sourceToolAction = new SourceToolAction;
+ QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(sourceToolAction);
+ connect(sourceToolAction->action(), &QAction::triggered, [=]() {
+ view()->changeCurrentToolTo(this);
+ });
+}
+
+SourceTool::~SourceTool() = default;
+
+void SourceTool::clear()
+{
+ AbstractFormEditorTool::clear();
+}
+
+void SourceTool::mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mousePressEvent(itemList, event);
+}
+
+void SourceTool::mouseMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * /*event*/)
+{
+}
+
+void SourceTool::hoverMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * /*event*/)
+{
+}
+
+void SourceTool::keyPressEvent(QKeyEvent * /*keyEvent*/)
+{
+}
+
+void SourceTool::keyReleaseEvent(QKeyEvent * /*keyEvent*/)
+{
+}
+
+void SourceTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+}
+
+void SourceTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+}
+
+void SourceTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mouseReleaseEvent(itemList, event);
+}
+
+void SourceTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, QGraphicsSceneMouseEvent *event)
+{
+ AbstractFormEditorTool::mouseDoubleClickEvent(itemList, event);
+}
+
+void SourceTool::itemsAboutToRemoved(const QList<FormEditorItem*> &removedItemList)
+{
+ if (removedItemList.contains(m_formEditorItem))
+ view()->changeToSelectionTool();
+}
+
+static QString baseDirectory(const QUrl &url)
+{
+ QString filePath = url.toLocalFile();
+ return QFileInfo(filePath).absoluteDir().path();
+}
+
+void SourceTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList)
+{
+ if (!itemList.isEmpty()) {
+ m_formEditorItem = itemList.constFirst();
+ m_oldFileName = m_formEditorItem->qmlItemNode().modelValue("source").toString();
+
+ QString openDirectory = baseDirectory(view()->model()->fileUrl());
+ if (openDirectory.isEmpty())
+ openDirectory = baseDirectory(view()->model()->fileUrl());
+
+ QString fileName = QFileDialog::getOpenFileName(nullptr,
+ tr("Open File"),
+ openDirectory);
+ fileSelected(fileName);
+
+ } else {
+ view()->changeToSelectionTool();
+ }
+}
+
+void SourceTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+void SourceTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/)
+{
+}
+
+void SourceTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > & /*propertyList*/)
+{
+}
+
+void SourceTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+int SourceTool::wantHandleItem(const ModelNode &modelNode) const
+{
+ if (modelNodeHasUrlSource(modelNode))
+ return 15;
+
+ return 0;
+}
+
+QString SourceTool::name() const
+{
+ return tr("Source Tool");
+}
+
+void SourceTool::fileSelected(const QString &fileName)
+{
+ if (m_formEditorItem
+ && QFileInfo(fileName).isFile()) {
+ QString modelFilePath = view()->model()->fileUrl().toLocalFile();
+ QDir modelFileDirectory = QFileInfo(modelFilePath).absoluteDir();
+ QString relativeFilePath = modelFileDirectory.relativeFilePath(fileName);
+ if (m_oldFileName != relativeFilePath) {
+ m_formEditorItem->qmlItemNode().setVariantProperty("source", relativeFilePath);
+ }
+ }
+
+ view()->changeToSelectionTool();
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/sourcetool/sourcetool.h b/src/plugins/qmldesigner/components/sourcetool/sourcetool.h
new file mode 100644
index 0000000000..16797b3912
--- /dev/null
+++ b/src/plugins/qmldesigner/components/sourcetool/sourcetool.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <abstractcustomtool.h>
+#include "selectionindicator.h"
+
+#include <QHash>
+#include <QPointer>
+#include <QFileDialog>
+
+namespace QmlDesigner {
+
+class SelectionContext;
+
+class SourceTool : public QObject, public AbstractCustomTool
+{
+ Q_OBJECT
+public:
+ SourceTool();
+ ~SourceTool() override;
+
+ void mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+ void dragLeaveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+ void dragMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+
+ void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override;
+
+ void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ void instancesCompleted(const QList<FormEditorItem*> &itemList) override;
+ void instancesParentChanged(const QList<FormEditorItem *> &itemList) override;
+ void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override;
+
+ void clear() override;
+
+ void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ int wantHandleItem(const ModelNode &modelNode) const override;
+
+ QString name() const override;
+
+private:
+ /* private methods */
+ void fileSelected(const QString &fileName);
+
+
+ /* members */
+ FormEditorItem *m_formEditorItem = nullptr;
+ QString m_oldFileName;
+};
+
+}
diff --git a/src/plugins/qmldesigner/components/sourcetool/sourcetool.pri b/src/plugins/qmldesigner/components/sourcetool/sourcetool.pri
new file mode 100644
index 0000000000..8117fd73e9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/sourcetool/sourcetool.pri
@@ -0,0 +1,3 @@
+HEADERS += $$PWD/sourcetool.h
+
+SOURCES += $$PWD/sourcetool.cpp
diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp
index 07e758b35a..ae7400e382 100644
--- a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp
+++ b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp
@@ -185,6 +185,7 @@ void StatesEditorModel::renameState(int internalNodeId, const QString &newName)
newName.isEmpty() ?
tr("The empty string as a name is reserved for the base state.") :
tr("Name already used in another state"));
+ reset();
} else {
m_statesEditorView->renameState(internalNodeId, newName);
}
diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.h b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.h
index 2f8bc56650..572b454892 100644
--- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.h
+++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.h
@@ -31,7 +31,6 @@
namespace QmlDesigner {
-
class StatesEditorModel;
class StatesEditorWidget;
diff --git a/src/plugins/qmldesigner/components/texttool/textedititem.cpp b/src/plugins/qmldesigner/components/texttool/textedititem.cpp
new file mode 100644
index 0000000000..1ce3244cbc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/textedititem.cpp
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "textedititem.h"
+
+#include <formeditorscene.h>
+#include <nodemetainfo.h>
+#include <rewritingexception.h>
+
+#include <QLineEdit>
+#include <QTextEdit>
+
+namespace QmlDesigner {
+
+TextEditItem::TextEditItem(FormEditorScene* scene)
+ : TextEditItemWidget(scene)
+ , m_formEditorItem(nullptr)
+{
+ connect(lineEdit(), &QLineEdit::returnPressed, this, &TextEditItem::returnPressed);
+}
+
+TextEditItem::~TextEditItem()
+{
+ m_formEditorItem = nullptr;
+}
+
+void TextEditItem::writeTextToProperty()
+{
+ if (m_formEditorItem) {
+ try {
+ if (text().isEmpty())
+ m_formEditorItem->qmlItemNode().removeProperty("text");
+ else if (m_formEditorItem->qmlItemNode().isTranslatableText("text"))
+ m_formEditorItem->qmlItemNode().setBindingProperty("text", QmlObjectNode::generateTranslatableText(text()));
+ else
+ m_formEditorItem->qmlItemNode().setVariantProperty("text", text());
+ }
+ catch (const RewritingException &e) {
+ e.showException();
+ }
+ }
+}
+
+void TextEditItem::setFormEditorItem(FormEditorItem *formEditorItem)
+{
+ m_formEditorItem = formEditorItem;
+ QRectF rect = formEditorItem->qmlItemNode().instancePaintedBoundingRect().united(formEditorItem->qmlItemNode().instanceBoundingRect()).adjusted(-12, -4, 12 ,4);
+ setGeometry(rect);
+
+ NodeMetaInfo metaInfo = m_formEditorItem->qmlItemNode().modelNode().metaInfo();
+ if (metaInfo.isValid() &&
+ (metaInfo.isSubclassOf("QtQuick.TextEdit")
+ || metaInfo.isSubclassOf("QtQuick.Controls.TextArea"))) {
+ QSize maximumSize = rect.size().toSize();
+ activateTextEdit(maximumSize);
+ } else {
+ activateLineEdit();
+ }
+
+ setTransform(formEditorItem->sceneTransform());
+ updateText();
+}
+
+FormEditorItem *TextEditItem::formEditorItem() const
+{
+ return m_formEditorItem;
+}
+
+void TextEditItem::updateText()
+{
+ if (formEditorItem()) {
+ TextEditItemWidget::updateText(formEditorItem()->qmlItemNode().
+ stripedTranslatableText("text"));
+ }
+}
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/texttool/textedititem.h b/src/plugins/qmldesigner/components/texttool/textedititem.h
new file mode 100644
index 0000000000..5497dadbe1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/textedititem.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#pragma once
+
+#include "textedititemwidget.h"
+
+namespace QmlDesigner {
+
+class FormEditorScene;
+class FormEditorItem;
+
+class TextEditItem : public TextEditItemWidget
+{
+ Q_OBJECT
+public:
+ TextEditItem(FormEditorScene* scene);
+ ~TextEditItem() override;
+ int type() const override;
+
+ void setFormEditorItem(FormEditorItem *formEditorItem);
+ FormEditorItem *formEditorItem() const;
+
+ void updateText();
+ void writeTextToProperty();
+
+signals:
+ void returnPressed();
+private:
+ FormEditorItem *m_formEditorItem;
+};
+
+inline int TextEditItem::type() const
+{
+ return 0xEAAB;
+}
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp
new file mode 100644
index 0000000000..40efb67e77
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "textedititemwidget.h"
+
+#include <utils/theme/theme.h>
+
+#include <QLineEdit>
+#include <QGraphicsScene>
+#include <QPainter>
+#include <QTextEdit>
+
+namespace QmlDesigner {
+
+TextEditItemWidget::TextEditItemWidget(QGraphicsScene* scene)
+{
+ scene->addItem(this);
+ setFlag(QGraphicsItem::ItemIsMovable, false);
+ activateLineEdit();
+}
+
+TextEditItemWidget::~TextEditItemWidget()
+{
+ setWidget(nullptr);
+}
+
+void TextEditItemWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
+{
+ painter->fillRect(boundingRect(), Qt::white);
+
+ /* Cursor painting is broken.
+ * QGraphicsProxyWidget::paint(painter, option, widget);
+ * We draw manually instead.
+ */
+
+ QPixmap pixmap = widget()->grab();
+ painter->drawPixmap(0, 0, pixmap);
+}
+
+QLineEdit* TextEditItemWidget::lineEdit() const
+{
+ if (m_lineEdit.isNull()) {
+ m_lineEdit.reset(new QLineEdit);
+ m_lineEdit->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+ QPalette palette = m_lineEdit->palette();
+ static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ palette.setColor(QPalette::Highlight, selectionColor);
+ palette.setColor(QPalette::HighlightedText, Qt::white);
+ palette.setColor(QPalette::Base, Qt::white);
+ palette.setColor(QPalette::Text, Qt::black);
+ m_lineEdit->setPalette(palette);
+ }
+ return m_lineEdit.data();
+}
+
+QTextEdit* TextEditItemWidget::textEdit() const
+{
+ if (m_textEdit.isNull()) {
+ m_textEdit.reset(new QTextEdit);
+ QPalette palette = m_textEdit->palette();
+ static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ palette.setColor(QPalette::Highlight, selectionColor);
+ palette.setColor(QPalette::HighlightedText, Qt::white);
+ palette.setColor(QPalette::Base, Qt::white);
+ palette.setColor(QPalette::Text, Qt::black);
+ m_textEdit->setPalette(palette);
+ }
+
+ return m_textEdit.data();
+}
+
+void TextEditItemWidget::activateTextEdit(const QSize &maximumSize)
+{
+ textEdit()->setMaximumSize(maximumSize);
+ textEdit()->setFocus();
+ setWidget(textEdit());
+}
+
+void TextEditItemWidget::activateLineEdit()
+{
+ lineEdit()->setFocus();
+ setWidget(lineEdit());
+}
+
+QString TextEditItemWidget::text() const
+{
+ if (widget() == m_lineEdit.data())
+ return m_lineEdit->text();
+ else if (widget() == m_textEdit.data())
+ return m_textEdit->toPlainText();
+ return QString();
+}
+
+void TextEditItemWidget::updateText(const QString &text)
+{
+ if (widget() == m_lineEdit.data()) {
+ m_lineEdit->setText(text);
+ m_lineEdit->selectAll();
+ } else if (widget() == m_textEdit.data()) {
+ m_textEdit->setText(text);
+ m_textEdit->selectAll();
+ }
+}
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.h b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h
new file mode 100644
index 0000000000..7ff2909919
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#pragma once
+
+#include <QGraphicsProxyWidget>
+#include <QScopedPointer>
+
+QT_BEGIN_NAMESPACE
+class QTextEdit;
+class QLineEdit;
+class QGraphicsScene;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class TextEditItemWidget : public QGraphicsProxyWidget
+{
+ Q_OBJECT
+public:
+ TextEditItemWidget(QGraphicsScene *scene);
+ ~TextEditItemWidget() override;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ void activateTextEdit(const QSize &maximumSize);
+ void activateLineEdit();
+ void updateText(const QString &text);
+
+protected:
+ QLineEdit* lineEdit() const;
+ QTextEdit* textEdit() const;
+
+ QString text() const;
+private:
+ mutable QScopedPointer<QLineEdit> m_lineEdit;
+ mutable QScopedPointer<QTextEdit> m_textEdit;
+};
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/texttool/texttool.cpp b/src/plugins/qmldesigner/components/texttool/texttool.cpp
new file mode 100644
index 0000000000..76dc414c03
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/texttool.cpp
@@ -0,0 +1,270 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "texttool.h"
+
+#include "formeditorscene.h"
+#include "formeditorview.h"
+#include "formeditorwidget.h"
+#include "itemutilfunctions.h"
+#include "formeditoritem.h"
+
+#include "resizehandleitem.h"
+#include "textedititem.h"
+
+#include "nodemetainfo.h"
+#include "qmlitemnode.h"
+#include <qmldesignerplugin.h>
+
+#include <abstractaction.h>
+#include <designeractionmanager.h>
+
+#include <QApplication>
+#include <QGraphicsSceneMouseEvent>
+#include <QAction>
+#include <QDebug>
+#include <QPair>
+
+namespace QmlDesigner {
+
+class TextToolAction : public AbstractAction
+{
+public:
+ TextToolAction() : AbstractAction(QCoreApplication::translate("TextToolAction","Edit Text")) {}
+
+ QByteArray category() const override
+ {
+ return QByteArray();
+ }
+
+ QByteArray menuId() const override
+ {
+ return "TextTool";
+ }
+
+ int priority() const override
+ {
+ return CustomActionsPriority;
+ }
+
+ Type type() const override
+ {
+ return ContextMenuAction;
+ }
+
+protected:
+ bool isVisible(const SelectionContext &selectionContext) const override
+ {
+ if (selectionContext.scenePosition().isNull())
+ return false;
+
+ if (selectionContext.singleNodeIsSelected())
+ return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("text");
+
+ return false;
+ }
+
+ bool isEnabled(const SelectionContext &selectionContext) const override
+ {
+ return isVisible(selectionContext);
+ }
+};
+
+TextTool::TextTool()
+{
+ auto textToolAction = new TextToolAction;
+ QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(textToolAction);
+ connect(textToolAction->action(), &QAction::triggered, [=]() {
+ view()->changeCurrentToolTo(this);
+ });
+}
+
+TextTool::~TextTool() = default;
+
+void TextTool::clear()
+{
+ if (textItem()) {
+ textItem()->clearFocus();
+ textItem()->deleteLater();
+ }
+
+ AbstractFormEditorTool::clear();
+}
+
+void TextTool::mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ event->setPos(textItem()->mapFromScene(event->scenePos()));
+ event->setLastPos(textItem()->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(textItem(), event);
+ AbstractFormEditorTool::mousePressEvent(itemList, event);
+}
+
+void TextTool::mouseMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent *event)
+{ event->setPos(textItem()->mapFromScene(event->scenePos()));
+ event->setLastPos(textItem()->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(textItem(), event);
+}
+
+void TextTool::hoverMoveEvent(const QList<QGraphicsItem*> & /*itemList*/,
+ QGraphicsSceneMouseEvent * event)
+{
+ event->setPos(textItem()->mapFromScene(event->scenePos()));
+ event->setLastPos(textItem()->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(textItem(), event);
+}
+
+void TextTool::keyPressEvent(QKeyEvent *keyEvent)
+{
+ if (keyEvent->key() == Qt::Key_Escape) {
+ textItem()->writeTextToProperty();
+ keyEvent->accept();
+ } else {
+ scene()->sendEvent(textItem(), keyEvent);
+ }
+}
+
+void TextTool::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ if (keyEvent->key() == Qt::Key_Escape) {
+ keyEvent->accept();
+ view()->changeToSelectionTool();
+ } else {
+ scene()->sendEvent(textItem(), keyEvent);
+ }
+}
+
+void TextTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+
+}
+
+void TextTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/)
+{
+
+}
+
+void TextTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event)
+{
+ if (!itemList.contains(textItem())) {
+ textItem()->writeTextToProperty();
+ view()->changeToSelectionTool();
+ }
+ AbstractFormEditorTool::mouseReleaseEvent(itemList, event);
+}
+
+
+void TextTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> & /*itemList*/, QGraphicsSceneMouseEvent *event)
+{
+ if (textItem() && !textItem()->boundingRect().contains(textItem()->mapFromScene(event->scenePos()))) {
+ textItem()->writeTextToProperty();
+ view()->changeToSelectionTool();
+ } else {
+ event->setPos(textItem()->mapFromScene(event->scenePos()));
+ event->setLastPos(textItem()->mapFromScene(event->lastScenePos()));
+ scene()->sendEvent(textItem(), event);
+ }
+}
+
+void TextTool::itemsAboutToRemoved(const QList<FormEditorItem*> &removedItemList)
+{
+ if (textItem() == nullptr)
+ return;
+
+ if (removedItemList.contains(textItem()->formEditorItem()))
+ view()->changeToSelectionTool();
+}
+
+void TextTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList)
+{
+ if (textItem()) {
+ textItem()->writeTextToProperty();
+ view()->changeToSelectionTool();
+ }
+ if (!itemList.isEmpty()) {
+ FormEditorItem *formEditorItem = itemList.constFirst();
+ m_textItem = new TextEditItem(scene());
+ textItem()->setParentItem(scene()->manipulatorLayerItem());
+ textItem()->setFormEditorItem(formEditorItem);
+ connect(textItem(), &TextEditItem::returnPressed, [this] {
+ textItem()->writeTextToProperty();
+ view()->changeToSelectionTool();
+ });
+ } else {
+ view()->changeToSelectionTool();
+ }
+}
+
+void TextTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+void TextTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/)
+{
+}
+
+void TextTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList)
+{
+ using ModelNodePropertyNamePair = QPair<ModelNode, PropertyName>;
+ foreach (const ModelNodePropertyNamePair &propertyPair, propertyList) {
+ if (propertyPair.first == textItem()->formEditorItem()->qmlItemNode().modelNode()
+ && propertyPair.second == "text")
+ textItem()->updateText();
+ }
+}
+
+void TextTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/)
+{
+}
+
+int TextTool::wantHandleItem(const ModelNode &modelNode) const
+{
+ if (modelNode.metaInfo().hasProperty("text"))
+ return 20;
+
+ return 0;
+}
+
+QString TextTool::name() const
+{
+ return QCoreApplication::translate("TextTool", "Text Tool");
+}
+
+void TextTool::focusLost()
+{
+ if (textItem()) {
+ textItem()->writeTextToProperty();
+ view()->changeToSelectionTool();
+ }
+}
+
+TextEditItem *TextTool::textItem() const
+{
+ return m_textItem.data();
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/texttool/texttool.h b/src/plugins/qmldesigner/components/texttool/texttool.h
new file mode 100644
index 0000000000..b7c352c917
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/texttool.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "abstractcustomtool.h"
+#include "selectionindicator.h"
+
+#include <QHash>
+#include <QPointer>
+#include <QColorDialog>
+
+namespace QmlDesigner {
+
+class TextEditItem;
+
+class TextTool : public QObject, public AbstractCustomTool
+{
+ Q_OBJECT
+public:
+ TextTool();
+ ~TextTool() override;
+
+ void mousePressEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneMouseEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+ void dragLeaveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+ void dragMoveEvent(const QList<QGraphicsItem*> &itemList,
+ QGraphicsSceneDragDropEvent * event) override;
+
+ void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override;
+
+ void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ void instancesCompleted(const QList<FormEditorItem*> &itemList) override;
+ void instancesParentChanged(const QList<FormEditorItem *> &itemList) override;
+ void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override;
+
+ void clear() override;
+
+ void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override;
+
+ int wantHandleItem(const ModelNode &modelNode) const override;
+
+ QString name() const override;
+
+ void focusLost() override;
+
+protected:
+ TextEditItem *textItem() const;
+
+private:
+ QPointer<TextEditItem> m_textItem;
+};
+
+}
diff --git a/src/plugins/qmldesigner/components/texttool/texttool.pri b/src/plugins/qmldesigner/components/texttool/texttool.pri
new file mode 100644
index 0000000000..0cfdfb7629
--- /dev/null
+++ b/src/plugins/qmldesigner/components/texttool/texttool.pri
@@ -0,0 +1,7 @@
+HEADERS += $$PWD/texttool.h
+HEADERS += $$PWD/textedititem.h
+HEADERS += $$PWD/textedititemwidget.h
+
+SOURCES += $$PWD/texttool.cpp
+SOURCES += $$PWD/textedititem.cpp
+SOURCES += $$PWD/textedititemwidget.cpp
diff --git a/src/plugins/qmldesigner/components/timelineeditor/canvas.cpp b/src/plugins/qmldesigner/components/timelineeditor/canvas.cpp
new file mode 100644
index 0000000000..eab3d0bf56
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/canvas.cpp
@@ -0,0 +1,359 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "canvas.h"
+#include "easingcurve.h"
+
+#include <QPainter>
+#include <QPointF>
+#include <QSize>
+
+namespace QmlDesigner {
+
+Canvas::Canvas(int width,
+ int height,
+ int marginX,
+ int marginY,
+ int cellCountX,
+ int cellCountY,
+ int offsetX,
+ int offsetY)
+ : m_width(width)
+ , m_height(height)
+ , m_marginX(marginX)
+ , m_marginY(marginY)
+ , m_cellCountX(cellCountX)
+ , m_cellCountY(cellCountY)
+ , m_offsetX(offsetX)
+ , m_offsetY(offsetY)
+ , m_scale(1.0)
+{}
+
+QRectF Canvas::gridRect() const
+{
+ double w = static_cast<double>(m_width);
+ double h = static_cast<double>(m_height);
+ double mx = static_cast<double>(m_marginX);
+ double my = static_cast<double>(m_marginY);
+ double gw = w - 2.0 * mx;
+ double gh = h - 2.0 * my;
+
+ if (m_style.aspect != 0) {
+ if (m_style.aspect < (w / h))
+ gw = gh * m_style.aspect;
+ else
+ gh = gw / m_style.aspect;
+ }
+
+ auto rect = QRectF(mx, my, gw * m_scale, gh * m_scale);
+ rect.moveCenter(QPointF(w / 2.0, h / 2.0));
+ return rect;
+}
+
+void Canvas::setCanvasStyle(const CanvasStyle &style)
+{
+ m_style = style;
+}
+
+void Canvas::setScale(double scale)
+{
+ if (scale > 0.05)
+ m_scale = scale;
+}
+
+void Canvas::resize(const QSize &size)
+{
+ m_width = size.width();
+ m_height = size.height();
+}
+
+void Canvas::paintGrid(QPainter *painter, const QBrush &background)
+{
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, true);
+
+ QPen pen = painter->pen();
+
+ pen.setWidthF(m_style.thinLineWidth);
+ pen.setColor(m_style.thinLineColor);
+ painter->setPen(pen);
+
+ painter->fillRect(0, 0, m_width, m_height, background);
+
+ QRectF rect = gridRect();
+
+ // Thin lines.
+ const int lineCountX = m_cellCountX + 1;
+ const double cellWidth = rect.width() / static_cast<double>(m_cellCountX);
+
+ // Vertical
+ double x = rect.left();
+ for (int i = 0; i < lineCountX; ++i) {
+ paintLine(painter, QPoint(x, rect.top()), QPoint(x, rect.bottom()));
+ x += cellWidth;
+ }
+
+ const int lineCountY = m_cellCountY + 1;
+ const double cellHeight = rect.height() / static_cast<double>(m_cellCountY);
+
+ // Horizontal
+ double y = rect.top();
+ for (int i = 0; i < lineCountY; ++i) {
+ paintLine(painter, QPoint(rect.left(), y), QPoint(rect.right(), y));
+ y += cellHeight;
+ }
+
+ // Thick lines.
+ pen.setWidthF(m_style.thickLineWidth);
+ pen.setColor(m_style.thickLineColor);
+ painter->setPen(pen);
+
+ if (m_offsetX != 0) {
+ const int minX = rect.left() + (cellWidth * m_offsetX);
+ const int maxX = rect.right() - (cellWidth * m_offsetX);
+ paintLine(painter, QPoint(minX, rect.top()), QPoint(minX, rect.bottom()));
+ paintLine(painter, QPoint(maxX, rect.top()), QPoint(maxX, rect.bottom()));
+ }
+
+ if (m_offsetY != 0) {
+ const int minY = rect.top() + (cellHeight * m_offsetY);
+ const int maxY = rect.bottom() - (cellHeight * m_offsetY);
+ paintLine(painter, QPoint(rect.left(), minY), QPoint(rect.right(), minY));
+ paintLine(painter, QPoint(rect.left(), maxY), QPoint(rect.right(), maxY));
+ }
+
+ painter->restore();
+}
+
+void Canvas::paintCurve(QPainter *painter, const EasingCurve &curve, const QColor &color)
+{
+ EasingCurve mapped = mapTo(curve);
+ painter->strokePath(mapped.path(), QPen(QBrush(color), m_style.curveWidth));
+}
+
+void Canvas::paintControlPoints(QPainter *painter, const EasingCurve &curve)
+{
+ QVector<QPointF> points = curve.toCubicSpline();
+ int count = points.count();
+
+ if (count <= 1)
+ return;
+
+ painter->save();
+
+ QPen pen = painter->pen();
+ pen.setWidthF(m_style.handleLineWidth);
+ pen.setColor(m_style.endPointColor);
+
+ painter->setPen(pen);
+ painter->setBrush(m_style.endPointColor);
+
+ // First and last point including handle.
+ paintLine(painter, mapTo(QPointF(0.0, 0.0)).toPoint(), mapTo(points.at(0)).toPoint());
+ paintPoint(painter, QPointF(0.0, 0.0), false);
+ paintPoint(painter, points.at(0), false, curve.active() == 0);
+
+ paintLine(painter, mapTo(QPointF(1.0, 1.0)).toPoint(), mapTo(points.at(count - 2)).toPoint());
+ paintPoint(painter, QPointF(1.0, 1.0), false);
+ paintPoint(painter, points.at(count - 2), false, curve.active() == (count - 2));
+
+ pen.setColor(m_style.interPointColor);
+ painter->setPen(pen);
+ painter->setBrush(m_style.interPointColor);
+
+ for (int i = 0; i < count - 1; ++i) {
+ if (curve.isHandle(i))
+ continue;
+
+ paintLine(painter, mapTo(points.at(i)).toPoint(), mapTo(points.at(i + 1)).toPoint());
+
+ if (i > 0)
+ paintLine(painter, mapTo(points.at(i - 1)).toPoint(), mapTo(points.at(i)).toPoint());
+ }
+
+ // Paint Points.
+ int active = curve.active();
+ for (int i = 1; i < count - 2; ++i)
+ paintPoint(painter, points.at(i), curve.isSmooth(i), active == i);
+
+ painter->restore();
+}
+
+void Canvas::paintProgress(QPainter *painter, const EasingCurve &curve, double progress)
+{
+ painter->save();
+
+ painter->setPen(Qt::green);
+ painter->setBrush(QBrush(Qt::green));
+
+ QPointF pos1(progress, curve.valueForProgress(progress));
+ pos1 = mapTo(pos1);
+
+ QRectF rect = gridRect();
+
+ painter->drawLine(rect.left(), pos1.y(), rect.right(), pos1.y());
+ painter->drawLine(pos1.x(), rect.top(), pos1.x(), rect.bottom());
+
+ painter->restore();
+}
+
+QPointF Canvas::mapTo(const QPointF &point) const
+{
+ QRectF rect = gridRect();
+
+ const double cellWidth = rect.width() / static_cast<double>(m_cellCountX);
+ const double cellHeight = rect.height() / static_cast<double>(m_cellCountY);
+
+ const double offsetX = cellWidth * m_offsetX;
+ const double offsetY = cellHeight * m_offsetY;
+
+ const int width = rect.width() - 2 * offsetX;
+ const int height = rect.height() - 2 * offsetY;
+
+ auto tmp = QPointF(point.x() * width + rect.left() + offsetX,
+ height - point.y() * height + rect.top() + offsetY);
+
+ return tmp;
+}
+
+CanvasStyle Canvas::canvasStyle() const
+{
+ return m_style;
+}
+
+double Canvas::scale() const
+{
+ return m_scale;
+}
+
+QPointF Canvas::normalize(const QPointF &point) const
+{
+ QRectF rect = gridRect();
+ return QPointF(point.x() / rect.width(), point.y() / rect.height());
+}
+
+EasingCurve Canvas::mapTo(const EasingCurve &curve) const
+{
+ QVector<QPointF> controlPoints = curve.toCubicSpline();
+
+ for (auto &point : controlPoints)
+ point = mapTo(point);
+
+ return EasingCurve(mapTo(curve.start()), controlPoints);
+}
+
+QPointF Canvas::mapFrom(const QPointF &point) const
+{
+ QRectF rect = gridRect();
+
+ const double cellWidth = rect.width() / static_cast<double>(m_cellCountX);
+ const double cellHeight = rect.height() / static_cast<double>(m_cellCountY);
+
+ const double offsetX = cellWidth * m_offsetX;
+ const double offsetY = cellHeight * m_offsetY;
+
+ const int width = rect.width() - 2 * offsetX;
+ const int height = rect.height() - 2 * offsetY;
+
+ return QPointF((point.x() - rect.left() - offsetX) / width,
+ 1 - (point.y() - rect.top() - offsetY) / height);
+}
+
+EasingCurve Canvas::mapFrom(const EasingCurve &curve) const
+{
+ QVector<QPointF> controlPoints = curve.toCubicSpline();
+ for (auto &point : controlPoints)
+ point = mapFrom(point);
+
+ EasingCurve result;
+ result.fromCubicSpline(controlPoints);
+ return result;
+}
+
+QPointF Canvas::clamp(const QPointF &point) const
+{
+ QRectF r = gridRect();
+ QPointF p = point;
+
+ if (p.x() > r.right())
+ p.rx() = r.right();
+
+ if (p.x() < r.left())
+ p.rx() = r.left();
+
+ if (p.y() < r.top())
+ p.ry() = r.top();
+
+ if (p.y() > r.bottom())
+ p.ry() = r.bottom();
+
+ return p;
+}
+
+void Canvas::paintLine(QPainter *painter, const QPoint &p1, const QPoint &p2)
+{
+ painter->drawLine(p1 + QPointF(0.5, 0.5), p2 + QPointF(0.5, 0.5));
+}
+
+void Canvas::paintPoint(QPainter *painter, const QPointF &point, bool smooth, bool active)
+{
+ const double pointSize = m_style.handleSize;
+ const double activePointSize = pointSize + 2;
+ if (smooth) {
+ if (active) {
+ painter->save();
+ painter->setPen(Qt::white);
+ painter->setBrush(QBrush());
+ painter->drawEllipse(QRectF(mapTo(point).x() - activePointSize + 0.5,
+ mapTo(point).y() - activePointSize + 0.5,
+ activePointSize * 2,
+ activePointSize * 2));
+ painter->restore();
+ }
+
+ painter->drawEllipse(QRectF(mapTo(point).x() - pointSize + 0.5,
+ mapTo(point).y() - pointSize + 0.5,
+ pointSize * 2,
+ pointSize * 2));
+
+ } else {
+ if (active) {
+ painter->save();
+ painter->setPen(Qt::white);
+ painter->setBrush(QBrush());
+ painter->drawRect(QRectF(mapTo(point).x() - activePointSize + 0.5,
+ mapTo(point).y() - activePointSize + 0.5,
+ activePointSize * 2,
+ activePointSize * 2));
+ painter->restore();
+ }
+ painter->drawRect(QRectF(mapTo(point).x() - pointSize + 0.5,
+ mapTo(point).y() - pointSize + 0.5,
+ pointSize * 2,
+ pointSize * 2));
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/canvas.h b/src/plugins/qmldesigner/components/timelineeditor/canvas.h
new file mode 100644
index 0000000000..c4491e4b79
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/canvas.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "canvasstyledialog.h"
+#include <qglobal.h>
+
+QT_FORWARD_DECLARE_CLASS(QBrush);
+QT_FORWARD_DECLARE_CLASS(QColor);
+QT_FORWARD_DECLARE_CLASS(QPainter);
+QT_FORWARD_DECLARE_CLASS(QPointF);
+QT_FORWARD_DECLARE_CLASS(QPoint);
+QT_FORWARD_DECLARE_CLASS(QSize);
+
+namespace QmlDesigner {
+
+class EasingCurve;
+
+class Canvas
+{
+public:
+ Canvas(int width,
+ int height,
+ int marginX,
+ int marginY,
+ int cellCountX,
+ int cellCountY,
+ int offsetX,
+ int offsetY);
+
+public:
+ CanvasStyle canvasStyle() const;
+
+ double scale() const;
+
+ QRectF gridRect() const;
+
+ QPointF normalize(const QPointF &point) const;
+
+ QPointF mapTo(const QPointF &point) const;
+
+ EasingCurve mapTo(const EasingCurve &curve) const;
+
+ QPointF mapFrom(const QPointF &point) const;
+
+ EasingCurve mapFrom(const EasingCurve &curve) const;
+
+ QPointF clamp(const QPointF &point) const;
+
+ void setCanvasStyle(const CanvasStyle &style);
+
+ void setScale(double scale);
+
+ void resize(const QSize &size);
+
+ void paintGrid(QPainter *painter, const QBrush &background);
+
+ void paintCurve(QPainter *painter, const EasingCurve &curve, const QColor &color);
+
+ void paintControlPoints(QPainter *painter, const EasingCurve &curve);
+
+ void paintProgress(QPainter *painter, const EasingCurve &curve, double progress);
+
+private:
+ void paintLine(QPainter *painter, const QPoint &p1, const QPoint &p2);
+
+ void paintPoint(QPainter *painter, const QPointF &point, bool smooth, bool active = false);
+
+private:
+ int m_width;
+ int m_height;
+
+ int m_marginX;
+ int m_marginY;
+
+ int m_cellCountX;
+ int m_cellCountY;
+
+ int m_offsetX;
+ int m_offsetY;
+
+ double m_scale;
+
+ CanvasStyle m_style;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.cpp
new file mode 100644
index 0000000000..88d78615ca
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.cpp
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "canvasstyledialog.h"
+
+#include <QColorDialog>
+#include <QDoubleSpinBox>
+#include <QLabel>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QVBoxLayout>
+
+namespace QmlDesigner {
+
+CanvasStyleDialog::CanvasStyleDialog(const CanvasStyle &style, QWidget *parent)
+ : QDialog(parent)
+ , m_aspect(new QDoubleSpinBox(this))
+ , m_thinLineWidth(new QDoubleSpinBox(this))
+ , m_thickLineWidth(new QDoubleSpinBox(this))
+ , m_thinLineColor(new ColorControl(style.thinLineColor, this))
+ , m_thickLineColor(new ColorControl(style.thickLineColor, this))
+ , m_handleSize(new QDoubleSpinBox(this))
+ , m_handleLineWidth(new QDoubleSpinBox(this))
+ , m_endPointColor(new ColorControl(style.endPointColor, this))
+ , m_interPointColor(new ColorControl(style.interPointColor, this))
+ , m_curveWidth(new QDoubleSpinBox(this))
+{
+ m_aspect->setValue(style.aspect);
+ m_thinLineWidth->setValue(style.thinLineWidth);
+ m_thickLineWidth->setValue(style.thickLineWidth);
+ m_handleSize->setValue(style.handleSize);
+ m_handleLineWidth->setValue(style.handleLineWidth);
+ m_curveWidth->setValue(style.curveWidth);
+
+ int labelWidth = QFontMetrics(this->font()).horizontalAdvance("Inter Handle ColorXX");
+ auto addControl = [labelWidth](QVBoxLayout *layout, const QString &name, QWidget *control) {
+ auto *hbox = new QHBoxLayout;
+
+ QLabel *label = new QLabel(name);
+ label->setAlignment(Qt::AlignLeft);
+ label->setFixedWidth(labelWidth);
+
+ hbox->addWidget(label);
+ hbox->addWidget(control);
+ layout->addLayout(hbox);
+ };
+
+ auto layout = new QVBoxLayout;
+ addControl(layout, "Aspect Ratio", m_aspect);
+
+ addControl(layout, "Thin Line Width", m_thinLineWidth);
+ addControl(layout, "Thin Line Color", m_thinLineColor);
+
+ addControl(layout, "Thick Line Width", m_thickLineWidth);
+ addControl(layout, "Thick Line Color", m_thickLineColor);
+
+ addControl(layout, "Handle Size", m_handleSize);
+ addControl(layout, "Handle Line Width", m_handleLineWidth);
+ addControl(layout, "End Handle Color", m_endPointColor);
+ addControl(layout, "Inter Handle Color", m_interPointColor);
+
+ addControl(layout, "Curve Width", m_curveWidth);
+
+ setLayout(layout);
+
+ auto emitValueChanged = [this]() {
+ CanvasStyle out;
+ out.aspect = m_aspect->value();
+ out.thinLineWidth = m_thinLineWidth->value();
+ out.thickLineWidth = m_thickLineWidth->value();
+ out.thinLineColor = m_thinLineColor->value();
+ out.thickLineColor = m_thickLineColor->value();
+ out.handleSize = m_handleSize->value();
+ out.handleLineWidth = m_handleLineWidth->value();
+ out.endPointColor = m_endPointColor->value();
+ out.interPointColor = m_interPointColor->value();
+ out.curveWidth = m_curveWidth->value();
+ emit styleChanged(out);
+ };
+
+ auto doubleValueChanged = QOverload<double>::of(
+ &QDoubleSpinBox::valueChanged);
+ auto colorValueChanged = &ColorControl::valueChanged;
+
+ connect(m_aspect, doubleValueChanged, this, emitValueChanged);
+
+ connect(m_thinLineWidth, doubleValueChanged, this, emitValueChanged);
+ connect(m_thickLineWidth, doubleValueChanged, this, emitValueChanged);
+
+ connect(m_thinLineColor, colorValueChanged, this, emitValueChanged);
+ connect(m_thickLineColor, colorValueChanged, this, emitValueChanged);
+
+ connect(m_handleSize, doubleValueChanged, this, emitValueChanged);
+ connect(m_handleLineWidth, doubleValueChanged, this, emitValueChanged);
+
+ connect(m_endPointColor, colorValueChanged, this, emitValueChanged);
+ connect(m_interPointColor, colorValueChanged, this, emitValueChanged);
+
+ connect(m_curveWidth, doubleValueChanged, this, emitValueChanged);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.h b/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.h
new file mode 100644
index 0000000000..b3b1a86c95
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/canvasstyledialog.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelinecontrols.h"
+
+#include <QColor>
+#include <QDialog>
+
+QT_FORWARD_DECLARE_CLASS(QDoubleSpinBox);
+
+namespace QmlDesigner {
+
+struct CanvasStyle
+{
+ qreal aspect = 1.5;
+
+ qreal thinLineWidth = 0.3;
+ qreal thickLineWidth = 2.5;
+
+ QColor thinLineColor = qRgb(0x99, 0x99, 0x99);
+ QColor thickLineColor = qRgb(0x5f, 0x5f, 0x5f);
+
+ qreal handleSize = 7.0;
+ qreal handleLineWidth = 2.0;
+
+ QColor endPointColor = qRgb(0xd6, 0xd3, 0x51);
+ QColor interPointColor = qRgb(0xce, 0x17, 0x17);
+
+ qreal curveWidth = 3.0;
+};
+
+class CanvasStyleDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ CanvasStyleDialog(const CanvasStyle &style, QWidget *parent = nullptr);
+
+signals:
+ void styleChanged(const CanvasStyle &style);
+
+private:
+ QDoubleSpinBox *m_aspect;
+
+ QDoubleSpinBox *m_thinLineWidth;
+ QDoubleSpinBox *m_thickLineWidth;
+
+ ColorControl *m_thinLineColor;
+ ColorControl *m_thickLineColor;
+
+ QDoubleSpinBox *m_handleSize;
+ QDoubleSpinBox *m_handleLineWidth;
+
+ ColorControl *m_endPointColor;
+ ColorControl *m_interPointColor;
+
+ QDoubleSpinBox *m_curveWidth;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/easingcurve.cpp b/src/plugins/qmldesigner/components/timelineeditor/easingcurve.cpp
new file mode 100644
index 0000000000..cae0bfab09
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/easingcurve.cpp
@@ -0,0 +1,502 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "easingcurve.h"
+#include "timelineutils.h"
+
+#include <QDataStream>
+#include <QDebug>
+#include <QLineF>
+#include <QPainterPath>
+#include <QPointF>
+
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+EasingCurve::EasingCurve()
+ : QEasingCurve(QEasingCurve::BezierSpline)
+ , m_active(-1)
+ , m_start(0.0, 0.0)
+{}
+
+EasingCurve::EasingCurve(const QEasingCurve &curve)
+ : QEasingCurve(curve)
+ , m_active(-1)
+ , m_start(0.0, 0.0)
+{}
+
+EasingCurve::EasingCurve(const EasingCurve &curve) = default;
+
+EasingCurve::EasingCurve(const QPointF &start, const QVector<QPointF> &points)
+ : QEasingCurve(QEasingCurve::BezierSpline)
+ , m_active(-1)
+ , m_start(start)
+{
+ fromCubicSpline(points);
+}
+
+EasingCurve &EasingCurve::operator=(const EasingCurve &curve) = default;
+
+EasingCurve::~EasingCurve() = default;
+
+bool EasingCurve::IsValidIndex(int idx)
+{
+ return idx >= 0;
+}
+
+void EasingCurve::registerStreamOperators()
+{
+ qRegisterMetaType<QmlDesigner::EasingCurve>("QmlDesigner::EasingCurve");
+ qRegisterMetaType<QmlDesigner::NamedEasingCurve>("QmlDesigner::NamedEasingCurve");
+ qRegisterMetaTypeStreamOperators<QmlDesigner::EasingCurve>("QmlDesigner::EasingCurve");
+ qRegisterMetaTypeStreamOperators<QmlDesigner::NamedEasingCurve>("QmlDesigner::NamedEasingCurve");
+}
+
+int EasingCurve::count() const
+{
+ return toCubicSpline().count();
+}
+
+int EasingCurve::active() const
+{
+ return m_active;
+}
+
+int EasingCurve::segmentCount() const
+{
+ return toCubicSpline().count() / 3;
+}
+
+bool EasingCurve::isLegal() const
+{
+ QPainterPath painterPath(path());
+
+ double increment = 1.0 / 30.0;
+ QPointF max = painterPath.pointAtPercent(0.0);
+ for (double i = increment; i <= 1.0; i += increment) {
+ QPointF current = painterPath.pointAtPercent(i);
+ if (current.x() < max.x())
+ return false;
+ else
+ max = current;
+ }
+ return true;
+}
+
+bool EasingCurve::hasActive() const
+{
+ QTC_ASSERT(m_active < toCubicSpline().size(), return false);
+ return m_active >= 0;
+}
+
+bool EasingCurve::isValidIndex(int idx) const
+{
+ return idx >= 0 && idx < toCubicSpline().size();
+}
+
+bool EasingCurve::isSmooth(int idx) const
+{
+ auto iter = std::find(m_smoothIds.begin(), m_smoothIds.end(), idx);
+ return iter != m_smoothIds.end();
+}
+
+bool EasingCurve::isHandle(int idx) const
+{
+ return (idx + 1) % 3;
+}
+
+bool EasingCurve::isLeftHandle(int idx) const
+{
+ return ((idx + 2) % 3) == 0;
+}
+
+QString EasingCurve::toString() const
+{
+ QLatin1Char c(',');
+ QString s = QLatin1String("[");
+ for (const QPointF &point : toCubicSpline()) {
+ auto x = QString::number(point.x(), 'g', 3);
+ auto y = QString::number(point.y(), 'g', 3);
+ s += x + c + y + c;
+ }
+
+ // Replace last "," with "]"
+ s.chop(1);
+ s.append(QLatin1Char(']'));
+
+ return s;
+}
+
+bool EasingCurve::fromString(const QString &code)
+{
+ if (code.startsWith(QLatin1Char('[')) && code.endsWith(QLatin1Char(']'))) {
+ const QStringRef cleanCode(&code, 1, code.size() - 2);
+ const auto stringList = cleanCode.split(QLatin1Char(','), QString::SkipEmptyParts);
+
+ if (stringList.count() >= 6 && (stringList.count() % 6 == 0)) {
+ bool checkX, checkY;
+ QVector<QPointF> points;
+ for (int i = 0; i < stringList.count(); ++i) {
+ QPointF point;
+ point.rx() = stringList[i].toDouble(&checkX);
+ point.ry() = stringList[++i].toDouble(&checkY);
+
+ if (!checkX || !checkY)
+ return false;
+
+ points.push_back(point);
+ }
+
+ if (points.constLast() != QPointF(1.0, 1.0))
+ return false;
+
+ QEasingCurve easingCurve(QEasingCurve::BezierSpline);
+
+ for (int i = 0; i < points.count() / 3; ++i) {
+ easingCurve.addCubicBezierSegment(points.at(i * 3),
+ points.at(i * 3 + 1),
+ points.at(i * 3 + 2));
+ }
+
+ fromCubicSpline(easingCurve.toCubicSpline());
+ return true;
+ }
+ }
+ return false;
+}
+
+QPointF EasingCurve::start() const
+{
+ return m_start;
+}
+
+QPointF EasingCurve::end() const
+{
+ return toCubicSpline().last();
+}
+
+QPainterPath EasingCurve::path() const
+{
+ QPainterPath path;
+ path.moveTo(m_start);
+
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ int numSegments = controlPoints.count() / 3;
+ for (int i = 0; i < numSegments; i++) {
+ QPointF p1 = controlPoints.at(i * 3);
+ QPointF p2 = controlPoints.at(i * 3 + 1);
+ QPointF p3 = controlPoints.at(i * 3 + 2);
+ path.cubicTo(p1, p2, p3);
+ }
+
+ return path;
+}
+
+int EasingCurve::curvePoint(int idx) const
+{
+ if (isHandle(idx)) {
+ if (isLeftHandle(idx))
+ return idx + 1;
+ else
+ return idx - 1;
+ }
+ return idx;
+}
+
+QPointF EasingCurve::point(int idx) const
+{
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ QTC_ASSERT(controlPoints.count() > idx || idx < 0, return QPointF());
+
+ return controlPoints.at(idx);
+}
+
+int EasingCurve::hit(const QPointF &point, double threshold) const
+{
+ int id = -1;
+ qreal distance = std::numeric_limits<qreal>::max();
+
+ QVector<QPointF> controlPoints = toCubicSpline();
+ for (int i = 0; i < controlPoints.size() - 1; ++i) {
+ qreal d = QLineF(point, controlPoints.at(i)).length();
+ if (d < threshold && d < distance) {
+ distance = d;
+ id = i;
+ }
+ }
+ return id;
+}
+
+void EasingCurve::makeDefault()
+{
+ QVector<QPointF> controlPoints;
+ controlPoints.append(QPointF(0.0, 0.2));
+ controlPoints.append(QPointF(0.3, 0.5));
+ controlPoints.append(QPointF(0.5, 0.5));
+
+ controlPoints.append(QPointF(0.7, 0.5));
+ controlPoints.append(QPointF(1.0, 0.8));
+ controlPoints.append(QPointF(1.0, 1.0));
+
+ fromCubicSpline(controlPoints);
+
+ m_smoothIds.push_back(2);
+}
+
+void EasingCurve::clearActive()
+{
+ m_active = -1;
+}
+
+void EasingCurve::setActive(int idx)
+{
+ m_active = idx;
+}
+
+void EasingCurve::makeSmooth(int idx)
+{
+ if (!isSmooth(idx) && !isHandle(idx)) {
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ QPointF before = m_start;
+ if (idx > 3)
+ before = controlPoints.at(idx - 3);
+
+ QPointF after = end();
+ if ((idx + 3) < controlPoints.count())
+ after = controlPoints.at(idx + 3);
+
+ QPointF tangent = (after - before) / 6;
+
+ QPointF thisPoint = controlPoints.at(idx);
+
+ if (idx > 0)
+ controlPoints[idx - 1] = thisPoint - tangent;
+
+ if (idx + 1 < controlPoints.count())
+ controlPoints[idx + 1] = thisPoint + tangent;
+
+ fromCubicSpline(controlPoints);
+
+ m_smoothIds.push_back(idx);
+ }
+}
+
+void EasingCurve::breakTangent(int idx)
+{
+ if (isSmooth(idx) && !isHandle(idx)) {
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ QPointF before = m_start;
+ if (idx > 3)
+ before = controlPoints.at(idx - 3);
+
+ QPointF after = end();
+ if ((idx + 3) < controlPoints.count())
+ after = controlPoints.at(idx + 3);
+
+ QPointF thisPoint = controlPoints.at(idx);
+
+ if (idx > 0)
+ controlPoints[idx - 1] = (before - thisPoint) / 3 + thisPoint;
+
+ if (idx + 1 < controlPoints.count())
+ controlPoints[idx + 1] = (after - thisPoint) / 3 + thisPoint;
+
+ fromCubicSpline(controlPoints);
+
+ auto iter = std::find(m_smoothIds.begin(), m_smoothIds.end(), idx);
+ m_smoothIds.erase(iter);
+ }
+}
+
+void EasingCurve::addPoint(const QPointF &point)
+{
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ int splitIndex = 0;
+ for (int i = 0; i < controlPoints.size() - 1; ++i) {
+ if (!isHandle(i)) {
+ if (controlPoints.at(i).x() > point.x())
+ break;
+
+ splitIndex = i;
+ }
+ }
+
+ QPointF before = m_start;
+ if (splitIndex > 0) {
+ before = controlPoints.at(splitIndex);
+ }
+
+ QPointF after = end();
+ if ((splitIndex + 3) < controlPoints.count()) {
+ after = controlPoints.at(splitIndex + 3);
+ }
+
+ int newIdx;
+
+ if (splitIndex > 0) {
+ newIdx = splitIndex + 3;
+ controlPoints.insert(splitIndex + 2, (point + after) / 2);
+ controlPoints.insert(splitIndex + 2, point);
+ controlPoints.insert(splitIndex + 2, (point + before) / 2);
+ } else {
+ newIdx = splitIndex + 2;
+ controlPoints.insert(splitIndex + 1, (point + after) / 2);
+ controlPoints.insert(splitIndex + 1, point);
+ controlPoints.insert(splitIndex + 1, (point + before) / 2);
+ }
+
+ fromCubicSpline(controlPoints);
+
+ QTC_ASSERT(!isHandle(newIdx), return );
+
+ m_active = newIdx;
+
+ breakTangent(newIdx);
+ makeSmooth(newIdx);
+}
+
+void EasingCurve::setPoint(int idx, const QPointF &point)
+{
+ if (!isValidIndex(idx))
+ return;
+
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ controlPoints[idx] = point;
+
+ fromCubicSpline(controlPoints);
+}
+
+void EasingCurve::movePoint(int idx, const QPointF &vector)
+{
+ if (!isValidIndex(idx))
+ return;
+
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ controlPoints[idx] += vector;
+
+ fromCubicSpline(controlPoints);
+}
+
+void EasingCurve::deletePoint(int idx)
+{
+ if (!isValidIndex(idx))
+ return;
+
+ QVector<QPointF> controlPoints = toCubicSpline();
+
+ controlPoints.remove(idx - 1, 3);
+
+ fromCubicSpline(controlPoints);
+}
+
+void EasingCurve::fromCubicSpline(const QVector<QPointF> &points)
+{
+ QEasingCurve tmp(QEasingCurve::BezierSpline);
+
+ int numSegments = points.count() / 3;
+ for (int i = 0; i < numSegments; ++i) {
+ tmp.addCubicBezierSegment(points.at(i * 3), points.at(i * 3 + 1), points.at(i * 3 + 2));
+ }
+ swap(tmp);
+}
+
+QDebug &operator<<(QDebug &stream, const EasingCurve &curve)
+{
+ stream << static_cast<QEasingCurve>(curve);
+ stream << "\"active:" << curve.m_active << "\"";
+ stream << "\"smooth ids:" << curve.m_smoothIds << "\"";
+ return stream;
+}
+
+QDataStream &operator<<(QDataStream &stream, const EasingCurve &curve)
+{
+ // Ignore the active flag.
+ stream << static_cast<QEasingCurve>(curve);
+ stream << curve.toCubicSpline();
+ stream << curve.m_smoothIds;
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, EasingCurve &curve)
+{
+ // This is to circumvent a bug in QEasingCurve serialization.
+ QVector<QPointF> points;
+
+ // Ignore the active flag.
+ stream >> static_cast<QEasingCurve &>(curve);
+ stream >> points;
+ curve.fromCubicSpline(points);
+ stream >> curve.m_smoothIds;
+
+ return stream;
+}
+
+NamedEasingCurve::NamedEasingCurve()
+ : m_name()
+ , m_curve()
+{}
+
+NamedEasingCurve::NamedEasingCurve(const QString &name, const EasingCurve &curve)
+ : m_name(name)
+ , m_curve(curve)
+{}
+
+NamedEasingCurve::NamedEasingCurve(const NamedEasingCurve &other) = default;
+
+NamedEasingCurve::~NamedEasingCurve() = default;
+
+QString NamedEasingCurve::name() const
+{
+ return m_name;
+}
+
+EasingCurve NamedEasingCurve::curve() const
+{
+ return m_curve;
+}
+
+QDataStream &operator<<(QDataStream &stream, const NamedEasingCurve &curve)
+{
+ stream << curve.m_name;
+ stream << curve.m_curve;
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, NamedEasingCurve &curve)
+{
+ stream >> curve.m_name;
+ stream >> curve.m_curve;
+ return stream;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/easingcurve.h b/src/plugins/qmldesigner/components/timelineeditor/easingcurve.h
new file mode 100644
index 0000000000..d46f5600aa
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/easingcurve.h
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QEasingCurve>
+#include <QMetaType>
+#include <QPointF>
+
+QT_FORWARD_DECLARE_CLASS(QPainterPath);
+
+namespace QmlDesigner {
+
+class EasingCurve : public QEasingCurve
+{
+public:
+ EasingCurve();
+
+ EasingCurve(const QEasingCurve &curve);
+
+ EasingCurve(const EasingCurve &curve);
+
+ EasingCurve(const QPointF &start, const QVector<QPointF> &points);
+
+ virtual ~EasingCurve();
+
+ EasingCurve &operator=(const EasingCurve &curve);
+
+ static bool IsValidIndex(int idx);
+
+ static void registerStreamOperators();
+
+public:
+ int count() const;
+
+ int active() const;
+
+ int segmentCount() const;
+
+ bool hasActive() const;
+
+ bool isLegal() const;
+
+ bool isValidIndex(int idx) const;
+
+ bool isSmooth(int idx) const;
+
+ bool isHandle(int idx) const;
+
+ bool isLeftHandle(int idx) const;
+
+ QString toString() const;
+
+ QPointF start() const;
+
+ QPointF end() const;
+
+ QPainterPath path() const;
+
+ int curvePoint(int idx) const;
+
+ QPointF point(int idx) const;
+
+ int hit(const QPointF &point, double threshold) const;
+
+public:
+ void makeDefault();
+
+ void clearActive();
+
+ void setActive(int idx);
+
+ void makeSmooth(int idx);
+
+ void breakTangent(int idx);
+
+ void addPoint(const QPointF &point);
+
+ void setPoint(int idx, const QPointF &point);
+
+ void movePoint(int idx, const QPointF &vector);
+
+ void deletePoint(int idx);
+
+ bool fromString(const QString &string);
+
+ void fromCubicSpline(const QVector<QPointF> &points);
+
+ friend QDebug &operator<<(QDebug &stream, const EasingCurve &curve);
+
+ friend QDataStream &operator<<(QDataStream &stream, const EasingCurve &curve);
+
+ friend QDataStream &operator>>(QDataStream &stream, EasingCurve &curve);
+
+ friend std::ostream &operator<<(std::ostream &stream, const EasingCurve &curve);
+
+ friend std::istream &operator>>(std::istream &stream, EasingCurve &curve);
+
+private:
+ int m_active;
+
+ QPointF m_start;
+
+ std::vector<int> m_smoothIds;
+};
+
+class NamedEasingCurve
+{
+public:
+ NamedEasingCurve();
+
+ NamedEasingCurve(const QString &name, const EasingCurve &curve);
+
+ NamedEasingCurve(const NamedEasingCurve &other);
+
+ virtual ~NamedEasingCurve();
+
+ QString name() const;
+
+ EasingCurve curve() const;
+
+ friend QDataStream &operator<<(QDataStream &stream, const NamedEasingCurve &curve);
+
+ friend QDataStream &operator>>(QDataStream &stream, NamedEasingCurve &curve);
+
+private:
+ QString m_name;
+
+ EasingCurve m_curve;
+};
+
+} // namespace QmlDesigner
+
+Q_DECLARE_METATYPE(QmlDesigner::EasingCurve);
+Q_DECLARE_METATYPE(QmlDesigner::NamedEasingCurve);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.cpp
new file mode 100644
index 0000000000..a069dc187b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.cpp
@@ -0,0 +1,298 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "easingcurvedialog.h"
+
+#include "preseteditor.h"
+#include "splineeditor.h"
+
+#include <QApplication>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <QPushButton>
+#include <QSizePolicy>
+#include <QSpinBox>
+#include <QTabBar>
+#include <QTabWidget>
+#include <QVBoxLayout>
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <rewritingexception.h>
+#include <theme.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+EasingCurveDialog::EasingCurveDialog(const QList<ModelNode> &frames, QWidget *parent)
+ : QDialog(parent)
+ , m_splineEditor(new SplineEditor(this))
+ , m_text(new QPlainTextEdit(this))
+ , m_presets(new PresetEditor(this))
+ , m_durationLayout(new QHBoxLayout)
+ , m_buttons(new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel
+ | QDialogButtonBox::Ok))
+ , m_label(new QLabel)
+ , m_frames(frames)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ auto tw = new QTabWidget;
+ tw->setTabPosition(QTabWidget::East);
+ tw->addTab(m_splineEditor, "Curve");
+ tw->addTab(m_text, "Text");
+
+ connect(tw, &QTabWidget::currentChanged, this, &EasingCurveDialog::tabClicked);
+ connect(m_text, &QPlainTextEdit::textChanged, this, &EasingCurveDialog::textChanged);
+
+ auto labelFont = m_label->font();
+ labelFont.setPointSize(labelFont.pointSize() + 2);
+ m_label->setFont(labelFont);
+
+ auto hSpacing = qApp->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
+ auto vSpacing = qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
+ auto *vbox = new QVBoxLayout;
+ vbox->setContentsMargins(2, 0, 0, vSpacing);
+ vbox->addWidget(m_label);
+
+ auto *presetBar = new QTabBar;
+
+ auto smallFont = presetBar->font();
+ smallFont.setPixelSize(Theme::instance()->smallFontPixelSize());
+
+ presetBar->setFont(smallFont);
+ presetBar->setExpanding(false);
+ presetBar->setDrawBase(false);
+ presetBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+
+ auto *durationLabel = new QLabel("Duration (ms)");
+ auto *durationEdit = new QSpinBox;
+ durationEdit->setMaximum(std::numeric_limits<int>::max());
+ durationEdit->setValue(1000);
+ auto *animateButton = new QPushButton("Preview");
+
+ m_durationLayout->setContentsMargins(0, vSpacing, 0, 0);
+ m_durationLayout->addWidget(durationLabel);
+ m_durationLayout->addWidget(durationEdit);
+ m_durationLayout->addWidget(animateButton);
+
+ m_durationLayout->insertSpacing(1, hSpacing);
+ m_durationLayout->insertSpacing(2, hSpacing);
+ m_durationLayout->insertSpacing(4, hSpacing);
+ m_durationLayout->addStretch(hSpacing);
+
+ m_buttons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ auto callButtonsClicked = [this](QAbstractButton *button) {
+ buttonsClicked(m_buttons->standardButton(button));
+ };
+
+ connect(m_buttons, &QDialogButtonBox::clicked, this, callButtonsClicked);
+
+ auto *buttonLayout = new QVBoxLayout;
+ buttonLayout->setContentsMargins(0, vSpacing, 0, 0);
+ buttonLayout->addWidget(m_buttons);
+
+ auto *grid = new QGridLayout;
+ grid->setVerticalSpacing(0);
+ grid->addLayout(vbox, 0, 0);
+ grid->addWidget(presetBar, 0, 1, Qt::AlignBottom);
+
+ grid->addWidget(tw);
+ grid->addWidget(m_presets, 1, 1);
+ grid->addLayout(m_durationLayout, 2, 0);
+ grid->addLayout(buttonLayout, 2, 1);
+
+ auto *groupBox = new QGroupBox;
+ groupBox->setLayout(grid);
+
+ auto *tabWidget = new QTabWidget(this);
+ tabWidget->addTab(groupBox, "Easing Curve Editor");
+
+ auto *mainBox = new QVBoxLayout;
+ mainBox->addWidget(tabWidget);
+ setLayout(mainBox);
+
+ connect(m_splineEditor,
+ &SplineEditor::easingCurveChanged,
+ this,
+ &EasingCurveDialog::updateEasingCurve);
+
+ connect(m_presets, &PresetEditor::presetChanged, m_splineEditor, &SplineEditor::setEasingCurve);
+
+ connect(durationEdit,
+ QOverload<int>::of(&QSpinBox::valueChanged),
+ m_splineEditor,
+ &SplineEditor::setDuration);
+
+ connect(animateButton, &QPushButton::clicked, m_splineEditor, &SplineEditor::animate);
+
+ m_presets->initialize(presetBar);
+
+ m_splineEditor->setDuration(durationEdit->value());
+
+ resize(QSize(1421, 918));
+}
+
+void EasingCurveDialog::initialize(const QString &curveString)
+{
+ EasingCurve curve;
+ if (curveString.isEmpty()) {
+ QEasingCurve qcurve;
+ qcurve.addCubicBezierSegment(QPointF(0.2, 0.2), QPointF(0.8, 0.8), QPointF(1.0, 1.0));
+ curve = EasingCurve(qcurve);
+ } else
+ curve.fromString(curveString);
+
+ m_splineEditor->setEasingCurve(curve);
+}
+
+void EasingCurveDialog::runDialog(const QList<ModelNode> &frames, QWidget *parent)
+{
+ if (frames.empty())
+ return;
+
+ EasingCurveDialog dialog(frames, parent);
+
+ ModelNode current = frames.last();
+
+ if (current.hasBindingProperty("easing.bezierCurve"))
+ dialog.initialize(current.bindingProperty("easing.bezierCurve").expression());
+ else
+ dialog.initialize("");
+
+ dialog.exec();
+}
+
+bool EasingCurveDialog::apply()
+{
+ QTC_ASSERT(!m_frames.empty(), return false);
+
+ EasingCurve curve = m_splineEditor->easingCurve();
+ if (!curve.isLegal()) {
+ QMessageBox msgBox;
+ msgBox.setText("Attempting to apply invalid curve to keyframe");
+ msgBox.setInformativeText("Please solve the issue before proceeding.");
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.exec();
+ return false;
+ }
+ AbstractView *view = m_frames.first().view();
+
+ return view->executeInTransaction("EasingCurveDialog::apply", [this, view](){
+ auto expression = m_splineEditor->easingCurve().toString();
+ for (const auto &frame : m_frames)
+ frame.bindingProperty("easing.bezierCurve").setExpression(expression);
+ });
+}
+
+void EasingCurveDialog::textChanged()
+{
+ auto curve = m_splineEditor->easingCurve();
+ curve.fromString(m_text->toPlainText());
+ m_splineEditor->setEasingCurve(curve);
+}
+
+void EasingCurveDialog::tabClicked(int id)
+{
+ if (auto tw = qobject_cast<const QTabWidget *>(sender())) {
+ int seid = tw->indexOf(m_splineEditor);
+ if (seid == id) {
+ for (int i = 0; i < m_durationLayout->count(); ++i) {
+ auto *item = m_durationLayout->itemAt(i);
+ if (auto *widget = item->widget())
+ widget->show();
+ }
+
+ auto curve = m_splineEditor->easingCurve();
+ curve.fromString(m_text->toPlainText());
+ m_splineEditor->setEasingCurve(curve);
+
+ } else {
+ for (int i = 0; i < m_durationLayout->count(); ++i) {
+ auto *item = m_durationLayout->itemAt(i);
+ if (auto *widget = item->widget())
+ widget->hide();
+ }
+
+ auto curve = m_splineEditor->easingCurve();
+ m_text->setPlainText(curve.toString());
+ }
+ }
+}
+
+void EasingCurveDialog::presetTabClicked(int id)
+{
+ m_presets->activate(id);
+}
+
+void EasingCurveDialog::updateEasingCurve(const EasingCurve &curve)
+{
+ if (!curve.isLegal()) {
+ auto *save = m_buttons->button(QDialogButtonBox::Save);
+ save->setEnabled(false);
+
+ auto *ok = m_buttons->button(QDialogButtonBox::Ok);
+ ok->setEnabled(false);
+
+ m_label->setText("Invalid Curve!");
+ } else {
+ auto *save = m_buttons->button(QDialogButtonBox::Save);
+ save->setEnabled(true);
+
+ auto *ok = m_buttons->button(QDialogButtonBox::Ok);
+ ok->setEnabled(true);
+
+ m_label->setText("");
+ }
+
+ m_presets->update(curve);
+}
+
+void EasingCurveDialog::buttonsClicked(QDialogButtonBox::StandardButton button)
+{
+ switch (button) {
+ case QDialogButtonBox::Ok:
+ if (apply())
+ close();
+ break;
+
+ case QDialogButtonBox::Cancel:
+ close();
+ break;
+
+ case QDialogButtonBox::Save:
+ m_presets->writePresets(m_splineEditor->easingCurve());
+ break;
+
+ default:
+ break;
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.h b/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.h
new file mode 100644
index 0000000000..a8c026989c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/easingcurvedialog.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDialog>
+#include <QDialogButtonBox>
+
+#include <modelnode.h>
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QPlainTextEdit;
+class QHBoxLayout;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class SplineEditor;
+class PresetEditor;
+class EasingCurve;
+
+class EasingCurveDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ EasingCurveDialog(const QList<ModelNode> &frames, QWidget *parent = nullptr);
+
+ void initialize(const QString &curveString);
+
+ static void runDialog(const QList<ModelNode> &frames, QWidget *parent = nullptr);
+
+private:
+ bool apply();
+
+ void textChanged();
+
+ void tabClicked(int id);
+
+ void presetTabClicked(int id);
+
+ void buttonsClicked(QDialogButtonBox::StandardButton button);
+
+ void updateEasingCurve(const EasingCurve &curve);
+
+private:
+ SplineEditor *m_splineEditor = nullptr;
+
+ QPlainTextEdit *m_text = nullptr;
+
+ PresetEditor *m_presets = nullptr;
+
+ QHBoxLayout *m_durationLayout = nullptr;
+
+ QDialogButtonBox *m_buttons = nullptr;
+
+ QLabel *m_label = nullptr;
+
+ QList<ModelNode> m_frames;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline.png b/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline.png
new file mode 100644
index 0000000000..af651276ed
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline@2x.png
new file mode 100644
index 0000000000..58a7174288
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/add_timeline@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/animation.png b/src/plugins/qmldesigner/components/timelineeditor/images/animation.png
new file mode 100644
index 0000000000..20ad0273b5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/animation.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/animation@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/animation@2x.png
new file mode 100644
index 0000000000..1ecf1857c7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/animation@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame.png b/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame.png
new file mode 100644
index 0000000000..69c93ebe3e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame@2x.png
new file mode 100644
index 0000000000..9bd8a52e59
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/back_one_frame@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor.png b/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor.png
new file mode 100644
index 0000000000..bda4dc0095
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor@2x.png
new file mode 100644
index 0000000000..3d5c3abe05
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/curve_editor@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker.png b/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker.png
new file mode 100644
index 0000000000..4842ac0738
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker@2x.png
new file mode 100644
index 0000000000..0d99fc180c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/curve_picker@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame.png b/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame.png
new file mode 100644
index 0000000000..0846f194e0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame@2x.png
new file mode 100644
index 0000000000..8e5ddc3930
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/forward_one_frame@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes.png b/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes.png
new file mode 100644
index 0000000000..64a28ca075
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes@2x.png
new file mode 100644
index 0000000000..534737f385
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/global_record_keyframes@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe.png b/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe.png
new file mode 100644
index 0000000000..5655e0b278
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe@2x.png
new file mode 100644
index 0000000000..2f522c22b6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/is_keyframe@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe-16px.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe-16px.png
new file mode 100644
index 0000000000..6e1c9f912a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe-16px.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe.png
new file mode 100644
index 0000000000..6bf7d1ad53
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe@2x.png
new file mode 100644
index 0000000000..5102e279a1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active.png
new file mode 100644
index 0000000000..8a3eaa7888
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active@2x.png
new file mode 100644
index 0000000000..e0168a097a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_active@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive.png
new file mode 100644
index 0000000000..2c12d98e01
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive@2x.png
new file mode 100644
index 0000000000..4bbbe6cd3f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_inactive@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected.png
new file mode 100644
index 0000000000..58ccb7c765
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected@2x.png
new file mode 100644
index 0000000000..829dd99391
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_autobezier_selected@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active.png
new file mode 100644
index 0000000000..a195ac5fca
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active@2x.png
new file mode 100644
index 0000000000..fd879e5837
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_active@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive.png
new file mode 100644
index 0000000000..b84a800097
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive@2x.png
new file mode 100644
index 0000000000..0ad868dcd6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_inactive@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected.png
new file mode 100644
index 0000000000..e840819f2d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected@2x.png
new file mode 100644
index 0000000000..e5f63f1fc9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_linear_selected@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active.png
new file mode 100644
index 0000000000..f85d3f78fd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active@2x.png
new file mode 100644
index 0000000000..2f65f7970c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_active@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive.png
new file mode 100644
index 0000000000..9798c76115
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive@2x.png
new file mode 100644
index 0000000000..b4ee45a566
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_inactive@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected.png
new file mode 100644
index 0000000000..1e39f84502
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected@2x.png
new file mode 100644
index 0000000000..b99474718c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_lineartobezier_selected@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active.png
new file mode 100644
index 0000000000..4b6a7c8978
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active@2x.png
new file mode 100644
index 0000000000..fd85a10758
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_active@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive.png
new file mode 100644
index 0000000000..9c0a1fd550
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive@2x.png
new file mode 100644
index 0000000000..1299a370fc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_inactive@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected.png
new file mode 100644
index 0000000000..7b5faebae0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected@2x.png
new file mode 100644
index 0000000000..726ead7a43
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/keyframe_manualbezier_selected@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes.png b/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes.png
new file mode 100644
index 0000000000..d68aa73214
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes@2x.png
new file mode 100644
index 0000000000..f5265a2218
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/local_record_keyframes@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback.png b/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback.png
new file mode 100644
index 0000000000..f38fbef1d4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback@2x.png
new file mode 100644
index 0000000000..b760a04133
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/loop_playback@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe.png b/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe.png
new file mode 100644
index 0000000000..415ec0127f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe@2x.png
new file mode 100644
index 0000000000..3f1e24e04a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/next_keyframe@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback.png b/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback.png
new file mode 100644
index 0000000000..001ca37b1c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback@2x.png
new file mode 100644
index 0000000000..95e8567ccb
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/pause_playback@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/playhead.png b/src/plugins/qmldesigner/components/timelineeditor/images/playhead.png
new file mode 100644
index 0000000000..518a77f404
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/playhead.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/playhead@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/playhead@2x.png
new file mode 100644
index 0000000000..7f6778556b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/playhead@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe.png b/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe.png
new file mode 100644
index 0000000000..52ba668973
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe@2x.png
new file mode 100644
index 0000000000..df151051fc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/previous_keyframe@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline.png b/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline.png
new file mode 100644
index 0000000000..0589f982a7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline@2x.png
new file mode 100644
index 0000000000..9eed9ce3c3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/remove_timeline@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/start_playback.png b/src/plugins/qmldesigner/components/timelineeditor/images/start_playback.png
new file mode 100644
index 0000000000..0cf0865c48
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/start_playback.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/start_playback@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/start_playback@2x.png
new file mode 100644
index 0000000000..f05dfcd9ed
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/start_playback@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/timeline-16px.png b/src/plugins/qmldesigner/components/timelineeditor/images/timeline-16px.png
new file mode 100644
index 0000000000..d4ecf00031
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/timeline-16px.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame.png b/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame.png
new file mode 100644
index 0000000000..910b856638
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame@2x.png
new file mode 100644
index 0000000000..abefa72fb3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/to_first_frame@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame.png b/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame.png
new file mode 100644
index 0000000000..d6bc429196
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame@2x.png
new file mode 100644
index 0000000000..affc3c9848
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/to_last_frame@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left.png b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left.png
new file mode 100644
index 0000000000..83d441d64f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left@2x.png
new file mode 100644
index 0000000000..0fec4b269e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_left@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right.png b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right.png
new file mode 100644
index 0000000000..611684a7f6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right@2x.png
new file mode 100644
index 0000000000..c1dbdbc56c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/work_area_handle_right@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big.png b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big.png
new file mode 100644
index 0000000000..eec61eb86c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big@2x.png
new file mode 100644
index 0000000000..1706de0bb4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_big@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small.png b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small.png
new file mode 100644
index 0000000000..20433d99c4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small@2x.png b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small@2x.png
new file mode 100644
index 0000000000..326ea32c25
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/images/zoom_small@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/components/timelineeditor/preseteditor.cpp b/src/plugins/qmldesigner/components/timelineeditor/preseteditor.cpp
new file mode 100644
index 0000000000..8ea7692e3d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/preseteditor.cpp
@@ -0,0 +1,560 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "preseteditor.h"
+
+#include "canvas.h"
+#include "easingcurve.h"
+#include "timelineicons.h"
+
+#include <QAbstractButton>
+#include <QApplication>
+#include <QContextMenuEvent>
+#include <QMenu>
+#include <QMessageBox>
+#include <QPainter>
+#include <QPixmap>
+#include <QSettings>
+#include <QStandardItemModel>
+#include <QString>
+
+#include <coreplugin/icore.h>
+#include <theme.h>
+
+namespace QmlDesigner {
+
+constexpr int iconWidth = 86;
+constexpr int iconHeight = 86;
+constexpr int itemFrame = 3;
+constexpr int itemWidth = iconWidth + 2 * itemFrame;
+constexpr int unsavedMarkSize = 18;
+
+constexpr int spacingg = 5;
+
+const QColor background = Qt::white;
+
+const QColor labelBackground = qRgb(0x70, 0x70, 0x70);
+const QColor canvasBackground = qRgb(0x46, 0x46, 0x46);
+const QColor curveLine = qRgb(0xe6, 0xe7, 0xe8);
+
+PresetItemDelegate::PresetItemDelegate() = default;
+
+void PresetItemDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &opt,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItem option = opt;
+ initStyleOption(&option, index);
+
+ auto *w = option.widget;
+ auto *style = w == nullptr ? qApp->style() : w->style();
+
+ QSize textSize = QSize(option.rect.width(),
+ style->subElementRect(QStyle::SE_ItemViewItemText, &option, w).height());
+
+ auto textRect = QRect(option.rect.topLeft(), textSize);
+ textRect.moveBottom(option.rect.bottom());
+
+ option.font.setPixelSize(Theme::instance()->smallFontPixelSize());
+
+ painter->save();
+ painter->fillRect(option.rect, canvasBackground);
+
+ if (option.text.isEmpty())
+ painter->fillRect(textRect, canvasBackground);
+ else
+ painter->fillRect(textRect, Theme::instance()->qmlDesignerButtonColor());
+
+ style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
+
+ QVariant dirty = option.index.data(PresetList::ItemRole_Dirty);
+ if (dirty.isValid()) {
+ if (dirty.toBool()) {
+ QRect asteriskRect(option.rect.right() - unsavedMarkSize,
+ itemFrame,
+ unsavedMarkSize,
+ unsavedMarkSize);
+
+ QFont font = painter->font();
+ font.setPixelSize(unsavedMarkSize);
+ painter->setFont(font);
+
+ auto pen = painter->pen();
+ pen.setColor(Qt::white);
+ painter->setPen(pen);
+
+ painter->drawText(asteriskRect, Qt::AlignTop | Qt::AlignRight, "*");
+ }
+ }
+ painter->restore();
+}
+
+QSize PresetItemDelegate::sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const
+{
+ QSize size = QStyledItemDelegate::sizeHint(opt, index);
+ size.rwidth() = itemWidth;
+ return size;
+}
+
+QIcon paintPreview()
+{
+ QPixmap pm(iconWidth, iconHeight);
+ pm.fill(canvasBackground);
+ return QIcon(pm);
+}
+
+QIcon paintPreview(const EasingCurve &curve)
+{
+ QPixmap pm(iconWidth, iconHeight);
+ pm.fill(canvasBackground);
+
+ QPainter painter(&pm);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+
+ Canvas canvas(iconWidth, iconHeight, 2, 2, 9, 6, 0, 1);
+ canvas.paintCurve(&painter, curve, curveLine);
+
+ return QIcon(pm);
+}
+
+namespace Internal {
+
+static const char settingsKey[] = "EasingCurveList";
+static const char settingsFileName[] = "/EasingCurves.ini";
+
+QString settingsFullFilePath(const QSettings::Scope &scope)
+{
+ if (scope == QSettings::SystemScope)
+ return Core::ICore::installerResourcePath() + settingsFileName;
+
+ return Core::ICore::userResourcePath() + settingsFileName;
+}
+
+} // namespace Internal
+
+PresetList::PresetList(QSettings::Scope scope, QWidget *parent)
+ : QListView(parent)
+ , m_scope(scope)
+ , m_index(-1)
+ , m_filename(Internal::settingsFullFilePath(scope))
+{
+ int magic = 4;
+ int scrollBarWidth = this->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+ const int width = 3 * itemWidth + 4 * spacingg + scrollBarWidth + magic;
+
+ setFixedWidth(width);
+
+ setModel(new QStandardItemModel);
+
+ setItemDelegate(new PresetItemDelegate);
+
+ setSpacing(spacingg);
+
+ setUniformItemSizes(true);
+
+ setIconSize(QSize(iconWidth, iconHeight));
+
+ setSelectionMode(QAbstractItemView::SingleSelection);
+
+ setViewMode(QListView::IconMode);
+
+ setFlow(QListView::LeftToRight);
+
+ setMovement(QListView::Static);
+
+ setWrapping(true);
+
+ setTextElideMode(Qt::ElideMiddle);
+
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+}
+
+void PresetList::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
+{
+ for (const QModelIndex &index : deselected.indexes()) {
+ if (dirty(index)) {
+ QMessageBox msgBox;
+ msgBox.setText("The preset has been modified.");
+ msgBox.setInformativeText("Do you want to save your changes?");
+ msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard
+ | QMessageBox::Cancel);
+ msgBox.setDefaultButton(QMessageBox::Save);
+
+ if (QAbstractButton *button = msgBox.button(QMessageBox::Discard))
+ button->setText("Discard Changes");
+
+ if (QAbstractButton *button = msgBox.button(QMessageBox::Cancel))
+ button->setText("Cancel Selection");
+
+ int ret = msgBox.exec();
+
+ switch (ret) {
+ case QMessageBox::Save:
+ // Save the preset and continue selection.
+ writePresets();
+ break;
+ case QMessageBox::Discard:
+ // Discard changes to the curve and continue selection.
+ revert(index);
+ break;
+
+ case QMessageBox::Cancel:
+ // Cancel selection operation and leave the curve untouched.
+ selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
+ return;
+
+ default:
+ // should never be reachedDiscard
+ break;
+ }
+ }
+ }
+
+ for (const auto &index : selected.indexes()) {
+ QVariant curveData = model()->data(index, ItemRole_Data);
+ if (curveData.isValid())
+ emit presetChanged(curveData.value<EasingCurve>());
+ }
+}
+
+bool PresetList::hasSelection() const
+{
+ return selectionModel()->hasSelection();
+}
+
+bool PresetList::dirty(const QModelIndex &index) const
+{
+ return model()->data(index, ItemRole_Dirty).toBool();
+}
+
+int PresetList::index() const
+{
+ return m_index;
+}
+
+bool PresetList::isEditable(const QModelIndex &index) const
+{
+ QFlags<Qt::ItemFlag> flags(model()->flags(index));
+ return flags.testFlag(Qt::ItemIsEditable);
+}
+
+void PresetList::initialize(int index)
+{
+ m_index = index;
+
+ readPresets();
+}
+
+void PresetList::readPresets()
+{
+ auto *simodel = qobject_cast<QStandardItemModel *>(model());
+
+ simodel->clear();
+
+ QList<NamedEasingCurve> curves = storedCurves();
+
+ for (int i = 0; i < curves.size(); ++i) {
+ QVariant curveData = QVariant::fromValue(curves[i].curve());
+
+ auto *item = new QStandardItem(paintPreview(curves[i].curve()), curves[i].name());
+ item->setData(curveData, ItemRole_Data);
+ item->setEditable(m_scope == QSettings::UserScope);
+ item->setToolTip(curves[i].name());
+
+ simodel->setItem(i, item);
+ }
+}
+
+void PresetList::writePresets()
+{
+ QList<QVariant> presets;
+ for (int i = 0; i < model()->rowCount(); ++i) {
+ QModelIndex index = model()->index(i, 0);
+
+ QVariant nameData = model()->data(index, Qt::DisplayRole);
+ QVariant curveData = model()->data(index, ItemRole_Data);
+
+ if (nameData.isValid() && curveData.isValid()) {
+ NamedEasingCurve curve(nameData.toString(), curveData.value<QmlDesigner::EasingCurve>());
+
+ presets << QVariant::fromValue(curve);
+ }
+
+ model()->setData(index, false, ItemRole_Dirty);
+ }
+
+ QSettings settings(m_filename, QSettings::IniFormat);
+ settings.clear();
+ settings.setValue(Internal::settingsKey, QVariant::fromValue(presets));
+}
+
+void PresetList::revert(const QModelIndex &index)
+{
+ auto *simodel = qobject_cast<QStandardItemModel *>(model());
+ if (auto *item = simodel->itemFromIndex(index)) {
+ QString name = item->data(Qt::DisplayRole).toString();
+ QList<NamedEasingCurve> curves = storedCurves();
+
+ for (const auto &curve : curves) {
+ if (curve.name() == name) {
+ item->setData(false, ItemRole_Dirty);
+ item->setData(paintPreview(curve.curve()), Qt::DecorationRole);
+ item->setData(QVariant::fromValue(curve.curve()), ItemRole_Data);
+ item->setToolTip(name);
+ return;
+ }
+ }
+ }
+}
+
+void PresetList::updateCurve(const EasingCurve &curve)
+{
+ if (!selectionModel()->hasSelection())
+ return;
+
+ QVariant icon = QVariant::fromValue(paintPreview(curve));
+ QVariant curveData = QVariant::fromValue(curve);
+
+ for (const auto &index : selectionModel()->selectedIndexes())
+ setItemData(index, curveData, icon);
+}
+
+void PresetList::contextMenuEvent(QContextMenuEvent *event)
+{
+ event->accept();
+
+ if (m_scope == QSettings::SystemScope)
+ return;
+
+ QMenu menu;
+
+ QAction *addAction = menu.addAction(tr("Add Preset"));
+
+ connect(addAction, &QAction::triggered, [&]() { createItem(); });
+
+ if (selectionModel()->hasSelection()) {
+ QAction *removeAction = menu.addAction(tr("Delete Selected Preset"));
+ connect(removeAction, &QAction::triggered, [&]() { removeSelectedItem(); });
+ }
+
+ menu.exec(event->globalPos());
+}
+
+void PresetList::dataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QVector<int> &roles)
+{
+ if (topLeft == bottomRight && roles.contains(0)) {
+ QVariant name = model()->data(topLeft, 0);
+ model()->setData(topLeft, name, Qt::ToolTipRole);
+ }
+}
+
+void PresetList::createItem()
+{
+ EasingCurve curve;
+ curve.makeDefault();
+ createItem(createUniqueName(), curve);
+}
+
+void PresetList::createItem(const QString &name, const EasingCurve &curve)
+{
+ auto *item = new QStandardItem(paintPreview(curve), name);
+ item->setData(QVariant::fromValue(curve), ItemRole_Data);
+ item->setToolTip(name);
+
+ int row = model()->rowCount();
+ qobject_cast<QStandardItemModel *>(model())->setItem(row, item);
+
+ QModelIndex index = model()->index(row, 0);
+
+ // Why is that needed? SingleSelection is specified.
+ selectionModel()->clear();
+ selectionModel()->select(index, QItemSelectionModel::Select);
+}
+
+void PresetList::removeSelectedItem()
+{
+ for (const auto &index : selectionModel()->selectedIndexes())
+ model()->removeRow(index.row());
+
+ writePresets();
+}
+
+void PresetList::setItemData(const QModelIndex &index, const QVariant &curve, const QVariant &icon)
+{
+ if (isEditable(index)) {
+ model()->setData(index, curve, PresetList::ItemRole_Data);
+ model()->setData(index, true, PresetList::ItemRole_Dirty);
+ model()->setData(index, icon, Qt::DecorationRole);
+ }
+}
+
+QString PresetList::createUniqueName() const
+{
+ QStringList names = allNames();
+ auto nameIsUnique = [&](const QString &name) {
+ auto iter = std::find(names.begin(), names.end(), name);
+ if (iter == names.end())
+ return true;
+ else
+ return false;
+ };
+
+ int counter = 0;
+ QString tmp("Default");
+ QString name = tmp;
+
+ while (!nameIsUnique(name))
+ name = tmp + QString(" %1").arg(counter++);
+
+ return name;
+}
+
+QStringList PresetList::allNames() const
+{
+ QStringList names;
+ for (int i = 0; i < model()->rowCount(); ++i) {
+ QModelIndex index = model()->index(i, 0);
+ QVariant nameData = model()->data(index, Qt::DisplayRole);
+ if (nameData.isValid())
+ names << nameData.toString();
+ }
+
+ return names;
+}
+
+QList<NamedEasingCurve> PresetList::storedCurves() const
+{
+ QSettings settings(m_filename, QSettings::IniFormat);
+ QVariant presetSettings = settings.value(Internal::settingsKey);
+
+ if (!presetSettings.isValid())
+ return QList<NamedEasingCurve>();
+
+ QList<QVariant> presets = presetSettings.toList();
+
+ QList<NamedEasingCurve> out;
+ for (const QVariant &preset : presets)
+ if (preset.isValid())
+ out << preset.value<NamedEasingCurve>();
+
+ return out;
+}
+
+PresetEditor::PresetEditor(QWidget *parent)
+ : QStackedWidget(parent)
+ , m_presets(new PresetList(QSettings::SystemScope))
+ , m_customs(new PresetList(QSettings::UserScope))
+{
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+
+ addWidget(m_presets);
+ addWidget(m_customs);
+
+ connect(m_presets, &PresetList::presetChanged, this, &PresetEditor::presetChanged);
+ connect(m_customs, &PresetList::presetChanged, this, &PresetEditor::presetChanged);
+}
+
+void PresetEditor::initialize(QTabBar *bar)
+{
+ m_presets->initialize(bar->addTab("Presets"));
+ m_customs->initialize(bar->addTab("Custom"));
+
+ connect(bar, &QTabBar::currentChanged, this, &PresetEditor::activate);
+ connect(this, &PresetEditor::currentChanged, bar, &QTabBar::setCurrentIndex);
+
+ m_presets->selectionModel()->clear();
+ m_customs->selectionModel()->clear();
+
+ activate(m_presets->index());
+}
+
+void PresetEditor::activate(int id)
+{
+ if (id == m_presets->index())
+ setCurrentWidget(m_presets);
+ else
+ setCurrentWidget(m_customs);
+}
+
+void PresetEditor::update(const EasingCurve &curve)
+{
+ if (isCurrent(m_presets))
+ m_presets->selectionModel()->clear();
+ else {
+ if (m_customs->selectionModel()->hasSelection()) {
+ QVariant icon = QVariant::fromValue(paintPreview(curve));
+ QVariant curveData = QVariant::fromValue(curve);
+ for (const QModelIndex &index : m_customs->selectionModel()->selectedIndexes())
+ m_customs->setItemData(index, curveData, icon);
+ }
+ }
+}
+
+bool PresetEditor::writePresets(const EasingCurve &curve)
+{
+ if (!curve.isLegal()) {
+ QMessageBox msgBox;
+ msgBox.setText("Attempting to save invalid curve");
+ msgBox.setInformativeText("Please solve the issue before proceeding.");
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.exec();
+ return false;
+ }
+
+ if (auto current = qobject_cast<const PresetList *>(currentWidget())) {
+ if (current->index() == m_presets->index()
+ || (current->index() == m_customs->index() && !m_customs->hasSelection())) {
+ bool ok;
+ QString name = QInputDialog::getText(this,
+ tr("Save Preset"),
+ tr("Name"),
+ QLineEdit::Normal,
+ QString(),
+ &ok);
+
+ if (ok && !name.isEmpty()) {
+ activate(m_customs->index());
+ m_customs->createItem(name, curve);
+ }
+ }
+
+ m_customs->writePresets();
+ return true;
+ }
+
+ return false;
+}
+
+bool PresetEditor::isCurrent(PresetList *list)
+{
+ if (auto current = qobject_cast<const PresetList *>(currentWidget()))
+ return list->index() == current->index();
+
+ return false;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/preseteditor.h b/src/plugins/qmldesigner/components/timelineeditor/preseteditor.h
new file mode 100644
index 0000000000..6fab3e7adb
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/preseteditor.h
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QInputDialog>
+#include <QListView>
+#include <QSettings>
+#include <QStackedWidget>
+#include <QStyledItemDelegate>
+
+QT_FORWARD_DECLARE_CLASS(QString)
+
+namespace QmlDesigner {
+
+class EasingCurve;
+class NamedEasingCurve;
+
+class PresetItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ PresetItemDelegate();
+
+ void paint(QPainter *painter,
+ const QStyleOptionViewItem &opt,
+ const QModelIndex &index) const override;
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+};
+
+class PresetList : public QListView
+{
+ Q_OBJECT
+
+public:
+ enum ItemRoles {
+ ItemRole_Undefined = Qt::UserRole,
+ ItemRole_Data,
+ ItemRole_Dirty,
+ ItemRole_Modifiable
+ };
+
+signals:
+ void presetChanged(const EasingCurve &curve);
+
+public:
+ PresetList(QSettings::Scope scope, QWidget *parent = nullptr);
+
+ void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
+
+ bool hasSelection() const;
+
+ bool dirty(const QModelIndex &index) const;
+
+ int index() const;
+
+ bool isEditable(const QModelIndex &index) const;
+
+ void initialize(int index);
+
+ void readPresets();
+
+ void writePresets();
+
+ void revert(const QModelIndex &index);
+
+ void updateCurve(const EasingCurve &curve);
+
+ void createItem();
+
+ void createItem(const QString &name, const EasingCurve &curve);
+
+ void setItemData(const QModelIndex &index, const QVariant &curve, const QVariant &icon);
+
+protected:
+ void contextMenuEvent(QContextMenuEvent *event) override;
+
+ void dataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QVector<int> &roles = QVector<int>()) override;
+
+private:
+ QStringList allNames() const;
+
+ QList<NamedEasingCurve> storedCurves() const;
+
+ QString createUniqueName() const;
+
+ void removeSelectedItem();
+
+private:
+ QSettings::Scope m_scope;
+
+ int m_index;
+
+ QString m_filename;
+};
+
+class PresetEditor : public QStackedWidget
+{
+ Q_OBJECT
+
+signals:
+ void presetChanged(const EasingCurve &curve);
+
+public:
+ explicit PresetEditor(QWidget *parent = nullptr);
+
+ void initialize(QTabBar *bar);
+
+ void activate(int id);
+
+ void update(const EasingCurve &curve);
+
+ void readPresets();
+
+ bool writePresets(const EasingCurve &curve);
+
+private:
+ bool isCurrent(PresetList *list);
+
+private:
+ PresetList *m_presets;
+
+ PresetList *m_customs;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp
new file mode 100644
index 0000000000..4120aaee21
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "setframevaluedialog.h"
+#include "ui_setframevaluedialog.h"
+
+namespace QmlDesigner {
+
+SetFrameValueDialog::SetFrameValueDialog(QWidget *parent)
+ : QDialog(parent)
+ , ui(new Ui::SetFrameValueDialog)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ ui->setupUi(this);
+}
+
+SetFrameValueDialog::~SetFrameValueDialog()
+{
+ delete ui;
+}
+
+QLineEdit *SetFrameValueDialog::lineEdit() const
+{
+ return ui->lineEdit;
+}
+
+void SetFrameValueDialog::setPropertName(const QString &name)
+{
+ setWindowTitle(tr("Change %1").arg(name));
+ ui->label->setText(name);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.h b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.h
new file mode 100644
index 0000000000..e7ed226b67
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDialog>
+
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+
+namespace QmlDesigner {
+
+namespace Ui {
+class SetFrameValueDialog;
+}
+
+class SetFrameValueDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SetFrameValueDialog(QWidget *parent = nullptr);
+ ~SetFrameValueDialog() override;
+
+ QLineEdit *lineEdit() const;
+
+ void setPropertName(const QString &name);
+
+private:
+ Ui::SetFrameValueDialog *ui;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.ui b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.ui
new file mode 100644
index 0000000000..2fa1241e4a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::SetFrameValueDialog</class>
+ <widget class="QDialog" name="QmlDesigner::SetFrameValueDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>184</width>
+ <height>79</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <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>QmlDesigner::SetFrameValueDialog</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>QmlDesigner::SetFrameValueDialog</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/plugins/qmldesigner/components/timelineeditor/splineeditor.cpp b/src/plugins/qmldesigner/components/timelineeditor/splineeditor.cpp
new file mode 100644
index 0000000000..44ef1f194c
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/splineeditor.cpp
@@ -0,0 +1,274 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "splineeditor.h"
+
+#include <theme.h>
+
+#include <QAction>
+#include <QApplication>
+#include <QContextMenuEvent>
+#include <QMenu>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QPropertyAnimation>
+#include <QResizeEvent>
+
+namespace QmlDesigner {
+
+SplineEditor::SplineEditor(QWidget *parent)
+ : QWidget(parent)
+ , m_canvas(0, 0, 25, 25, 9, 6, 0, 1)
+ , m_animation(new QPropertyAnimation(this, "progress"))
+{
+ m_animation->setStartValue(0.0);
+ m_animation->setEndValue(1.0);
+ m_animation->setLoopCount(1);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+double SplineEditor::progress() const
+{
+ return m_progress;
+}
+
+EasingCurve SplineEditor::easingCurve() const
+{
+ return m_curve;
+}
+
+void SplineEditor::animate() const
+{
+ m_animation->start();
+}
+
+void SplineEditor::setDuration(int duration)
+{
+ m_animation->setDuration(duration);
+}
+
+void SplineEditor::setProgress(double progress)
+{
+ m_progress = progress;
+ update();
+}
+
+void SplineEditor::setEasingCurve(const EasingCurve &curve)
+{
+ m_curve = curve;
+ update();
+}
+
+void SplineEditor::resizeEvent(QResizeEvent *event)
+{
+ m_canvas.resize(event->size());
+ QWidget::resizeEvent(event);
+}
+
+void SplineEditor::paintEvent(QPaintEvent *)
+{
+ QPainter painter(this);
+
+ QPen pen(Qt::black);
+ pen.setWidth(1);
+ painter.drawRect(0, 0, width() - 1, height() - 1);
+
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ pen = QPen(Qt::darkGray);
+ pen.setWidth(1);
+ painter.setPen(pen);
+
+ QColor curveColor = Qt::white;
+ if (!m_curve.isLegal())
+ curveColor = Qt::red;
+
+ QBrush background(Theme::instance()->qmlDesignerBackgroundColorDarker());
+ m_canvas.paintGrid(&painter, background);
+ m_canvas.paintCurve(&painter, m_curve, curveColor);
+ m_canvas.paintControlPoints(&painter, m_curve);
+
+ if (m_animation->state() == QAbstractAnimation::Running)
+ m_canvas.paintProgress(&painter, m_curve, m_progress);
+}
+
+void SplineEditor::mousePressEvent(QMouseEvent *e)
+{
+ bool clearActive = true;
+ if (e->button() == Qt::LeftButton) {
+ EasingCurve mappedCurve = m_canvas.mapTo(m_curve);
+ int active = mappedCurve.hit(e->pos(), 10);
+
+ if (EasingCurve::IsValidIndex(active)) {
+ clearActive = false;
+ m_curve.setActive(active);
+ mouseMoveEvent(e);
+ }
+
+ m_mousePress = e->pos();
+ e->accept();
+ }
+
+ if (clearActive) {
+ m_curve.clearActive();
+ update();
+ }
+}
+
+void SplineEditor::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (e->button() == Qt::LeftButton) {
+ m_mouseDrag = false;
+ e->accept();
+ }
+}
+
+void dragHandle(EasingCurve &curve, int id, const QPointF &pos)
+{
+ QPointF distance = pos - curve.point(id);
+
+ curve.setPoint(id, pos);
+
+ if (curve.isLeftHandle(id))
+ curve.movePoint(id + 2, -distance);
+ else
+ curve.movePoint(id - 2, -distance);
+}
+
+void SplineEditor::mouseMoveEvent(QMouseEvent *e)
+{
+ // If we've moved more then 25 pixels, assume user is dragging
+ if (!m_mouseDrag
+ && QPoint(m_mousePress - e->pos()).manhattanLength() > qApp->startDragDistance())
+ m_mouseDrag = true;
+
+ if (m_mouseDrag && m_curve.hasActive()) {
+ QPointF p = m_canvas.mapFrom(e->pos());
+ int active = m_curve.active();
+
+ if ((active == 0 || active == m_curve.count() - 2)
+ && e->modifiers().testFlag(Qt::ShiftModifier)) {
+ if (active == 0) {
+ QPointF opposite = QPointF(1.0, 1.0) - p;
+ dragHandle(m_curve, active, p);
+ dragHandle(m_curve, m_curve.count() - 2, opposite);
+
+ } else {
+ QPointF opposite = QPointF(1.0, 1.0) - p;
+ dragHandle(m_curve, active, p);
+ dragHandle(m_curve, 0, opposite);
+ }
+
+ } else if (m_curve.isHandle(active)) {
+ int poc = m_curve.curvePoint(active);
+
+ if (!m_curve.isSmooth(poc))
+ m_curve.setPoint(active, p);
+ else
+ dragHandle(m_curve, active, p);
+
+ } else {
+ QPointF targetPoint = p;
+ QPointF distance = targetPoint - m_curve.point(m_curve.active());
+
+ m_curve.setPoint(active, targetPoint);
+ m_curve.movePoint(active + 1, distance);
+ m_curve.movePoint(active - 1, distance);
+ }
+
+ update();
+ emit easingCurveChanged(m_curve);
+ }
+}
+
+void SplineEditor::contextMenuEvent(QContextMenuEvent *e)
+{
+ m_curve.clearActive();
+
+ QMenu menu;
+
+ EasingCurve mappedCurve = m_canvas.mapTo(m_curve);
+ int index = mappedCurve.hit(e->pos(), 10);
+
+ if (index > 0 && !m_curve.isHandle(index)) {
+ QAction *deleteAction = menu.addAction(tr("Delete Point"));
+ connect(deleteAction, &QAction::triggered, [this, index]() {
+ m_curve.deletePoint(index);
+ update();
+ emit easingCurveChanged(m_curve);
+ });
+
+ QAction *smoothAction = menu.addAction(tr("Smooth Point"));
+ smoothAction->setCheckable(true);
+ smoothAction->setChecked(m_curve.isSmooth(index));
+ connect(smoothAction, &QAction::triggered, [this, index]() {
+ m_curve.makeSmooth(index);
+ update();
+ emit easingCurveChanged(m_curve);
+ });
+
+ QAction *cornerAction = menu.addAction(tr("Corner Point"));
+ connect(cornerAction, &QAction::triggered, [this, index]() {
+ m_curve.breakTangent(index);
+ update();
+ emit easingCurveChanged(m_curve);
+ });
+
+ } else {
+ QAction *addAction = menu.addAction(tr("Add Point"));
+ connect(addAction, &QAction::triggered, [&]() {
+ m_curve.addPoint(m_canvas.mapFrom(e->pos()));
+ m_curve.makeSmooth(m_curve.active());
+ update();
+ emit easingCurveChanged(m_curve);
+ });
+ }
+
+ QAction *zoomAction = menu.addAction(tr("Reset Zoom"));
+ connect(zoomAction, &QAction::triggered, [&]() {
+ m_canvas.setScale(1.0);
+ update();
+ });
+
+ menu.exec(e->globalPos());
+ e->accept();
+}
+
+void SplineEditor::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ m_animation->start();
+ QWidget::mouseDoubleClickEvent(event);
+}
+
+void SplineEditor::wheelEvent(QWheelEvent *event)
+{
+ double tmp = event->angleDelta().y() > 0 ? 0.05 : -0.05;
+
+ m_canvas.setScale(m_canvas.scale() + tmp);
+ event->accept();
+ update();
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/splineeditor.h b/src/plugins/qmldesigner/components/timelineeditor/splineeditor.h
new file mode 100644
index 0000000000..8b454943ad
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/splineeditor.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QWidget>
+
+#include "canvas.h"
+#include "easingcurve.h"
+
+QT_FORWARD_DECLARE_CLASS(QPropertyAnimation)
+
+namespace QmlDesigner {
+
+class SegmentProperties;
+
+class SplineEditor : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(double progress READ progress WRITE setProgress)
+
+signals:
+ void easingCurveChanged(const EasingCurve &curve);
+
+public:
+ explicit SplineEditor(QWidget *parent = nullptr);
+
+ double progress() const;
+
+ EasingCurve easingCurve() const;
+
+ void animate() const;
+
+ void setDuration(int duration);
+
+ void setProgress(double progress);
+
+ void setEasingCurve(const EasingCurve &curve);
+
+protected:
+ void resizeEvent(QResizeEvent *) override;
+
+ void paintEvent(QPaintEvent *) override;
+
+ void mousePressEvent(QMouseEvent *) override;
+
+ void mouseMoveEvent(QMouseEvent *) override;
+
+ void mouseReleaseEvent(QMouseEvent *) override;
+
+ void contextMenuEvent(QContextMenuEvent *) override;
+
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+
+ void wheelEvent(QWheelEvent *event) override;
+
+private:
+ Canvas m_canvas;
+
+ EasingCurve m_curve;
+
+ QPoint m_mousePress;
+
+ bool m_mouseDrag = false;
+
+ bool m_block = false;
+
+ double m_progress = 0;
+
+ QPropertyAnimation *m_animation;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timeline.metainfo b/src/plugins/qmldesigner/components/timelineeditor/timeline.metainfo
new file mode 100644
index 0000000000..c1d7e92bf5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timeline.metainfo
@@ -0,0 +1,35 @@
+MetaInfo {
+ Type {
+ name: "QtQuick.Timeline.Timeline"
+ icon: ":/timelineplugin/images/timeline-16px.png"
+
+ ItemLibraryEntry {
+ name: "Timeline"
+ category: "none"
+ version: "1.0"
+ requiredImport: "none"
+ }
+ }
+ Type {
+ name: "QtQuick.Timeline.Keyframe"
+ icon: ":/timelineplugin/images/keyframe-16px.png"
+
+ ItemLibraryEntry {
+ name: "Keyframe"
+ category: "none"
+ version: "1.0"
+ requiredImport: "none"
+ }
+ }
+ Type {
+ name: "QtQuick.Timeline.KeyframeGroup"
+ icon: ":/timelineplugin/images/keyframe-16px.png"
+
+ ItemLibraryEntry {
+ name: "KeyframeGroup"
+ category: "none"
+ version: "1.0"
+ requiredImport: "none"
+ }
+ }
+}
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timeline.qrc b/src/plugins/qmldesigner/components/timelineeditor/timeline.qrc
new file mode 100644
index 0000000000..b793c1f8da
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timeline.qrc
@@ -0,0 +1,77 @@
+<RCC>
+ <qresource prefix="/timelineplugin">
+ <file>timeline.metainfo</file>
+ <file>images/add_timeline.png</file>
+ <file>images/add_timeline@2x.png</file>
+ <file>images/animation.png</file>
+ <file>images/animation@2x.png</file>
+ <file>images/back_one_frame.png</file>
+ <file>images/back_one_frame@2x.png</file>
+ <file>images/curve_editor.png</file>
+ <file>images/curve_editor@2x.png</file>
+ <file>images/curve_picker.png</file>
+ <file>images/curve_picker@2x.png</file>
+ <file>images/forward_one_frame.png</file>
+ <file>images/forward_one_frame@2x.png</file>
+ <file>images/global_record_keyframes.png</file>
+ <file>images/global_record_keyframes@2x.png</file>
+ <file>images/is_keyframe.png</file>
+ <file>images/is_keyframe@2x.png</file>
+ <file>images/keyframe.png</file>
+ <file>images/keyframe@2x.png</file>
+ <file>images/keyframe_autobezier_active.png</file>
+ <file>images/keyframe_autobezier_active@2x.png</file>
+ <file>images/keyframe_autobezier_inactive.png</file>
+ <file>images/keyframe_autobezier_inactive@2x.png</file>
+ <file>images/keyframe_autobezier_selected.png</file>
+ <file>images/keyframe_autobezier_selected@2x.png</file>
+ <file>images/keyframe_linear_active.png</file>
+ <file>images/keyframe_linear_active@2x.png</file>
+ <file>images/keyframe_linear_inactive.png</file>
+ <file>images/keyframe_linear_inactive@2x.png</file>
+ <file>images/keyframe_linear_selected.png</file>
+ <file>images/keyframe_linear_selected@2x.png</file>
+ <file>images/keyframe_lineartobezier_active.png</file>
+ <file>images/keyframe_lineartobezier_active@2x.png</file>
+ <file>images/keyframe_lineartobezier_inactive.png</file>
+ <file>images/keyframe_lineartobezier_inactive@2x.png</file>
+ <file>images/keyframe_lineartobezier_selected.png</file>
+ <file>images/keyframe_lineartobezier_selected@2x.png</file>
+ <file>images/keyframe_manualbezier_active.png</file>
+ <file>images/keyframe_manualbezier_active@2x.png</file>
+ <file>images/keyframe_manualbezier_inactive.png</file>
+ <file>images/keyframe_manualbezier_inactive@2x.png</file>
+ <file>images/keyframe_manualbezier_selected.png</file>
+ <file>images/keyframe_manualbezier_selected@2x.png</file>
+ <file>images/local_record_keyframes.png</file>
+ <file>images/local_record_keyframes@2x.png</file>
+ <file>images/loop_playback.png</file>
+ <file>images/loop_playback@2x.png</file>
+ <file>images/next_keyframe.png</file>
+ <file>images/next_keyframe@2x.png</file>
+ <file>images/previous_keyframe.png</file>
+ <file>images/previous_keyframe@2x.png</file>
+ <file>images/start_playback.png</file>
+ <file>images/start_playback@2x.png</file>
+ <file>images/to_first_frame.png</file>
+ <file>images/to_first_frame@2x.png</file>
+ <file>images/to_last_frame.png</file>
+ <file>images/to_last_frame@2x.png</file>
+ <file>images/pause_playback.png</file>
+ <file>images/pause_playback@2x.png</file>
+ <file>images/playhead.png</file>
+ <file>images/playhead@2x.png</file>
+ <file>images/work_area_handle_left.png</file>
+ <file>images/work_area_handle_left@2x.png</file>
+ <file>images/work_area_handle_right.png</file>
+ <file>images/work_area_handle_right@2x.png</file>
+ <file>images/zoom_big.png</file>
+ <file>images/zoom_big@2x.png</file>
+ <file>images/zoom_small.png</file>
+ <file>images/zoom_small@2x.png</file>
+ <file>images/keyframe-16px.png</file>
+ <file>images/timeline-16px.png</file>
+ <file>images/remove_timeline.png</file>
+ <file>images/remove_timeline@2x.png</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp
new file mode 100644
index 0000000000..485bb8dbb0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineabstracttool.h"
+
+#include "timelinetooldelegate.h"
+
+#include <QPointF>
+
+namespace QmlDesigner {
+
+TimelineAbstractTool::TimelineAbstractTool(TimelineGraphicsScene *scene)
+ : m_scene(scene)
+ , m_delegate(nullptr)
+{}
+
+TimelineAbstractTool::TimelineAbstractTool(TimelineGraphicsScene *scene,
+ TimelineToolDelegate *delegate)
+ : m_scene(scene)
+ , m_delegate(delegate)
+{}
+
+TimelineAbstractTool::~TimelineAbstractTool() = default;
+
+TimelineGraphicsScene *TimelineAbstractTool::scene() const
+{
+ return m_scene;
+}
+
+TimelineToolDelegate *TimelineAbstractTool::delegate() const
+{
+ return m_delegate;
+}
+
+QPointF TimelineAbstractTool::startPosition() const
+{
+ return m_delegate->startPoint();
+}
+
+TimelineMovableAbstractItem *TimelineAbstractTool::currentItem() const
+{
+ return m_delegate->item();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h
new file mode 100644
index 0000000000..0411a8d166
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsSceneMouseEvent)
+QT_FORWARD_DECLARE_CLASS(QKeyEvent)
+QT_FORWARD_DECLARE_CLASS(QPointF)
+
+namespace QmlDesigner {
+
+enum class ToolType { Move, Select };
+
+class TimelineMovableAbstractItem;
+class TimelineGraphicsScene;
+class TimelineToolDelegate;
+
+class TimelineAbstractTool
+{
+public:
+ explicit TimelineAbstractTool(TimelineGraphicsScene *scene);
+ explicit TimelineAbstractTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+ virtual ~TimelineAbstractTool();
+
+ virtual void mousePressEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) = 0;
+ virtual void mouseMoveEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) = 0;
+ virtual void mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) = 0;
+ virtual void mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) = 0;
+
+ virtual void keyPressEvent(QKeyEvent *keyEvent) = 0;
+ virtual void keyReleaseEvent(QKeyEvent *keyEvent) = 0;
+
+ TimelineGraphicsScene *scene() const;
+
+ TimelineToolDelegate *delegate() const;
+
+ QPointF startPosition() const;
+
+ TimelineMovableAbstractItem *currentItem() const;
+
+private:
+ TimelineGraphicsScene *m_scene;
+
+ TimelineToolDelegate *m_delegate;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineactions.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineactions.cpp
new file mode 100644
index 0000000000..4751765874
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineactions.cpp
@@ -0,0 +1,322 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineactions.h"
+
+#include "timelineutils.h"
+#include "timelineview.h"
+
+#include <bindingproperty.h>
+#include <designdocument.h>
+#include <designdocumentview.h>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+#include <variantproperty.h>
+#include <qmldesignerplugin.h>
+#include <qmlobjectnode.h>
+#include <qmltimelinekeyframegroup.h>
+
+namespace QmlDesigner {
+
+TimelineActions::TimelineActions() = default;
+
+void TimelineActions::deleteAllKeyframesForTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline)
+{
+ targetNode.view()->executeInTransaction("TimelineActions::deleteAllKeyframesForTarget", [=](){
+ if (timeline.isValid()) {
+ for (auto frames : timeline.keyframeGroupsForTarget(targetNode))
+ frames.destroy();
+ }
+ });
+}
+
+void TimelineActions::insertAllKeyframesForTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline)
+{
+ targetNode.view()->executeInTransaction("TimelineActions::insertAllKeyframesForTarget", [=](){
+ auto object = QmlObjectNode(targetNode);
+ if (timeline.isValid() && object.isValid()) {
+ for (auto frames : timeline.keyframeGroupsForTarget(targetNode)) {
+ QVariant value = object.instanceValue(frames.propertyName());
+ frames.setValue(value, timeline.currentKeyframe());
+ }
+ }
+
+ });
+}
+
+void TimelineActions::copyAllKeyframesForTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline)
+{
+ DesignDocumentView::copyModelNodes(Utils::transform(timeline.keyframeGroupsForTarget(targetNode),
+ &QmlTimelineKeyframeGroup::modelNode));
+}
+
+void TimelineActions::pasteKeyframesToTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline)
+{
+ if (timeline.isValid()) {
+ QScopedPointer<Model> pasteModel(DesignDocumentView::pasteToModel());
+
+ if (!pasteModel)
+ return;
+
+ DesignDocumentView view;
+ pasteModel->attachView(&view);
+
+ if (!view.rootModelNode().isValid())
+ return;
+
+ ModelNode rootNode = view.rootModelNode();
+
+ //Sanity check
+ if (!QmlTimelineKeyframeGroup::checkKeyframesType(rootNode)) {
+ for (const ModelNode &node : rootNode.directSubModelNodes())
+ if (!QmlTimelineKeyframeGroup::checkKeyframesType(node))
+ return;
+ }
+
+ pasteModel->detachView(&view);
+
+ view.executeInTransaction("TimelineActions::pasteKeyframesToTarget", [=, &view](){
+
+
+ targetNode.view()->model()->attachView(&view);
+
+ ModelNode nonConstTargetNode = targetNode;
+ nonConstTargetNode.validId();
+
+ if (QmlTimelineKeyframeGroup::checkKeyframesType(rootNode)) {
+ /* Single selection */
+
+ ModelNode newNode = view.insertModel(rootNode);
+ QmlTimelineKeyframeGroup frames(newNode);
+ frames.setTarget(targetNode);
+
+ timeline.modelNode().defaultNodeListProperty().reparentHere(newNode);
+
+ } else {
+ /* Multi selection */
+ for (const ModelNode &node : rootNode.directSubModelNodes()) {
+ ModelNode newNode = view.insertModel(node);
+ QmlTimelineKeyframeGroup frames(newNode);
+ frames.setTarget(targetNode);
+ timeline.modelNode().defaultNodeListProperty().reparentHere(newNode);
+ }
+ }
+ });
+ }
+}
+
+void TimelineActions::copyKeyframes(const QList<ModelNode> &keyframes)
+{
+ QList<ModelNode> nodes;
+ for (const auto &node : keyframes) {
+ NodeAbstractProperty pp = node.parentProperty();
+ QTC_ASSERT(pp.isValid(), return );
+
+ ModelNode parentModelNode = pp.parentModelNode();
+ for (const auto &property : parentModelNode.properties()) {
+ auto name = property.name();
+ if (property.isBindingProperty()) {
+ BindingProperty bp = property.toBindingProperty();
+ ModelNode bpNode = bp.resolveToModelNode();
+ if (bpNode.isValid())
+ node.setAuxiliaryData(name, bpNode.id());
+ } else if (property.isVariantProperty()) {
+ VariantProperty vp = property.toVariantProperty();
+ node.setAuxiliaryData(name, vp.value());
+ }
+ }
+
+ nodes << node;
+ }
+
+ DesignDocumentView::copyModelNodes(nodes);
+}
+
+bool isKeyframe(const ModelNode &node)
+{
+ return node.isValid() && node.metaInfo().isValid()
+ && node.metaInfo().isSubclassOf("QtQuick.Timeline.Keyframe");
+}
+
+QVariant getValue(const ModelNode &node)
+{
+ if (node.isValid())
+ return node.variantProperty("value").value();
+
+ return QVariant();
+}
+
+qreal getTime(const ModelNode &node)
+{
+ Q_ASSERT(node.isValid());
+ Q_ASSERT(node.hasProperty("frame"));
+
+ return node.variantProperty("frame").value().toReal();
+}
+
+QmlTimelineKeyframeGroup getFrameGroup(const ModelNode &node,
+ AbstractView *timelineView,
+ const QmlTimeline &timeline)
+{
+ QVariant targetId = node.auxiliaryData("target");
+ QVariant property = node.auxiliaryData("property");
+
+ if (targetId.isValid() && property.isValid()) {
+ ModelNode targetNode = timelineView->modelNodeForId(targetId.toString());
+ if (targetNode.isValid()) {
+ for (QmlTimelineKeyframeGroup frameGrp : timeline.keyframeGroupsForTarget(targetNode)) {
+ if (frameGrp.propertyName() == property.toByteArray())
+ return frameGrp;
+ }
+ }
+ }
+ return QmlTimelineKeyframeGroup();
+}
+
+void pasteKeyframe(const qreal expectedTime,
+ const ModelNode &keyframe,
+ AbstractView *timelineView,
+ const QmlTimeline &timeline)
+{
+ QmlTimelineKeyframeGroup group = getFrameGroup(keyframe, timelineView, timeline);
+ if (group.isValid()) {
+ const qreal clampedTime = TimelineUtils::clamp(expectedTime,
+ timeline.startKeyframe(),
+ timeline.endKeyframe());
+
+ // Create a new frame ...
+ group.setValue(getValue(keyframe), clampedTime);
+
+ // ... look it up by time ...
+ for (const ModelNode &key : group.keyframePositions()) {
+ qreal time = key.variantProperty("frame").value().toReal();
+ if (qFuzzyCompare(clampedTime, time)) {
+ // ... and transfer the properties.
+ for (const auto &property : keyframe.properties()) {
+ if (property.name() == "frame" || property.name() == "value")
+ continue;
+
+ if (property.isVariantProperty()) {
+ auto vp = property.toVariantProperty();
+ key.variantProperty(vp.name()).setValue(vp.value());
+ } else if (property.isBindingProperty()) {
+ auto bp = property.toBindingProperty();
+ key.bindingProperty(bp.name()).setExpression(bp.expression());
+ }
+ }
+ }
+ }
+ }
+}
+
+std::vector<std::tuple<ModelNode, qreal>> getFramesRelative(const ModelNode &parent)
+{
+ auto byTime = [](const ModelNode &lhs, const ModelNode &rhs) {
+ return getTime(lhs) < getTime(rhs);
+ };
+
+ std::vector<std::tuple<ModelNode, qreal>> result;
+
+ QList<ModelNode> sortedByTime;
+ QList<ModelNode> subs(parent.directSubModelNodes());
+
+ std::copy_if(subs.begin(), subs.end(), std::back_inserter(sortedByTime), &isKeyframe);
+ std::sort(sortedByTime.begin(), sortedByTime.end(), byTime);
+
+ if (!sortedByTime.empty()) {
+ qreal firstTime = getTime(sortedByTime.first());
+ for (ModelNode keyframe : sortedByTime)
+ result.emplace_back(keyframe, getTime(keyframe) - firstTime);
+ }
+
+ return result;
+}
+
+void TimelineActions::pasteKeyframes(AbstractView *timelineView, const QmlTimeline &timeline)
+{
+ QScopedPointer<Model> pasteModel(DesignDocumentView::pasteToModel());
+
+ if (!pasteModel)
+ return;
+
+ DesignDocumentView view;
+ pasteModel->attachView(&view);
+
+ if (!view.rootModelNode().isValid())
+ return;
+
+ const qreal currentTime = timeline.currentKeyframe();
+
+ ModelNode rootNode = view.rootModelNode();
+
+ timelineView->executeInTransaction("TimelineActions::pasteKeyframes", [=](){
+ if (isKeyframe(rootNode))
+ pasteKeyframe(currentTime, rootNode, timelineView, timeline);
+ else
+ for (auto frame : getFramesRelative(rootNode))
+ pasteKeyframe(currentTime + std::get<1>(frame),
+ std::get<0>(frame),
+ timelineView,
+ timeline);
+
+ });
+}
+
+bool TimelineActions::clipboardContainsKeyframes()
+{
+ QScopedPointer<Model> pasteModel(DesignDocumentView::pasteToModel());
+
+ if (!pasteModel)
+ return false;
+
+ DesignDocumentView view;
+ pasteModel->attachView(&view);
+
+ if (!view.rootModelNode().isValid())
+ return false;
+
+ ModelNode rootNode = view.rootModelNode();
+
+ if (!rootNode.hasAnySubModelNodes())
+ return false;
+
+ //Sanity check
+ if (!QmlTimelineKeyframeGroup::checkKeyframesType(rootNode)) {
+ for (const ModelNode &node : rootNode.directSubModelNodes())
+ if (!QmlTimelineKeyframeGroup::checkKeyframesType(node))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineactions.h b/src/plugins/qmldesigner/components/timelineeditor/timelineactions.h
new file mode 100644
index 0000000000..bae09b110f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineactions.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <modelnode.h>
+#include <qmltimeline.h>
+
+namespace QmlDesigner {
+
+class TimelineActions
+{
+public:
+ static void deleteAllKeyframesForTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline);
+ static void insertAllKeyframesForTarget(const ModelNode &targetNode,
+ const QmlTimeline &timeline);
+ static void copyAllKeyframesForTarget(const ModelNode &targetNode, const QmlTimeline &timeline);
+ static void pasteKeyframesToTarget(const ModelNode &targetNode, const QmlTimeline &timeline);
+
+ static void copyKeyframes(const QList<ModelNode> &keyframes);
+ static void pasteKeyframes(AbstractView *timelineView, const QmlTimeline &TimelineActions);
+
+ static bool clipboardContainsKeyframes();
+
+private:
+ TimelineActions();
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp
new file mode 100644
index 0000000000..032a133f89
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp
@@ -0,0 +1,262 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineanimationform.h"
+#include "ui_timelineanimationform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <signalhandlerproperty.h>
+#include <variantproperty.h>
+#include <qmlitemnode.h>
+#include <qmlobjectnode.h>
+
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+TimelineAnimationForm::TimelineAnimationForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::TimelineAnimationForm)
+{
+ ui->setupUi(this);
+
+ connectSpinBox(ui->duration, "duration");
+ connectSpinBox(ui->loops, "loops");
+
+ connectSpinBox(ui->startFrame, "from");
+ connectSpinBox(ui->endFrame, "to");
+
+ connect(ui->loops, QOverload<int>::of(&QSpinBox::valueChanged), [this]() {
+ ui->continuous->setChecked(ui->loops->value() == -1);
+ });
+
+ connect(ui->continuous, &QCheckBox::toggled, [this](bool checked) {
+ if (checked) {
+ setProperty("loops", -1);
+ ui->loops->setValue(-1);
+ } else {
+ setProperty("loops", 1);
+ ui->loops->setValue(1);
+ }
+ });
+
+ connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() {
+ QTC_ASSERT(m_timeline.isValid(), return );
+
+ static QString lastString;
+
+ const QString newId = ui->idLineEdit->text();
+
+ if (lastString == newId)
+ return;
+
+ lastString = newId;
+
+ if (newId == animation().id())
+ return;
+
+ bool error = false;
+
+ if (!ModelNode::isValidId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 is an invalid id.").arg(newId));
+ error = true;
+ } else if (animation().view()->hasId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 already exists.").arg(newId));
+ } else {
+ animation().setIdWithRefactoring(newId);
+ error = true;
+ }
+
+ if (error) {
+ lastString.clear();
+ ui->idLineEdit->setText(animation().id());
+ }
+ });
+
+ connect(ui->running, &QCheckBox::clicked, [this](bool checked) {
+ if (checked) {
+ setProperty("running", true);
+ } else {
+ setProperty("running", false);
+ }
+ });
+
+ connect(ui->pingPong, &QCheckBox::clicked, [this](bool checked) {
+ if (checked) {
+ setProperty("pingPong", true);
+ } else {
+ setProperty("pingPong", false);
+ }
+ });
+
+ connect(ui->transitionToState,
+ QOverload<int>::of(&QComboBox::activated),
+ [this](int index) {
+ if (!m_animation.isValid())
+ return;
+ if (!m_animation.view()->rootModelNode().hasId())
+ return;
+
+ ModelNode rootNode = m_animation.view()->rootModelNode();
+
+ if (index == 0) {
+ if (m_animation.signalHandlerProperty("onFinished").isValid())
+ m_animation.removeProperty("onFinished");
+ } else if (index == 1) {
+ m_animation.signalHandlerProperty("onFinished")
+ .setSource(rootNode.id() + ".state = \"" + "\"");
+ } else {
+ m_animation.signalHandlerProperty("onFinished")
+ .setSource(rootNode.id() + ".state = \""
+ + ui->transitionToState->currentText() + "\"");
+ }
+ });
+}
+
+TimelineAnimationForm::~TimelineAnimationForm()
+{
+ delete ui;
+}
+
+void TimelineAnimationForm::setup(const ModelNode &animation)
+{
+ m_timeline = QmlTimeline(animation.parentProperty().parentModelNode());
+ setAnimation(animation);
+ setupAnimation();
+}
+
+ModelNode TimelineAnimationForm::animation() const
+{
+ return m_animation;
+}
+
+void TimelineAnimationForm::setAnimation(const ModelNode &animation)
+{
+ m_animation = animation;
+}
+
+void TimelineAnimationForm::setupAnimation()
+{
+ if (!m_animation.isValid())
+ setEnabled(false);
+
+ if (m_animation.isValid()) {
+ setEnabled(true);
+
+ ui->idLineEdit->setText(m_animation.id());
+
+ if (m_animation.hasVariantProperty("duration"))
+ ui->duration->setValue(m_animation.variantProperty("duration").value().toInt());
+ else
+ ui->duration->setValue(0);
+
+ ui->startFrame->setValue(m_animation.variantProperty("from").value().toInt());
+ ui->endFrame->setValue(m_animation.variantProperty("to").value().toInt());
+
+ if (m_animation.hasVariantProperty("loops"))
+ ui->loops->setValue(m_animation.variantProperty("loops").value().toInt());
+ else
+ ui->loops->setValue(0);
+
+ if (m_animation.hasVariantProperty("running"))
+ ui->running->setChecked(m_animation.variantProperty("running").value().toBool());
+ else
+ ui->running->setChecked(false);
+
+ if (m_animation.hasVariantProperty("pingPong"))
+ ui->pingPong->setChecked(m_animation.variantProperty("pingPong").value().toBool());
+ else
+ ui->pingPong->setChecked(false);
+
+ ui->continuous->setChecked(ui->loops->value() == -1);
+ }
+
+ populateStateComboBox();
+
+ ui->duration->setEnabled(m_animation.isValid());
+ ui->running->setEnabled(m_animation.isValid());
+ ui->continuous->setEnabled(m_animation.isValid());
+ ui->loops->setEnabled(m_animation.isValid());
+}
+
+void TimelineAnimationForm::setProperty(const PropertyName &propertyName, const QVariant &value)
+{
+ QTC_ASSERT(m_animation.isValid(), return );
+
+ try {
+ m_animation.variantProperty(propertyName).setValue(value);
+ } catch (const Exception &e) {
+ e.showException();
+ }
+}
+
+void TimelineAnimationForm::connectSpinBox(QSpinBox *spinBox, const PropertyName &propertyName)
+{
+ connect(spinBox, &QSpinBox::editingFinished, [this, propertyName, spinBox]() {
+ setProperty(propertyName, spinBox->value());
+ });
+}
+
+void TimelineAnimationForm::populateStateComboBox()
+{
+ ui->transitionToState->clear();
+ ui->transitionToState->addItem(tr("none"));
+ ui->transitionToState->addItem(tr("Base State"));
+ if (!m_animation.isValid())
+ return;
+ QmlObjectNode rootNode = QmlObjectNode(m_animation.view()->rootModelNode());
+ if (rootNode.isValid() && rootNode.modelNode().hasId()) {
+ for (const QmlModelState &state : QmlItemNode(rootNode).states().allStates()) {
+ ui->transitionToState
+ ->addItem(state.modelNode().variantProperty("name").value().toString(),
+ QVariant::fromValue<ModelNode>(state.modelNode()));
+ }
+ if (m_animation.signalHandlerProperty("onFinished").isValid()) {
+ const QString source = m_animation.signalHandlerProperty("onFinished").source();
+ const QStringList list = source.split("=");
+ if (list.count() == 2) {
+ QString name = list.last().trimmed();
+ name.chop(1);
+ name.remove(0, 1);
+ if (name.isEmpty())
+ ui->transitionToState->setCurrentIndex(1);
+ else
+ ui->transitionToState->setCurrentText(name);
+ }
+ }
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.h b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.h
new file mode 100644
index 0000000000..1b5eaf6cab
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QWidget>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+namespace Ui {
+class TimelineAnimationForm;
+}
+
+class TimelineAnimationForm : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineAnimationForm(QWidget *parent);
+ ~TimelineAnimationForm() override;
+ void setup(const ModelNode &animation);
+ ModelNode animation() const;
+
+private:
+ void setupAnimation();
+
+ void setAnimation(const ModelNode &animation);
+ void setProperty(const PropertyName &propertyName, const QVariant &value);
+ void connectSpinBox(QSpinBox *spinBox, const PropertyName &propertyName);
+ void populateStateComboBox();
+
+ Ui::TimelineAnimationForm *ui;
+
+ QmlTimeline m_timeline;
+ ModelNode m_animation;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui
new file mode 100644
index 0000000000..5d13cfa726
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TimelineAnimationForm</class>
+ <widget class="QWidget" name="QmlDesigner::TimelineAnimationForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>641</width>
+ <height>176</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="4" column="3">
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>Loops:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="4" colspan="2">
+ <widget class="QSpinBox" name="loops">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-1</number>
+ </property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Continuous</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QCheckBox" name="continuous">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QComboBox" name="transitionToState">
+ <item>
+ <property name="text">
+ <string>none</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Animation Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="9">
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>213</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="minimumSize">
+ <size>
+ <width>140</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Animation ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="9">
+ <spacer name="horizontalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>213</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="8">
+ <widget class="QSpinBox" name="duration">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-10000</number>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="label_23">
+ <property name="text">
+ <string>Finished:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="7">
+ <widget class="QLabel" name="label_17">
+ <property name="text">
+ <string>Ping pong</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="8">
+ <widget class="QCheckBox" name="pingPong">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="9">
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>213</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_20">
+ <property name="minimumSize">
+ <size>
+ <width>140</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Transition to state:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="idLineEdit">
+ <property name="text">
+ <string>animation02</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3" colspan="2">
+ <widget class="QLabel" name="label_18">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Running in base state</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="startFrame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-10000</number>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Start frame:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5">
+ <widget class="QCheckBox" name="running">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="9">
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>213</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="7">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Duration:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>End frame:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4" colspan="3">
+ <widget class="QSpinBox" name="endFrame">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-10000</number>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h
new file mode 100644
index 0000000000..6c012beec1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QGraphicsItem>
+
+namespace QmlDesigner {
+namespace TimelineConstants {
+const int sectionHeight = 18;
+const int rulerHeight = sectionHeight + 4;
+const int sectionWidth = 200;
+const int moveableAbstractItemUserType = QGraphicsItem::UserType + 1;
+const int timelineSectionItemUserType = QGraphicsItem::UserType + 2;
+const int timelinePropertyItemUserType = QGraphicsItem::UserType + 3;
+const int textIndentationProperties = 54;
+const int textIndentationSections = 24;
+const int toolButtonSize = 11;
+const int timelineBounds = 8;
+const int timelineLeftOffset = 10;
+
+const char timelineCategory[] = "Timeline";
+const int priorityTimelineCategory = 110;
+const char timelineCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Timeline");
+
+const char timelineCopyKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
+ "Copy All Keyframes");
+const char timelinePasteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
+ "Paste Keyframes");
+const char timelineInsertKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
+ "Add Keyframes at Current Frame");
+const char timelineDeleteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
+ "Delete All Keyframes");
+
+const char timelineStatusBarFrameNumber[] = QT_TRANSLATE_NOOP("QmlDesignerTimeline", "Frame %1");
+
+const char C_QMLTIMELINE[] = "QmlDesigner::Timeline";
+const char C_SETTINGS[] = "QmlDesigner.Settings";
+const char C_ADD_TIMELINE[] = "QmlDesigner.AddTimeline";
+const char C_TO_START[] = "QmlDesigner.ToStart";
+const char C_TO_END[] = "QmlDesigner.ToEnd";
+const char C_PREVIOUS[] = "QmlDesigner.Previous";
+const char C_PLAY[] = "QmlDesigner.Play";
+const char C_NEXT[] = "QmlDesigner.Next";
+const char C_AUTO_KEYFRAME[] = "QmlDesigner.AutoKeyframe";
+const char C_CURVE_PICKER[] = "QmlDesigner.CurvePicker";
+const char C_ZOOM_IN[] = "QmlDesigner.ZoomIn";
+const char C_ZOOM_OUT[] = "QmlDesigner.ZoomOut";
+
+const char C_BAR_ITEM_OVERRIDE[] = "Timeline.OverrideColor";
+
+const int keyFrameSize = 17;
+const int keyFrameMargin = 2;
+} // namespace TimelineConstants
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.cpp
new file mode 100644
index 0000000000..b3e2e12a3f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.cpp
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinecontext.h"
+#include "timelineconstants.h"
+#include "timelinewidget.h"
+
+namespace QmlDesigner {
+
+TimelineContext::TimelineContext(QWidget *widget)
+ : IContext(widget)
+{
+ setWidget(widget);
+ setContext(Core::Context(TimelineConstants::C_QMLTIMELINE));
+}
+
+void TimelineContext::contextHelp(const Core::IContext::HelpCallback &callback) const
+{
+ if (auto *widget = qobject_cast<TimelineWidget *>(m_widget))
+ widget->contextHelp(callback);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.h b/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.h
new file mode 100644
index 0000000000..a8a6ca3b8f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinecontext.h
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <coreplugin/icontext.h>
+
+#include <QWidget>
+
+namespace QmlDesigner {
+
+class TimelineContext : public Core::IContext
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineContext(QWidget *widget);
+ void contextHelp(const HelpCallback &callback) const override;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.cpp
new file mode 100644
index 0000000000..682a14f1c7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.cpp
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinecontrols.h"
+#include "timelinepropertyitem.h"
+
+#include <coreplugin/icore.h>
+
+#include <QColorDialog>
+#include <QMouseEvent>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QToolTip>
+
+#include <theme.h>
+
+#include <limits>
+
+namespace QmlDesigner {
+
+TimelineControl *createTimelineControl(const TypeName &name)
+{
+ if (name == "real" || name == "double" || name == "float")
+ return new FloatControl;
+ if (name == "QColor" || name == "color")
+ return new ColorControl;
+
+ return nullptr;
+}
+
+FloatControl::FloatControl()
+ : QDoubleSpinBox(nullptr)
+{
+ setValue(0.0);
+ setButtonSymbols(QAbstractSpinBox::NoButtons);
+ setFrame(false);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
+ setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
+#endif
+
+ setMinimum(std::numeric_limits<float>::lowest());
+ setMaximum(std::numeric_limits<float>::max());
+
+ QColor bg = Theme::instance()->qmlDesignerBackgroundColorDarkAlternate();
+
+ auto p = palette();
+ p.setColor(QPalette::Base, bg.darker(110));
+ setPalette(p);
+
+ m_timer.setInterval(100);
+ m_timer.setSingleShot(true);
+
+ auto startTimer = [this]( ) { m_timer.start(); };
+ auto deferredSlot = [this]( ) { emit controlValueChanged(QVariant(this->value())); };
+
+ QObject::connect(this, &QDoubleSpinBox::editingFinished, &m_timer, startTimer);
+ QObject::connect(&m_timer, &QTimer::timeout, deferredSlot);
+}
+
+FloatControl::~FloatControl() = default;
+
+QWidget *FloatControl::widget()
+{
+ return this;
+}
+
+void FloatControl::connect(TimelinePropertyItem *item)
+{
+ QObject::connect(this,
+ &FloatControl::controlValueChanged,
+ item,
+ &TimelinePropertyItem::changePropertyValue);
+}
+
+QVariant FloatControl::controlValue() const
+{
+ return QVariant(value());
+}
+
+void FloatControl::setControlValue(const QVariant &value)
+{
+ if (value.userType() != QMetaType::Float && value.userType() != QMetaType::Double)
+ return;
+
+ QSignalBlocker blocker(this);
+ setValue(value.toDouble());
+}
+
+void FloatControl::setSize(int width, int height)
+{
+ setFixedWidth(width);
+ setFixedHeight(height);
+}
+
+ColorControl::ColorControl()
+ : QWidget(nullptr)
+ , m_color(Qt::black)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFixedHeight(20);
+}
+
+ColorControl::ColorControl(const QColor &color, QWidget *parent)
+ : QWidget(parent)
+ , m_color(color)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFixedHeight(20);
+}
+
+ColorControl::~ColorControl() = default;
+
+QWidget *ColorControl::widget()
+{
+ return this;
+}
+
+void ColorControl::connect(TimelinePropertyItem *item)
+{
+ QObject::connect(this,
+ &ColorControl::controlValueChanged,
+ item,
+ &TimelinePropertyItem::changePropertyValue);
+}
+
+QVariant ColorControl::controlValue() const
+{
+ return QVariant(value());
+}
+
+void ColorControl::setControlValue(const QVariant &value)
+{
+ if (value.userType() != QMetaType::QColor)
+ return;
+
+ m_color = qvariant_cast<QColor>(value);
+}
+
+void ColorControl::setSize(int width, int height)
+{
+ setFixedWidth(width);
+ setFixedHeight(height);
+}
+
+QColor ColorControl::value() const
+{
+ return m_color;
+}
+
+bool ColorControl::event(QEvent *event)
+{
+ if (event->type() == QEvent::ToolTip) {
+ if (auto helpEvent = static_cast<const QHelpEvent *>(event)) {
+ QToolTip::showText(helpEvent->globalPos(), m_color.name());
+ return true;
+ }
+ }
+ return QWidget::event(event);
+}
+
+void ColorControl::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ painter.fillRect(event->rect(), m_color);
+}
+
+void ColorControl::mouseReleaseEvent(QMouseEvent *event)
+{
+ QColor color = QColorDialog::getColor(m_color, Core::ICore::dialogParent());
+
+ event->accept();
+
+ if (color != m_color) {
+ m_color = color;
+ update();
+ emit valueChanged();
+ emit controlValueChanged(QVariant(m_color));
+ }
+}
+
+void ColorControl::mousePressEvent(QMouseEvent *event)
+{
+ // Needed to make the mouseRelease Event work if this
+ // widget is embedded inside a QGraphicsProxyWidget.
+ QWidget::mousePressEvent(event);
+ event->accept();
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.h b/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.h
new file mode 100644
index 0000000000..4c30357be9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinecontrols.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <nodemetainfo.h>
+
+#include <QDoubleSpinBox>
+#include <QTimer>
+#include <QWidget>
+
+namespace QmlDesigner {
+
+class TimelinePropertyItem;
+
+class TimelineControl
+{
+public:
+ virtual ~TimelineControl() = default;
+
+ virtual QWidget *widget() = 0;
+
+ virtual void connect(TimelinePropertyItem *scene) = 0;
+
+ virtual QVariant controlValue() const = 0;
+
+ virtual void setControlValue(const QVariant &value) = 0;
+
+ virtual void setSize(int width, int height) = 0;
+};
+
+TimelineControl *createTimelineControl(const TypeName &name);
+
+class FloatControl : public QDoubleSpinBox, public TimelineControl
+{
+ Q_OBJECT
+
+public:
+ FloatControl();
+
+ ~FloatControl() override;
+
+ QWidget *widget() override;
+
+ void connect(TimelinePropertyItem *scene) override;
+
+ QVariant controlValue() const override;
+
+ void setControlValue(const QVariant &value) override;
+
+ void setSize(int width, int height) override;
+
+signals:
+ void controlValueChanged(const QVariant &value);
+
+private:
+ QTimer m_timer;
+};
+
+class ColorControl : public QWidget, public TimelineControl
+{
+ Q_OBJECT
+
+public:
+ ColorControl();
+
+ ColorControl(const QColor &color, QWidget *parent = nullptr);
+
+ ~ColorControl() override;
+
+ QWidget *widget() override;
+
+ void connect(TimelinePropertyItem *item) override;
+
+ QVariant controlValue() const override;
+
+ void setControlValue(const QVariant &value) override;
+
+ void setSize(int width, int height) override;
+
+ QColor value() const;
+
+protected:
+ bool event(QEvent *event) override;
+
+ void paintEvent(QPaintEvent *event) override;
+
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+ void mousePressEvent(QMouseEvent *event) override;
+
+signals:
+ void valueChanged();
+
+ void controlValueChanged(const QVariant &value);
+
+private:
+ QColor m_color;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri b/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri
new file mode 100644
index 0000000000..8001748fb0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri
@@ -0,0 +1,81 @@
+QT *= qml quick core
+
+VPATH += $$PWD
+
+INCLUDEPATH += $$PWD
+
+DEFINES += TIMELINE_QML_PATH=\\\"$$PWD/qml/\\\"
+
+SOURCES += \
+ timelineview.cpp \
+ timelinewidget.cpp \
+ timelinegraphicsscene.cpp \
+ timelinegraphicslayout.cpp \
+ timelinepropertyitem.cpp \
+ timelinesectionitem.cpp \
+ timelineitem.cpp \
+ timelinemovableabstractitem.cpp \
+ timelineabstracttool.cpp \
+ timelinemovetool.cpp \
+ timelineselectiontool.cpp \
+ timelineplaceholder.cpp \
+ setframevaluedialog.cpp \
+ timelinetoolbar.cpp \
+ easingcurvedialog.cpp \
+ timelinetoolbutton.cpp \
+ timelinesettingsdialog.cpp \
+ timelineactions.cpp \
+ timelinecontext.cpp \
+ timelineutils.cpp \
+ timelineanimationform.cpp \
+ timelineform.cpp \
+ splineeditor.cpp \
+ preseteditor.cpp \
+ canvas.cpp \
+ canvasstyledialog.cpp \
+ easingcurve.cpp \
+ timelinesettingsmodel.cpp \
+ timelinetooldelegate.cpp \
+ timelinecontrols.cpp
+
+HEADERS += \
+ timelineview.h \
+ timelinewidget.h \
+ timelinegraphicsscene.h \
+ timelinegraphicslayout.h \
+ timelinepropertyitem.h \
+ timelinesectionitem.h \
+ timelineitem.h \
+ timelineconstants.h \
+ timelinemovableabstractitem.h \
+ timelineabstracttool.h \
+ timelinemovetool.h \
+ timelineselectiontool.h \
+ timelineplaceholder.h \
+ timelineicons.h \
+ timelinetoolbar.h \
+ setframevaluedialog.h \
+ easingcurvedialog.h \
+ timelinetoolbutton.h \
+ timelinesettingsdialog.h \
+ timelineactions.h \
+ timelinecontext.h \
+ timelineutils.h \
+ timelineanimationform.h \
+ timelineform.h \
+ splineeditor.h \
+ preseteditor.h \
+ canvas.h \
+ canvasstyledialog.h \
+ easingcurve.h \
+ timelinesettingsmodel.h \
+ timelinecontrols.h
+
+RESOURCES += \
+ timeline.qrc
+
+FORMS += \
+ setframevaluedialog.ui \
+ timelinesettingsdialog.ui \
+ timelineanimationform.ui \
+ timelineform.ui
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp
new file mode 100644
index 0000000000..eb63ad4883
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp
@@ -0,0 +1,186 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineform.h"
+#include "ui_timelineform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+TimelineForm::TimelineForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::TimelineForm)
+{
+ ui->setupUi(this);
+
+ ui->duration->setVisible(false);
+
+ connect(ui->expressionBindingLineEdit, &QLineEdit::editingFinished, [this]() {
+ QTC_ASSERT(m_timeline.isValid(), return );
+
+
+ static QString lastString;
+
+ const QString bindingText = ui->expressionBindingLineEdit->text();
+
+ if (bindingText == lastString)
+ return;
+
+ lastString = bindingText;
+
+ if (bindingText.isEmpty()) {
+ ui->animation->setChecked(true);
+ try {
+ m_timeline.modelNode().removeProperty("currentFrame");
+ } catch (const Exception &e) {
+ e.showException();
+ }
+ return;
+ }
+
+ ui->expressionBinding->setChecked(true);
+
+ try {
+ m_timeline.modelNode()
+ .bindingProperty("currentFrame")
+ .setExpression(bindingText);
+ } catch (const Exception &e) {
+ e.showException();
+ }
+ });
+
+ connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() {
+ QTC_ASSERT(m_timeline.isValid(), return );
+
+ static QString lastString;
+
+ const QString newId = ui->idLineEdit->text();
+
+ if (newId == lastString)
+ return;
+
+ lastString = newId;
+
+ if (newId == m_timeline.modelNode().id())
+ return;
+
+ bool error = false;
+
+ if (!ModelNode::isValidId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 is an invalid id.").arg(newId));
+ error = true;
+ } else if (m_timeline.view()->hasId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 already exists.").arg(newId));
+ error = true;
+ } else {
+ m_timeline.modelNode().setIdWithRefactoring(newId);
+ }
+
+ if (error) {
+ lastString.clear();
+ ui->idLineEdit->setText(m_timeline.modelNode().id());
+ }
+ });
+
+ connectSpinBox(ui->startFrame, "startFrame");
+ connectSpinBox(ui->endFrame, "endFrame");
+}
+
+TimelineForm::~TimelineForm()
+{
+ delete ui;
+}
+
+void TimelineForm::setTimeline(const QmlTimeline &timeline)
+{
+ m_timeline = timeline;
+
+ ui->expressionBindingLineEdit->clear();
+
+ if (m_timeline.isValid()) {
+ ui->idLineEdit->setText(m_timeline.modelNode().displayName());
+ ui->duration->setValue(qRound(m_timeline.duration()));
+ ui->startFrame->setValue(
+ m_timeline.modelNode().variantProperty("startFrame").value().toInt());
+ ui->endFrame->setValue(m_timeline.modelNode().variantProperty("endFrame").value().toInt());
+
+ ui->duration->setValue(qRound(m_timeline.duration()));
+
+ if (m_timeline.modelNode().hasBindingProperty("currentFrame")) {
+ ui->expressionBindingLineEdit->setText(
+ m_timeline.modelNode().bindingProperty("currentFrame").expression());
+ ui->expressionBinding->setChecked(true);
+ } else {
+ ui->expressionBinding->setChecked(false);
+ }
+ }
+}
+
+QmlTimeline TimelineForm::timeline() const
+{
+ return m_timeline;
+}
+
+void TimelineForm::setHasAnimation(bool b)
+{
+ ui->expressionBinding->setChecked(!b);
+ ui->animation->setChecked(b);
+ ui->expressionBindingLineEdit->setDisabled(b);
+}
+
+void TimelineForm::setProperty(const PropertyName &propertyName, const QVariant &value)
+{
+ QTC_ASSERT(m_timeline.isValid(), return );
+
+ try {
+ m_timeline.modelNode().variantProperty(propertyName).setValue(value);
+ } catch (const Exception &e) {
+ e.showException();
+ }
+ ui->duration->setValue(qRound(m_timeline.duration()));
+}
+
+void TimelineForm::connectSpinBox(QSpinBox *spinBox, const PropertyName &propertyName)
+{
+ connect(spinBox, &QSpinBox::editingFinished, [this, propertyName, spinBox]() {
+ setProperty(propertyName, spinBox->value());
+ });
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.h b/src/plugins/qmldesigner/components/timelineeditor/timelineform.h
new file mode 100644
index 0000000000..7745de3a0f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QWidget>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+namespace Ui {
+class TimelineForm;
+}
+
+class TimelineForm : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineForm(QWidget *parent);
+ ~TimelineForm() override;
+ void setTimeline(const QmlTimeline &timeline);
+ QmlTimeline timeline() const;
+ void setHasAnimation(bool b);
+
+private:
+ void setProperty(const PropertyName &propertyName, const QVariant &value);
+ void connectSpinBox(QSpinBox *spinBox, const PropertyName &propertyName);
+
+ Ui::TimelineForm *ui;
+ QmlTimeline m_timeline;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui b/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui
new file mode 100644
index 0000000000..b8b47e4c70
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TimelineForm</class>
+ <widget class="QWidget" name="QmlDesigner::TimelineForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>627</width>
+ <height>170</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="8" colspan="2">
+ <spacer name="horizontalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="5">
+ <widget class="QSpinBox" name="endFrame">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-10000</number>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" colspan="2">
+ <widget class="QSpinBox" name="startFrame">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>-10000</number>
+ </property>
+ <property name="maximum">
+ <number>10000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="6">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Duration</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Expression binding:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="8" colspan="2">
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="4">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>End frame:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3" colspan="2">
+ <widget class="QRadioButton" name="animation">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Animation</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="8" colspan="2">
+ <spacer name="horizontalSpacer_12">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="1" colspan="5">
+ <widget class="QLineEdit" name="expressionBindingLineEdit">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>240</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="8" colspan="2">
+ <spacer name="horizontalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="7">
+ <widget class="QSpinBox" name="duration">
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>80</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="maximum">
+ <number>20000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QRadioButton" name="expressionBinding">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Expression binding</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="5">
+ <widget class="QLineEdit" name="idLineEdit">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Timeline ID:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Timeline Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Start frame:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp
new file mode 100644
index 0000000000..af0f26d249
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp
@@ -0,0 +1,162 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinegraphicslayout.h"
+
+#include "timelinegraphicsscene.h"
+#include "timelineplaceholder.h"
+#include "timelinesectionitem.h"
+#include "timelineview.h"
+
+#include <QGraphicsLinearLayout>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+TimelineGraphicsLayout::TimelineGraphicsLayout(TimelineGraphicsScene *scene, TimelineItem *parent)
+ : TimelineItem(parent)
+ , m_layout(new QGraphicsLinearLayout)
+ , m_rulerItem(TimelineRulerSectionItem::create(scene, this))
+ , m_placeholder1(TimelinePlaceholder::create(scene, this))
+ , m_placeholder2(TimelinePlaceholder::create(scene, this))
+{
+ m_layout->setOrientation(Qt::Vertical);
+ m_layout->setSpacing(0);
+ m_layout->setContentsMargins(0, 0, 0, 0);
+
+ m_layout->addItem(m_rulerItem);
+ m_layout->addItem(m_placeholder1);
+ m_layout->addItem(m_placeholder2);
+
+ setLayout(m_layout);
+
+ setPos(QPointF(0, 0));
+
+ connect(m_rulerItem,
+ &TimelineRulerSectionItem::rulerClicked,
+ this,
+ &TimelineGraphicsLayout::rulerClicked);
+}
+
+TimelineGraphicsLayout::~TimelineGraphicsLayout() = default;
+
+double TimelineGraphicsLayout::rulerWidth() const
+{
+ return m_rulerItem->preferredWidth();
+}
+
+double TimelineGraphicsLayout::rulerScaling() const
+{
+ return m_rulerItem->rulerScaling();
+}
+
+double TimelineGraphicsLayout::rulerDuration() const
+{
+ return m_rulerItem->rulerDuration();
+}
+
+double TimelineGraphicsLayout::startFrame() const
+{
+ return m_rulerItem->startFrame();
+}
+
+double TimelineGraphicsLayout::endFrame() const
+{
+ return m_rulerItem->endFrame();
+}
+
+void TimelineGraphicsLayout::setWidth(int width)
+{
+ m_rulerItem->setSizeHints(width);
+ m_placeholder1->setMinimumWidth(width);
+ m_placeholder2->setMinimumWidth(width);
+ setPreferredWidth(width);
+ setMaximumWidth(width);
+}
+
+void TimelineGraphicsLayout::setTimeline(const QmlTimeline &timeline)
+{
+ m_layout->removeItem(m_rulerItem);
+ m_layout->removeItem(m_placeholder1);
+ m_layout->removeItem(m_placeholder2);
+
+ m_rulerItem->setParentItem(nullptr);
+ m_placeholder1->setParentItem(nullptr);
+ m_placeholder2->setParentItem(nullptr);
+
+ qDeleteAll(this->childItems());
+
+ m_rulerItem->setParentItem(this);
+ m_rulerItem->invalidateRulerSize(timeline);
+ m_layout->addItem(m_rulerItem);
+
+ m_placeholder1->setParentItem(this);
+ m_layout->addItem(m_placeholder1);
+
+ m_layout->invalidate();
+
+ if (timeline.isValid()) {
+ for (const ModelNode &target : timeline.allTargets()) {
+ if (target.isValid()) {
+ auto item = TimelineSectionItem::create(timeline, target, this);
+ m_layout->addItem(item);
+ }
+ }
+ }
+
+ m_placeholder2->setParentItem(this);
+ m_layout->addItem(m_placeholder2);
+
+ if (auto *scene = timelineScene())
+ if (auto *view = scene->timelineView())
+ if (!timeline.isValid() && view->isAttached())
+ emit scaleFactorChanged(0);
+}
+
+void TimelineGraphicsLayout::setRulerScaleFactor(int factor)
+{
+ m_rulerItem->setRulerScaleFactor(factor);
+}
+
+void TimelineGraphicsLayout::invalidate()
+{
+ m_layout->invalidate();
+}
+
+int TimelineGraphicsLayout::maximumScrollValue() const
+{
+ const qreal w = this->geometry().width() - qreal(TimelineConstants::sectionWidth);
+ const qreal duration = m_rulerItem->rulerDuration() + m_rulerItem->rulerDuration() * 0.1;
+ const qreal maxr = m_rulerItem->rulerScaling() * duration - w;
+ return std::round(qMax(maxr, 0.0));
+}
+
+void TimelineGraphicsLayout::activate()
+{
+ m_layout->activate();
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h
new file mode 100644
index 0000000000..d5b7c4debc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineitem.h"
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+
+namespace QmlDesigner {
+
+class TimelineItem;
+class TimelineRulerSectionItem;
+class TimelinePlaceholder;
+
+class QmlTimeline;
+
+class TimelineGraphicsLayout : public TimelineItem
+{
+ Q_OBJECT
+
+signals:
+ void rulerClicked(const QPointF &pos);
+
+ void scaleFactorChanged(int factor);
+
+public:
+ TimelineGraphicsLayout(TimelineGraphicsScene *scene, TimelineItem *parent = nullptr);
+
+ ~TimelineGraphicsLayout() override;
+
+public:
+ double rulerWidth() const;
+
+ double rulerScaling() const;
+
+ double rulerDuration() const;
+
+ double startFrame() const;
+
+ double endFrame() const;
+
+ void setWidth(int width);
+
+ void setTimeline(const QmlTimeline &timeline);
+
+ void setRulerScaleFactor(int factor);
+
+ void invalidate();
+
+ int maximumScrollValue() const;
+
+ void activate();
+
+private:
+ QGraphicsLinearLayout *m_layout = nullptr;
+
+ TimelineRulerSectionItem *m_rulerItem = nullptr;
+
+ TimelinePlaceholder *m_placeholder1 = nullptr;
+
+ TimelinePlaceholder *m_placeholder2 = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp
new file mode 100644
index 0000000000..99cc486710
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp
@@ -0,0 +1,720 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinegraphicsscene.h"
+
+#include "timelineactions.h"
+#include "timelinegraphicslayout.h"
+#include "timelineitem.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinemovetool.h"
+#include "timelineplaceholder.h"
+#include "timelinepropertyitem.h"
+#include "timelinesectionitem.h"
+#include "timelinetoolbar.h"
+#include "timelineview.h"
+#include "timelinewidget.h"
+
+#include <designdocumentview.h>
+#include <exception.h>
+#include <rewritertransaction.h>
+#include <rewriterview.h>
+#include <viewmanager.h>
+#include <qmldesignerplugin.h>
+#include <qmlobjectnode.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <bindingproperty.h>
+
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QComboBox>
+#include <QGraphicsLinearLayout>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QKeyEvent>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+QList<QmlTimelineKeyframeGroup> allTimelineFrames(const QmlTimeline &timeline)
+{
+ QList<QmlTimelineKeyframeGroup> returnList;
+
+ for (const ModelNode &childNode :
+ timeline.modelNode().defaultNodeListProperty().toModelNodeList()) {
+ if (QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(childNode))
+ returnList.append(QmlTimelineKeyframeGroup(childNode));
+ }
+
+ return returnList;
+}
+
+TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *parent)
+ : QGraphicsScene(parent)
+ , m_parent(parent)
+ , m_layout(new TimelineGraphicsLayout(this))
+ , m_currentFrameIndicator(new TimelineFrameHandle)
+ , m_tools(this)
+{
+ addItem(m_layout);
+ addItem(m_currentFrameIndicator);
+
+ setSceneRect(m_layout->geometry());
+
+ connect(m_layout, &QGraphicsWidget::geometryChanged, this, [this]() {
+ auto rect = m_layout->geometry();
+
+ setSceneRect(rect);
+
+ if (auto *gview = graphicsView())
+ gview->setSceneRect(rect.adjusted(0, TimelineConstants::rulerHeight, 0, 0));
+
+ if (auto *rview = rulerView())
+ rview->setSceneRect(rect);
+
+ m_currentFrameIndicator->setHeight(m_layout->geometry().height());
+ });
+
+ auto moveFrameIndicator = [this](const QPointF &pos) {
+ m_currentFrameIndicator->commitPosition(pos);
+ };
+ connect(m_layout, &TimelineGraphicsLayout::rulerClicked, moveFrameIndicator);
+
+ auto changeScale = [this](int factor) {
+ timelineWidget()->changeScaleFactor(factor);
+ setRulerScaling(qreal(factor));
+ };
+ connect(m_layout, &TimelineGraphicsLayout::scaleFactorChanged, changeScale);
+}
+
+TimelineGraphicsScene::~TimelineGraphicsScene()
+{
+ QSignalBlocker block(this);
+ clearSelection();
+ qDeleteAll(items());
+}
+
+void TimelineGraphicsScene::onShow()
+{
+ if (timelineView()->isAttached()) {
+ auto timeline = currentTimeline();
+ if (timeline.isValid()) {
+ int cf = std::round(timeline.currentKeyframe());
+ setCurrentFrame(cf);
+ }
+
+ emit m_layout->scaleFactorChanged(0);
+ }
+}
+
+void TimelineGraphicsScene::setTimeline(const QmlTimeline &timeline)
+{
+ if (qFuzzyCompare(timeline.duration(), 0.0))
+ return;
+
+ m_layout->setTimeline(timeline);
+}
+
+void TimelineGraphicsScene::clearTimeline()
+{
+ m_layout->setTimeline(QmlTimeline());
+}
+
+void TimelineGraphicsScene::setWidth(int width)
+{
+ m_layout->setWidth(width);
+ invalidateScrollbar();
+}
+
+void TimelineGraphicsScene::invalidateLayout()
+{
+ m_layout->invalidate();
+}
+
+void TimelineGraphicsScene::setCurrenFrame(const QmlTimeline &timeline, qreal frame)
+{
+ if (timeline.isValid())
+ m_currentFrameIndicator->setPosition(frame);
+ else
+ m_currentFrameIndicator->setPosition(0);
+
+ invalidateCurrentValues();
+}
+
+void TimelineGraphicsScene::setCurrentFrame(int frame)
+{
+ QmlTimeline timeline(timelineModelNode());
+
+ if (timeline.isValid()) {
+ timeline.modelNode().setAuxiliaryData("currentFrame@NodeInstance", frame);
+ m_currentFrameIndicator->setPosition(frame + timeline.startKeyframe());
+ } else {
+ m_currentFrameIndicator->setPosition(0);
+ }
+
+ invalidateCurrentValues();
+
+ emitStatusBarFrameMessageChanged(frame);
+}
+
+void TimelineGraphicsScene::setStartFrame(int frame)
+{
+ QmlTimeline timeline(timelineModelNode());
+
+ if (timeline.isValid())
+ timeline.modelNode().variantProperty("startFrame").setValue(frame);
+}
+
+void TimelineGraphicsScene::setEndFrame(int frame)
+{
+ QmlTimeline timeline(timelineModelNode());
+
+ if (timeline.isValid())
+ timeline.modelNode().variantProperty("endFrame").setValue(frame);
+}
+
+qreal TimelineGraphicsScene::rulerScaling() const
+{
+ return m_layout->rulerScaling();
+}
+
+int TimelineGraphicsScene::rulerWidth() const
+{
+ return m_layout->rulerWidth();
+}
+
+qreal TimelineGraphicsScene::rulerDuration() const
+{
+ return m_layout->rulerDuration();
+}
+
+qreal TimelineGraphicsScene::startFrame() const
+{
+ return m_layout->startFrame();
+}
+
+qreal TimelineGraphicsScene::endFrame() const
+{
+ return m_layout->endFrame();
+}
+
+qreal TimelineGraphicsScene::mapToScene(qreal x) const
+{
+ return TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ + (x - startFrame()) * rulerScaling() - scrollOffset();
+}
+
+qreal TimelineGraphicsScene::mapFromScene(qreal x) const
+{
+ auto xPosOffset = (x - TimelineConstants::sectionWidth - TimelineConstants::timelineLeftOffset)
+ + scrollOffset();
+
+ return xPosOffset / rulerScaling() + startFrame();
+}
+
+qreal TimelineGraphicsScene::currentFramePosition() const
+{
+ return currentTimeline().currentKeyframe();
+}
+
+QVector<qreal> TimelineGraphicsScene::keyframePositions() const
+{
+ QVector<qreal> positions;
+ for (const auto &frames : allTimelineFrames(currentTimeline()))
+ positions.append(keyframePositions(frames));
+ return positions;
+}
+
+QVector<qreal> TimelineGraphicsScene::keyframePositions(const QmlTimelineKeyframeGroup &frames) const
+{
+ const QList<ModelNode> keyframes = frames.keyframePositions();
+ QVector<qreal> positions;
+ for (const ModelNode &modelNode : keyframes)
+ positions.append(modelNode.variantProperty("frame").value().toReal());
+ return positions;
+}
+
+void TimelineGraphicsScene::setRulerScaling(int scaleFactor)
+{
+ const qreal oldOffset = scrollOffset();
+ const qreal oldScaling = m_layout->rulerScaling();
+ const qreal oldPosition = mapToScene(currentFramePosition());
+ m_layout->setRulerScaleFactor(scaleFactor);
+
+ const qreal newScaling = m_layout->rulerScaling();
+ const qreal newPosition = mapToScene(currentFramePosition());
+
+ const qreal newOffset = oldOffset + (newPosition - oldPosition);
+
+ if (std::isinf(oldScaling) || std::isinf(newScaling))
+ setScrollOffset(0);
+ else {
+ setScrollOffset(std::round(newOffset));
+
+ const qreal start = mapToScene(startFrame());
+ const qreal head = TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset;
+
+ if (start - head > 0)
+ setScrollOffset(0);
+ }
+
+ invalidateSections();
+ QmlTimeline timeline(timelineModelNode());
+
+ if (timeline.isValid())
+ setCurrenFrame(timeline,
+ timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal());
+
+ invalidateScrollbar();
+ update();
+}
+
+void TimelineGraphicsScene::commitCurrentFrame(qreal frame)
+{
+ QmlTimeline timeline(timelineModelNode());
+
+ if (timeline.isValid()) {
+ timeline.modelNode().setAuxiliaryData("currentFrame@NodeInstance", qRound(frame));
+ setCurrenFrame(timeline, qRound(frame));
+ invalidateCurrentValues();
+ }
+ emitStatusBarFrameMessageChanged(int(frame));
+}
+
+QList<TimelineKeyframeItem *> TimelineGraphicsScene::selectedKeyframes() const
+{
+ return m_selectedKeyframes;
+}
+
+bool TimelineGraphicsScene::hasSelection() const
+{
+ return !m_selectedKeyframes.empty();
+}
+
+bool TimelineGraphicsScene::isCurrent(TimelineKeyframeItem *keyframe) const
+{
+ if (m_selectedKeyframes.empty())
+ return false;
+
+ return m_selectedKeyframes.back() == keyframe;
+}
+
+bool TimelineGraphicsScene::isKeyframeSelected(TimelineKeyframeItem *keyframe) const
+{
+ return m_selectedKeyframes.contains(keyframe);
+}
+
+bool TimelineGraphicsScene::multipleKeyframesSelected() const
+{
+ return m_selectedKeyframes.count() > 1;
+}
+
+void TimelineGraphicsScene::invalidateSectionForTarget(const ModelNode &target)
+{
+ if (!target.isValid())
+ return;
+
+ bool found = false;
+ for (auto child : m_layout->childItems())
+ TimelineSectionItem::updateDataForTarget(child, target, &found);
+
+ if (!found)
+ invalidateScene();
+
+ clearSelection();
+ invalidateLayout();
+}
+
+void TimelineGraphicsScene::invalidateKeyframesForTarget(const ModelNode &target)
+{
+ for (auto child : m_layout->childItems())
+ TimelineSectionItem::updateFramesForTarget(child, target);
+}
+
+void TimelineGraphicsScene::invalidateScene()
+{
+ ModelNode node = timelineView()->modelNodeForId(
+ timelineWidget()->toolBar()->currentTimelineId());
+ setTimeline(QmlTimeline(node));
+ invalidateScrollbar();
+}
+
+void TimelineGraphicsScene::invalidateScrollbar()
+{
+ double max = m_layout->maximumScrollValue();
+ timelineWidget()->setupScrollbar(0, max, scrollOffset());
+ if (scrollOffset() > max)
+ setScrollOffset(max);
+}
+
+void TimelineGraphicsScene::invalidateCurrentValues()
+{
+ for (auto item : items())
+ TimelinePropertyItem::updateTextEdit(item);
+}
+
+void TimelineGraphicsScene::invalidateRecordButtonsStatus()
+{
+ for (auto item : items())
+ TimelinePropertyItem::updateRecordButtonStatus(item);
+}
+
+int TimelineGraphicsScene::scrollOffset() const
+{
+ return m_scrollOffset;
+}
+
+void TimelineGraphicsScene::setScrollOffset(int offset)
+{
+ m_scrollOffset = offset;
+ emitScrollOffsetChanged();
+ update();
+}
+
+QGraphicsView *TimelineGraphicsScene::graphicsView() const
+{
+ for (auto *v : views())
+ if (v->objectName() == "SceneView")
+ return v;
+
+ return nullptr;
+}
+
+QGraphicsView *TimelineGraphicsScene::rulerView() const
+{
+ for (auto *v : views())
+ if (v->objectName() == "RulerView")
+ return v;
+
+ return nullptr;
+}
+
+QmlTimeline TimelineGraphicsScene::currentTimeline() const
+{
+ return QmlTimeline(timelineModelNode());
+}
+
+QRectF TimelineGraphicsScene::selectionBounds() const
+{
+ QRectF bbox;
+
+ for (auto *frame : m_selectedKeyframes)
+ bbox = bbox.united(frame->rect());
+
+ return bbox;
+}
+
+void TimelineGraphicsScene::selectKeyframes(const SelectionMode &mode,
+ const QList<TimelineKeyframeItem *> &items)
+{
+ if (mode == SelectionMode::Remove || mode == SelectionMode::Toggle) {
+ for (auto *item : items) {
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) {
+ if (m_selectedKeyframes.contains(keyframe)) {
+ keyframe->setHighlighted(false);
+ m_selectedKeyframes.removeAll(keyframe);
+
+ } else if (mode == SelectionMode::Toggle) {
+ if (!m_selectedKeyframes.contains(keyframe)) {
+ keyframe->setHighlighted(true);
+ m_selectedKeyframes << keyframe;
+ }
+ }
+ }
+ }
+
+ } else {
+ if (mode == SelectionMode::New)
+ clearSelection();
+
+ for (auto item : items) {
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) {
+ if (!m_selectedKeyframes.contains(keyframe)) {
+ keyframe->setHighlighted(true);
+ m_selectedKeyframes.append(keyframe);
+ }
+ }
+ }
+ }
+ emit selectionChanged();
+}
+
+void TimelineGraphicsScene::clearSelection()
+{
+ for (auto *keyframe : m_selectedKeyframes)
+ if (keyframe)
+ keyframe->setHighlighted(false);
+
+ m_selectedKeyframes.clear();
+}
+
+QList<QGraphicsItem *> TimelineGraphicsScene::itemsAt(const QPointF &pos)
+{
+ QTransform transform;
+
+ if (auto *gview = graphicsView())
+ transform = gview->transform();
+
+ return items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform);
+}
+
+void TimelineGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mousePressEvent(topItem, event);
+ QGraphicsScene::mousePressEvent(event);
+}
+
+void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseMoveEvent(topItem, event);
+ QGraphicsScene::mouseMoveEvent(event);
+}
+
+void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ /* The tool has handle the event last. */
+ QGraphicsScene::mouseReleaseEvent(event);
+ m_tools.mouseReleaseEvent(topItem, event);
+}
+
+void TimelineGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseDoubleClickEvent(topItem, event);
+ QGraphicsScene::mouseDoubleClickEvent(event);
+}
+
+void TimelineGraphicsScene::keyPressEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyPressEvent(keyEvent);
+ return;
+ }
+
+ if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) {
+ switch (keyEvent->key()) {
+ case Qt::Key_C:
+ copySelectedKeyframes();
+ break;
+
+ case Qt::Key_V:
+ pasteSelectedKeyframes();
+ break;
+
+ default:
+ QGraphicsScene::keyPressEvent(keyEvent);
+ break;
+ }
+ } else {
+ switch (keyEvent->key()) {
+ case Qt::Key_Left:
+ emit scroll(TimelineUtils::Side::Left);
+ keyEvent->accept();
+ break;
+
+ case Qt::Key_Right:
+ emit scroll(TimelineUtils::Side::Right);
+ keyEvent->accept();
+ break;
+
+ default:
+ QGraphicsScene::keyPressEvent(keyEvent);
+ break;
+ }
+ }
+}
+
+void TimelineGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+ return;
+ }
+
+ switch (keyEvent->key()) {
+ case Qt::Key_Delete:
+ handleKeyframeDeletion();
+ break;
+
+ default:
+ break;
+ }
+
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+}
+
+void TimelineGraphicsScene::invalidateSections()
+{
+ for (auto child : m_layout->childItems())
+ TimelineSectionItem::updateData(child);
+
+ clearSelection();
+ invalidateLayout();
+}
+
+TimelineView *TimelineGraphicsScene::timelineView() const
+{
+ return m_parent->timelineView();
+}
+
+TimelineWidget *TimelineGraphicsScene::timelineWidget() const
+{
+ return m_parent;
+}
+
+TimelineToolBar *TimelineGraphicsScene::toolBar() const
+{
+ return timelineWidget()->toolBar();
+}
+
+ModelNode TimelineGraphicsScene::timelineModelNode() const
+{
+ if (timelineView()->isAttached()) {
+ const QString timelineId = timelineWidget()->toolBar()->currentTimelineId();
+ return timelineView()->modelNodeForId(timelineId);
+ }
+
+ return ModelNode();
+}
+
+void TimelineGraphicsScene::handleKeyframeDeletion()
+{
+ QList<ModelNode> nodesToBeDeleted;
+ for (auto keyframe : m_selectedKeyframes) {
+ nodesToBeDeleted.append(keyframe->frameNode());
+ }
+ deleteKeyframes(nodesToBeDeleted);
+}
+
+void TimelineGraphicsScene::deleteAllKeyframesForTarget(const ModelNode &targetNode)
+{
+ TimelineActions::deleteAllKeyframesForTarget(targetNode, currentTimeline());
+}
+
+void TimelineGraphicsScene::insertAllKeyframesForTarget(const ModelNode &targetNode)
+{
+ TimelineActions::insertAllKeyframesForTarget(targetNode, currentTimeline());
+}
+
+void TimelineGraphicsScene::copyAllKeyframesForTarget(const ModelNode &targetNode)
+{
+ TimelineActions::copyAllKeyframesForTarget(targetNode, currentTimeline());
+}
+
+void TimelineGraphicsScene::pasteKeyframesToTarget(const ModelNode &targetNode)
+{
+ TimelineActions::pasteKeyframesToTarget(targetNode, currentTimeline());
+}
+
+void TimelineGraphicsScene::copySelectedKeyframes()
+{
+ TimelineActions::copyKeyframes(
+ Utils::transform(m_selectedKeyframes, &TimelineKeyframeItem::frameNode));
+}
+
+void TimelineGraphicsScene::pasteSelectedKeyframes()
+{
+ TimelineActions::pasteKeyframes(timelineView(), currentTimeline());
+}
+
+void TimelineGraphicsScene::handleKeyframeInsertion(const ModelNode &target,
+ const PropertyName &propertyName)
+{
+ timelineView()->insertKeyframe(target, propertyName);
+}
+
+void TimelineGraphicsScene::deleteKeyframeGroup(const ModelNode &group)
+{
+ if (!QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(group))
+ return;
+
+ timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeGroupDeletion", [group](){
+ ModelNode nonConst = group;
+ nonConst.destroy();
+ });
+
+}
+
+void TimelineGraphicsScene::deleteKeyframes(const QList<ModelNode> &frames)
+{
+ timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeDeletion", [frames](){
+ for (auto keyframe : frames) {
+ if (keyframe.isValid()) {
+ ModelNode frame = keyframe;
+ ModelNode parent = frame.parentProperty().parentModelNode();
+ keyframe.destroy();
+ if (parent.isValid() && parent.defaultNodeListProperty().isEmpty())
+ parent.destroy();
+ }
+ }
+ });
+}
+
+void TimelineGraphicsScene::activateLayout()
+{
+ m_layout->activate();
+}
+
+void TimelineGraphicsScene::emitScrollOffsetChanged()
+{
+ for (QGraphicsItem *item : items())
+ TimelineMovableAbstractItem::emitScrollOffsetChanged(item);
+}
+
+void TimelineGraphicsScene::emitStatusBarFrameMessageChanged(int frame)
+{
+ emit statusBarMessageChanged(
+ QString(TimelineConstants::timelineStatusBarFrameNumber).arg(frame));
+}
+
+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);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h
new file mode 100644
index 0000000000..b8f93595c4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelinetooldelegate.h"
+#include "timelineutils.h"
+
+#include <qmltimeline.h>
+
+#include <QGraphicsScene>
+
+#include <memory>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+
+namespace QmlDesigner {
+
+class TimelineView;
+class TimelineWidget;
+class TimelineItem;
+class TimelineRulerSectionItem;
+class TimelineFrameHandle;
+class TimelineAbstractTool;
+class TimelineMoveTool;
+class TimelineKeyframeItem;
+class TimelinePlaceholder;
+class TimelineGraphicsLayout;
+class TimelineToolBar;
+
+class TimelineGraphicsScene : public QGraphicsScene
+{
+ Q_OBJECT
+
+signals:
+ void selectionChanged();
+
+ void scroll(const TimelineUtils::Side &side);
+
+public:
+ explicit TimelineGraphicsScene(TimelineWidget *parent);
+
+ ~TimelineGraphicsScene() override;
+
+ void onShow();
+
+ void setTimeline(const QmlTimeline &timeline);
+ void clearTimeline();
+
+ void setWidth(int width);
+
+ void invalidateLayout();
+ void setCurrenFrame(const QmlTimeline &timeline, qreal frame);
+ void setCurrentFrame(int frame);
+ void setStartFrame(int frame);
+ void setEndFrame(int frame);
+
+ TimelineView *timelineView() const;
+ TimelineWidget *timelineWidget() const;
+ TimelineToolBar *toolBar() const;
+
+ qreal rulerScaling() const;
+ int rulerWidth() const;
+ qreal rulerDuration() const;
+ qreal startFrame() const;
+ qreal endFrame() const;
+
+ qreal mapToScene(qreal x) const;
+ qreal mapFromScene(qreal x) const;
+
+ qreal currentFramePosition() const;
+ QVector<qreal> keyframePositions() const;
+ QVector<qreal> keyframePositions(const QmlTimelineKeyframeGroup &frames) const;
+
+ void setRulerScaling(int scaling);
+
+ void commitCurrentFrame(qreal frame);
+
+ QList<TimelineKeyframeItem *> selectedKeyframes() const;
+
+ bool hasSelection() const;
+ bool isCurrent(TimelineKeyframeItem *keyframe) const;
+ bool isKeyframeSelected(TimelineKeyframeItem *keyframe) const;
+ bool multipleKeyframesSelected() const;
+
+ void invalidateSectionForTarget(const ModelNode &modelNode);
+ void invalidateKeyframesForTarget(const ModelNode &modelNode);
+
+ void invalidateScene();
+ void invalidateScrollbar();
+ void invalidateCurrentValues();
+ void invalidateRecordButtonsStatus();
+
+ int scrollOffset() const;
+ void setScrollOffset(int offset);
+ QGraphicsView *graphicsView() const;
+ QGraphicsView *rulerView() const;
+
+ QmlTimeline currentTimeline() const;
+
+ QRectF selectionBounds() const;
+
+ void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items);
+ void clearSelection();
+
+ void handleKeyframeDeletion();
+ void deleteAllKeyframesForTarget(const ModelNode &targetNode);
+ void insertAllKeyframesForTarget(const ModelNode &targetNode);
+ void copyAllKeyframesForTarget(const ModelNode &targetNode);
+ void pasteKeyframesToTarget(const ModelNode &targetNode);
+
+ void handleKeyframeInsertion(const ModelNode &target, const PropertyName &propertyName);
+
+ void deleteKeyframeGroup(const ModelNode &group);
+ void deleteKeyframes(const QList<ModelNode> &frames);
+
+ void activateLayout();
+
+signals:
+ void statusBarMessageChanged(const QString &message);
+
+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 keyPressEvent(QKeyEvent *keyEvent) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+private:
+ void copySelectedKeyframes();
+ void pasteSelectedKeyframes();
+
+ void invalidateSections();
+ ModelNode timelineModelNode() const;
+
+ void emitScrollOffsetChanged();
+ void emitStatusBarFrameMessageChanged(int frame);
+
+ QList<QGraphicsItem *> itemsAt(const QPointF &pos);
+
+private:
+ TimelineWidget *m_parent = nullptr;
+
+ TimelineGraphicsLayout *m_layout = nullptr;
+
+ TimelineFrameHandle *m_currentFrameIndicator = nullptr;
+
+ TimelineToolDelegate m_tools;
+
+ QList<TimelineKeyframeItem *> m_selectedKeyframes;
+
+ int m_scrollOffset = 0;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineicons.h b/src/plugins/qmldesigner/components/timelineeditor/timelineicons.h
new file mode 100644
index 0000000000..641d4e77b6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineicons.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <utils/icon.h>
+
+namespace QmlDesigner {
+namespace TimelineIcons {
+
+// Icons on the timeline ruler
+const Utils::Icon WORK_AREA_HANDLE_LEFT(
+ ":/timelineplugin/images/work_area_handle_left.png");
+const Utils::Icon WORK_AREA_HANDLE_RIGHT(
+ ":/timelineplugin/images/work_area_handle_right.png");
+const Utils::Icon PLAYHEAD(
+ ":/timelineplugin/images/playhead.png");
+
+// Icons on the timeline tracks
+const Utils::Icon KEYFRAME_LINEAR_INACTIVE(
+ ":/timelineplugin/images/keyframe_linear_inactive.png");
+const Utils::Icon KEYFRAME_LINEAR_ACTIVE(
+ ":/timelineplugin/images/keyframe_linear_active.png");
+const Utils::Icon KEYFRAME_LINEAR_SELECTED(
+ ":/timelineplugin/images/keyframe_linear_selected.png");
+const Utils::Icon KEYFRAME_MANUALBEZIER_INACTIVE(
+ ":/timelineplugin/images/keyframe_manualbezier_inactive.png");
+const Utils::Icon KEYFRAME_MANUALBEZIER_ACTIVE(
+ ":/timelineplugin/images/keyframe_manualbezier_active.png");
+const Utils::Icon KEYFRAME_MANUALBEZIER_SELECTED(
+ ":/timelineplugin/images/keyframe_manualbezier_selected.png");
+const Utils::Icon KEYFRAME_AUTOBEZIER_INACTIVE(
+ ":/timelineplugin/images/keyframe_autobezier_inactive.png");
+const Utils::Icon KEYFRAME_AUTOBEZIER_ACTIVE(
+ ":/timelineplugin/images/keyframe_autobezier_active.png");
+const Utils::Icon KEYFRAME_AUTOBEZIER_SELECTED(
+ ":/timelineplugin/images/keyframe_autobezier_selected.png");
+const Utils::Icon KEYFRAME_LINEARTOBEZIER_INACTIVE(
+ ":/timelineplugin/images/keyframe_lineartobezier_inactive.png");
+const Utils::Icon KEYFRAME_LINEARTOBEZIER_ACTIVE(
+ ":/timelineplugin/images/keyframe_lineartobezier_active.png");
+const Utils::Icon KEYFRAME_LINEARTOBEZIER_SELECTED(
+ ":/timelineplugin/images/keyframe_lineartobezier_selected.png");
+
+// Icons on the "section"
+const Utils::Icon KEYFRAME(
+ ":/timelineplugin/images/keyframe.png");
+const Utils::Icon IS_KEYFRAME(
+ ":/timelineplugin/images/is_keyframe.png");
+const Utils::Icon NEXT_KEYFRAME({
+ {":/timelineplugin/images/next_keyframe.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon PREVIOUS_KEYFRAME({
+ {":/timelineplugin/images/previous_keyframe.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon LOCAL_RECORD_KEYFRAMES({
+ {":/timelineplugin/images/local_record_keyframes.png", Utils::Theme::IconsStopToolBarColor}});
+const Utils::Icon ADD_TIMELINE({
+ {":/timelineplugin/images/add_timeline.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon REMOVE_TIMELINE({
+ {":/timelineplugin/images/remove_timeline.png", Utils::Theme::IconsBaseColor}});
+
+// Icons on the toolbars
+const Utils::Icon ANIMATION({
+ {":/timelineplugin/images/animation.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon TO_FIRST_FRAME({
+ {":/timelineplugin/images/to_first_frame.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon BACK_ONE_FRAME({
+ {":/timelineplugin/images/back_one_frame.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon START_PLAYBACK({
+ {":/timelineplugin/images/start_playback.png", Utils::Theme::IconsRunToolBarColor}});
+const Utils::Icon PAUSE_PLAYBACK({
+ {":/timelineplugin/images/pause_playback.png", Utils::Theme::IconsInterruptToolBarColor}});
+const Utils::Icon FORWARD_ONE_FRAME({
+ {":/timelineplugin/images/forward_one_frame.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon TO_LAST_FRAME({
+ {":/timelineplugin/images/to_last_frame.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon LOOP_PLAYBACK({
+ {":/timelineplugin/images/loop_playback.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon CURVE_PICKER({
+ {":/timelineplugin/images/curve_picker.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon CURVE_EDITOR({
+ {":/timelineplugin/images/curve_editor.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon GLOBAL_RECORD_KEYFRAMES({
+ {":/timelineplugin/images/global_record_keyframes.png", Utils::Theme::IconsStopToolBarColor}});
+const Utils::Icon GLOBAL_RECORD_KEYFRAMES_OFF({
+ {":/timelineplugin/images/global_record_keyframes.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon ZOOM_SMALL({
+ {":/timelineplugin/images/zoom_small.png", Utils::Theme::IconsBaseColor}});
+const Utils::Icon ZOOM_BIG({
+ {":/timelineplugin/images/zoom_big.png", Utils::Theme::IconsBaseColor}});
+
+} // namespace TimelineIcons
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp
new file mode 100644
index 0000000000..ebe3644e4e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp
@@ -0,0 +1,293 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineitem.h"
+
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelineicons.h"
+#include "timelinesectionitem.h"
+#include "timelineutils.h"
+#include "timelinewidget.h"
+
+#include <theme.h>
+
+#include <coreplugin/icore.h>
+
+#include <QApplication>
+#include <QCursor>
+#include <QGraphicsView>
+#include <QPainter>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+TimelineItem::TimelineItem(TimelineItem *parent)
+ : QGraphicsWidget(parent)
+{}
+
+TimelineGraphicsScene *TimelineItem::timelineScene() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene());
+ ;
+}
+
+TimelineFrameHandle::TimelineFrameHandle(TimelineItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ static const QColor color = Theme::getColor(Theme::IconsWarningToolBarColor);
+ setBrush(color);
+ setPen(color);
+
+ setRect(0, 0, TimelineConstants::rulerHeight, TimelineConstants::rulerHeight);
+ setZValue(40);
+ setCursor(Qt::ClosedHandCursor);
+
+ m_timer.setSingleShot(true);
+ m_timer.setInterval(15);
+ QObject::connect(&m_timer, &QTimer::timeout, [this]() {
+ if (QApplication::mouseButtons() == Qt::LeftButton)
+ scrollOutOfBounds();
+ });
+}
+
+void TimelineFrameHandle::setHeight(int height)
+{
+ setRect(rect().x(), rect().y(), rect().width(), height);
+}
+
+void TimelineFrameHandle::setPosition(qreal position)
+{
+ const qreal scenePos = mapFromFrameToScene(position);
+ QRectF newRect(scenePos - rect().width() / 2, rect().y(), rect().width(), rect().height());
+
+ if (!qFuzzyCompare(newRect.x(), rect().x())) {
+ setRect(newRect);
+ }
+ m_position = position;
+}
+
+void TimelineFrameHandle::setPositionInteractive(const QPointF &position)
+{
+ const double width = timelineScene()->width();
+
+ if (position.x() > width) {
+ callSetClampedXPosition(width - (rect().width() / 2) - 1);
+ m_timer.start();
+ } else if (position.x() < TimelineConstants::sectionWidth) {
+ callSetClampedXPosition(TimelineConstants::sectionWidth);
+ m_timer.start();
+ } else {
+ callSetClampedXPosition(position.x() - rect().width() / 2);
+ const qreal frame = std::round(mapFromSceneToFrame(rect().center().x()));
+ timelineScene()->commitCurrentFrame(frame);
+ }
+}
+
+void TimelineFrameHandle::commitPosition(const QPointF &point)
+{
+ setPositionInteractive(point);
+}
+
+void TimelineItem::drawLine(QPainter *painter, qreal x1, qreal y1, qreal x2, qreal y2)
+{
+ painter->drawLine(QPointF(x1 + 0.5, y1 + 0.5), QPointF(x2 + 0.5, y2 + 0.5));
+}
+
+qreal TimelineFrameHandle::position() const
+{
+ return m_position;
+}
+
+TimelineFrameHandle *TimelineFrameHandle::asTimelineFrameHandle()
+{
+ return this;
+}
+
+void TimelineFrameHandle::scrollOffsetChanged()
+{
+ setPosition(position());
+}
+
+QPainterPath TimelineFrameHandle::shape() const
+{
+ QPainterPath path;
+ QRectF rect = boundingRect();
+ rect.setHeight(TimelineConstants::sectionHeight);
+ rect.adjust(-4, 0, 4, 0);
+ path.addEllipse(rect);
+ return path;
+}
+
+static int devicePixelWidth(const QPixmap &pixmap)
+{
+ return pixmap.width() / pixmap.devicePixelRatioF();
+}
+
+static int devicePixelHeight(const QPixmap &pixmap)
+{
+ return pixmap.height() / pixmap.devicePixelRatioF();
+}
+
+void TimelineFrameHandle::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem * /*option*/,
+ QWidget * /*widget*/)
+{
+ static const QPixmap playHead = TimelineIcons::PLAYHEAD.pixmap();
+
+ static const int pixmapHeight = devicePixelHeight(playHead);
+ static const int pixmapWidth = devicePixelWidth(playHead);
+
+ if (rect().x() < TimelineConstants::sectionWidth - rect().width() / 2)
+ return;
+
+ painter->save();
+ painter->setOpacity(0.8);
+ const qreal center = rect().width() / 2 + rect().x();
+
+ painter->setPen(pen());
+
+ auto offsetTop = pixmapHeight - 7;
+ TimelineItem::drawLine(painter, center, offsetTop, center, rect().height() - 1);
+
+ const QPointF pmTopLeft(center - pixmapWidth / 2, -4.);
+ painter->drawPixmap(pmTopLeft, playHead);
+
+ painter->restore();
+}
+
+QPointF TimelineFrameHandle::mapFromGlobal(const QPoint &pos) const
+{
+ for (auto *view : timelineScene()->views()) {
+ if (view->objectName() == "SceneView") {
+ auto graphicsViewCoords = view->mapFromGlobal(pos);
+ auto sceneCoords = view->mapToScene(graphicsViewCoords);
+ return sceneCoords;
+ }
+ }
+ return {};
+}
+
+int TimelineFrameHandle::computeScrollSpeed() const
+{
+ const double mouse = mapFromGlobal(QCursor::pos()).x();
+ const double width = timelineScene()->width();
+
+ const double acc = mouse > width ? mouse - width
+ : double(TimelineConstants::sectionWidth) - mouse;
+ const double delta = TimelineUtils::clamp<double>(acc, 0., 200.);
+ const double blend = TimelineUtils::reverseLerp(delta, 0., 200.);
+ const double factor = TimelineUtils::lerp<double>(blend, 5, 20);
+
+ if (mouse > width)
+ return scrollOffset() + std::round(factor);
+ else
+ return scrollOffset() - std::round(factor);
+
+ return 0;
+}
+
+void TimelineFrameHandle::callSetClampedXPosition(double x)
+{
+ const int minimumWidth = TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ - rect().width() / 2;
+ const int maximumWidth = minimumWidth
+ + timelineScene()->rulerDuration() * timelineScene()->rulerScaling()
+ - scrollOffset();
+
+ setClampedXPosition(x, minimumWidth, maximumWidth);
+}
+
+// Auto scroll when dragging playhead out of bounds.
+void TimelineFrameHandle::scrollOutOfBounds()
+{
+ const double width = timelineScene()->width();
+ const double mouse = mapFromGlobal(QCursor::pos()).x();
+
+ if (mouse > width)
+ scrollOutOfBoundsMax();
+ else if (mouse < TimelineConstants::sectionWidth)
+ scrollOutOfBoundsMin();
+}
+
+void TimelineFrameHandle::scrollOutOfBoundsMax()
+{
+ const double width = timelineScene()->width();
+ if (QApplication::mouseButtons() == Qt::LeftButton) {
+ const double frameWidth = timelineScene()->rulerScaling();
+ const double upperThreshold = width - frameWidth;
+
+ if (rect().center().x() > upperThreshold) {
+ timelineScene()->setScrollOffset(computeScrollSpeed());
+ timelineScene()->invalidateScrollbar();
+ }
+
+ callSetClampedXPosition(width - (rect().width() / 2) - 1);
+ m_timer.start();
+ } else {
+ // Mouse release
+ callSetClampedXPosition(width - (rect().width() / 2) - 1);
+
+ const int frame = std::floor(mapFromSceneToFrame(rect().center().x()));
+ const int ef = timelineScene()->endFrame();
+ timelineScene()->commitCurrentFrame(frame <= ef ? frame : ef);
+ }
+}
+
+void TimelineFrameHandle::scrollOutOfBoundsMin()
+{
+ if (QApplication::mouseButtons() == Qt::LeftButton) {
+ auto offset = computeScrollSpeed();
+
+ if (offset >= 0)
+ timelineScene()->setScrollOffset(offset);
+ else
+ timelineScene()->setScrollOffset(0);
+
+ timelineScene()->invalidateScrollbar();
+
+ callSetClampedXPosition(TimelineConstants::sectionWidth);
+ m_timer.start();
+ } else {
+ // Mouse release
+ callSetClampedXPosition(TimelineConstants::sectionWidth);
+
+ int frame = mapFromSceneToFrame(rect().center().x());
+
+ const int sframe = timelineScene()->startFrame();
+ if (frame != sframe) {
+ const qreal framePos = mapFromFrameToScene(frame);
+
+ if (framePos
+ <= (TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset))
+ frame++;
+ }
+
+ timelineScene()->commitCurrentFrame(frame >= sframe ? frame : sframe);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h
new file mode 100644
index 0000000000..87fb9e3ec8
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelinemovableabstractitem.h"
+
+#include <QGraphicsRectItem>
+#include <QGraphicsWidget>
+#include <QTimer>
+
+namespace QmlDesigner {
+
+class TimelineItem : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineItem(TimelineItem *parent = nullptr);
+
+ static void drawLine(QPainter *painter, qreal x1, qreal y1, qreal x2, qreal y2);
+ TimelineGraphicsScene *timelineScene() const;
+};
+
+class TimelineFrameHandle : public TimelineMovableAbstractItem
+{
+public:
+ explicit TimelineFrameHandle(TimelineItem *parent = nullptr);
+
+ void setHeight(int height);
+ void setPosition(qreal position);
+ void setPositionInteractive(const QPointF &postion) override;
+ void commitPosition(const QPointF &point) override;
+ qreal position() const;
+
+ TimelineFrameHandle *asTimelineFrameHandle() override;
+
+protected:
+ void scrollOffsetChanged() override;
+ QPainterPath shape() const override;
+ void paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+
+private:
+ QPointF mapFromGlobal(const QPoint &pos) const;
+ int computeScrollSpeed() const;
+
+ void callSetClampedXPosition(double x);
+ void scrollOutOfBounds();
+ void scrollOutOfBoundsMax();
+ void scrollOutOfBoundsMin();
+
+private:
+ qreal m_position = 0;
+
+ QTimer m_timer;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp
new file mode 100644
index 0000000000..4db4567fd6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp
@@ -0,0 +1,154 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinemovableabstractitem.h"
+
+#include "timelinegraphicsscene.h"
+
+#include "timelineitem.h"
+
+#include <QGraphicsSceneMouseEvent>
+
+namespace QmlDesigner {
+
+TimelineMovableAbstractItem::TimelineMovableAbstractItem(QGraphicsItem *parent)
+ : QGraphicsRectItem(parent)
+{}
+
+void TimelineMovableAbstractItem::setPositionInteractive(const QPointF &) {}
+
+void TimelineMovableAbstractItem::commitPosition(const QPointF &) {}
+
+void TimelineMovableAbstractItem::itemMoved(const QPointF & /*start*/, const QPointF &end)
+{
+ setPositionInteractive(end);
+}
+
+int TimelineMovableAbstractItem::scrollOffset() const
+{
+ return timelineScene()->scrollOffset();
+}
+
+int TimelineMovableAbstractItem::xPosScrollOffset(int x) const
+{
+ return x + scrollOffset();
+}
+
+qreal TimelineMovableAbstractItem::mapFromFrameToScene(qreal x) const
+{
+ return TimelineConstants::sectionWidth + (x - timelineScene()->startFrame()) * rulerScaling()
+ - scrollOffset() + TimelineConstants::timelineLeftOffset;
+}
+
+qreal TimelineMovableAbstractItem::mapFromSceneToFrame(qreal x) const
+{
+ return xPosScrollOffset(x - TimelineConstants::sectionWidth
+ - TimelineConstants::timelineLeftOffset)
+ / timelineScene()->rulerScaling()
+ + timelineScene()->startFrame();
+}
+
+void TimelineMovableAbstractItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ event->accept();
+}
+
+void TimelineMovableAbstractItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ event->accept();
+}
+
+void TimelineMovableAbstractItem::setClampedXPosition(qreal x,
+ qreal minimumWidth,
+ qreal maximumWidth)
+{
+ if (x > minimumWidth) {
+ if (x < maximumWidth)
+ setRect(x, rect().y(), rect().width(), rect().height());
+ else
+ setRect(maximumWidth, rect().y(), rect().width(), rect().height());
+ } else {
+ setRect(minimumWidth, rect().y(), rect().width(), rect().height());
+ }
+}
+
+TimelineMovableAbstractItem *TimelineMovableAbstractItem::cast(QGraphicsItem *item)
+{
+ return qgraphicsitem_cast<TimelineMovableAbstractItem *>(item);
+}
+
+TimelineMovableAbstractItem *TimelineMovableAbstractItem::topMoveableItem(
+ const QList<QGraphicsItem *> &items)
+{
+ for (auto item : items)
+ if (auto castedItem = TimelineMovableAbstractItem::cast(item))
+ return castedItem;
+
+ return nullptr;
+}
+
+void TimelineMovableAbstractItem::emitScrollOffsetChanged(QGraphicsItem *item)
+{
+ auto movableItem = TimelineMovableAbstractItem::cast(item);
+ if (movableItem)
+ movableItem->scrollOffsetChanged();
+}
+
+TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem(QGraphicsItem *item)
+{
+ auto movableItem = TimelineMovableAbstractItem::cast(item);
+
+ if (movableItem)
+ return movableItem->asTimelineKeyframeItem();
+
+ return nullptr;
+}
+
+qreal TimelineMovableAbstractItem::rulerScaling() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene())->rulerScaling();
+}
+
+int TimelineMovableAbstractItem::type() const
+{
+ return Type;
+}
+
+TimelineGraphicsScene *TimelineMovableAbstractItem::timelineScene() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene());
+}
+
+TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem()
+{
+ return nullptr;
+}
+
+TimelineFrameHandle *TimelineMovableAbstractItem::asTimelineFrameHandle()
+{
+ return nullptr;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h
new file mode 100644
index 0000000000..4bc11675c2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineconstants.h"
+
+#include <QCoreApplication>
+#include <QGraphicsRectItem>
+
+namespace QmlDesigner {
+
+class TimelineGraphicsScene;
+class TimelineKeyframeItem;
+class TimelineFrameHandle;
+
+class TimelineMovableAbstractItem : public QGraphicsRectItem
+{
+ Q_DECLARE_TR_FUNCTIONS(TimelineMovableAbstractItem)
+
+public:
+ enum { Type = TimelineConstants::moveableAbstractItemUserType };
+
+ explicit TimelineMovableAbstractItem(QGraphicsItem *item);
+
+ int type() const override;
+
+ static TimelineMovableAbstractItem *cast(QGraphicsItem *item);
+ static TimelineMovableAbstractItem *topMoveableItem(const QList<QGraphicsItem *> &items);
+ static void emitScrollOffsetChanged(QGraphicsItem *item);
+ static TimelineKeyframeItem *asTimelineKeyframeItem(QGraphicsItem *item);
+
+ qreal rulerScaling() const;
+
+ virtual void setPositionInteractive(const QPointF &point);
+ virtual void commitPosition(const QPointF &point);
+ virtual void itemMoved(const QPointF &start, const QPointF &end);
+
+ int xPosScrollOffset(int x) const;
+
+ qreal mapFromFrameToScene(qreal x) const;
+ qreal mapFromSceneToFrame(qreal x) const;
+
+ virtual void scrollOffsetChanged() = 0;
+
+ virtual TimelineKeyframeItem *asTimelineKeyframeItem();
+ virtual TimelineFrameHandle *asTimelineFrameHandle();
+
+protected:
+ int scrollOffset() const;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void setClampedXPosition(qreal x, qreal min, qreal max);
+ TimelineGraphicsScene *timelineScene() const;
+
+private:
+ bool m_multiSelectedMove = false;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp
new file mode 100644
index 0000000000..0384d7c0a3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp
@@ -0,0 +1,173 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinemovetool.h"
+
+#include "timelinegraphicsscene.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinepropertyitem.h"
+#include "timelineview.h"
+
+#include <exception.h>
+
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+static QPointF mapPointToItem(TimelineMovableAbstractItem *item, const QPointF &pos)
+{
+ if (auto parent = item->parentItem())
+ return parent->mapFromScene(pos);
+ return pos;
+}
+
+QPointF mapToItem(TimelineMovableAbstractItem *item, const QPointF &pos)
+{
+ if (auto parent = item->parentItem())
+ return parent->mapFromScene(pos);
+ return pos;
+}
+
+QPointF mapToItem(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event)
+{
+ if (auto parent = item->parentItem())
+ return parent->mapFromScene(event->scenePos());
+ return event->scenePos();
+}
+
+TimelineMoveTool::TimelineMoveTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate)
+ : TimelineAbstractTool(scene, delegate)
+{}
+
+void TimelineMoveTool::mousePressEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+}
+
+void TimelineMoveTool::mouseMoveEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+
+ if (!currentItem())
+ return;
+
+ if (auto *current = currentItem()->asTimelineKeyframeItem()) {
+ const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x()));
+ const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x()));
+ qreal deltaFrame = targetFrame - sourceFrame;
+
+ const qreal minFrame = scene()->startFrame();
+ const qreal maxFrame = scene()->endFrame();
+
+ auto bbox = scene()->selectionBounds().united(current->rect());
+
+ double firstFrame = std::round(current->mapFromSceneToFrame(bbox.center().x()));
+ double lastFrame = std::round(current->mapFromSceneToFrame(bbox.center().x()));
+
+ if ((lastFrame + deltaFrame) > maxFrame)
+ deltaFrame = maxFrame - lastFrame;
+
+ if ((firstFrame + deltaFrame) <= minFrame)
+ deltaFrame = minFrame - firstFrame;
+
+ current->setPosition(sourceFrame + deltaFrame);
+
+ for (auto *keyframe : scene()->selectedKeyframes()) {
+ if (keyframe != current) {
+ qreal pos = std::round(current->mapFromSceneToFrame(keyframe->rect().center().x()));
+ keyframe->setPosition(pos + deltaFrame);
+ }
+ }
+
+ } else {
+ currentItem()->itemMoved(mapPointToItem(currentItem(), startPosition()),
+ mapToItem(currentItem(), event));
+ }
+}
+
+void TimelineMoveTool::mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+
+ if (auto *current = currentItem()) {
+ if (current->asTimelineFrameHandle()) {
+ double mousePos = event->pos().x();
+ double start = current->mapFromFrameToScene(scene()->startFrame());
+ double end = current->mapFromFrameToScene(scene()->endFrame());
+
+ if (mousePos < start) {
+ scene()->setCurrentFrame(scene()->startFrame());
+ scene()->statusBarMessageChanged(QObject::tr("Frame %1").arg(scene()->startFrame()));
+ return;
+ } else if (mousePos > end) {
+ scene()->setCurrentFrame(scene()->endFrame());
+ scene()->statusBarMessageChanged(QObject::tr("Frame %1").arg(scene()->endFrame()));
+ return;
+ }
+ }
+
+ scene()->timelineView()->executeInTransaction("TimelineMoveTool::mouseReleaseEvent", [this, current](){
+ current->commitPosition(mapToItem(current, current->rect().center()));
+
+ if (current->asTimelineKeyframeItem()) {
+ double frame = std::round(
+ current->mapFromSceneToFrame(current->rect().center().x()));
+
+ scene()->statusBarMessageChanged(QObject::tr("Frame %1").arg(frame));
+
+ for (auto keyframe : scene()->selectedKeyframes())
+ if (keyframe != current)
+ keyframe->commitPosition(mapToItem(current, keyframe->rect().center()));
+ }
+ });
+ }
+}
+
+void TimelineMoveTool::mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+}
+
+void TimelineMoveTool::keyPressEvent(QKeyEvent *keyEvent)
+{
+ Q_UNUSED(keyEvent);
+}
+
+void TimelineMoveTool::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ Q_UNUSED(keyEvent);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h
new file mode 100644
index 0000000000..55b9a39417
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineabstracttool.h"
+
+#include <QPointF>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsRectItem)
+
+namespace QmlDesigner {
+
+class TimelineMovableAbstractItem;
+
+class TimelineMoveTool : public TimelineAbstractTool
+{
+public:
+ explicit TimelineMoveTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+ void mousePressEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+
+ void keyPressEvent(QKeyEvent *keyEvent) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.cpp
new file mode 100644
index 0000000000..7e7cb69fc9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.cpp
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineplaceholder.h"
+
+#include <theme.h>
+
+#include <QPainter>
+
+namespace QmlDesigner {
+
+TimelinePlaceholder::TimelinePlaceholder(TimelineItem *parent)
+ : TimelineItem(parent)
+{
+ setPreferredHeight(TimelineConstants::sectionHeight);
+ setMinimumHeight(TimelineConstants::sectionHeight);
+ setMaximumHeight(TimelineConstants::sectionHeight);
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+}
+
+TimelinePlaceholder *TimelinePlaceholder::create(QGraphicsScene * /*parentScene*/,
+ TimelineItem *parent)
+{
+ auto item = new TimelinePlaceholder(parent);
+
+ return item;
+}
+
+void TimelinePlaceholder::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ painter->save();
+ static const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ static const QColor backgroundColor = Theme::instance()
+ ->qmlDesignerBackgroundColorDarkAlternate();
+ static const QColor backgroundColorSection = Theme::getColor(Theme::BackgroundColorDark);
+
+ painter->fillRect(0, 0, size().width(), size().height(), backgroundColor);
+ painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColorSection);
+
+ painter->setPen(penColor);
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height() - 1);
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+ painter->restore();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.h b/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.h
new file mode 100644
index 0000000000..14d6d8a2fc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineplaceholder.h
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineitem.h"
+
+namespace QmlDesigner {
+
+class TimelinePlaceholder : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ static TimelinePlaceholder *create(QGraphicsScene *parentScene, TimelineItem *parent);
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override;
+
+private:
+ TimelinePlaceholder(TimelineItem *parent = nullptr);
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
new file mode 100644
index 0000000000..beeca23183
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
@@ -0,0 +1,641 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinepropertyitem.h"
+
+#include "abstractview.h"
+#include "easingcurvedialog.h"
+#include "setframevaluedialog.h"
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelineicons.h"
+#include "timelinetoolbar.h"
+#include "timelinetoolbutton.h"
+
+#include <rewritertransaction.h>
+#include <rewritingexception.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmlobjectnode.h>
+
+#include <coreplugin/icore.h>
+#include <utils/qtcassert.h>
+#include <utils/utilsicons.h>
+
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <coreplugin/icore.h>
+
+#include <QCursor>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QLineEdit>
+#include <QMenu>
+#include <QPainter>
+
+#include <algorithm>
+
+namespace QmlDesigner {
+
+static bool s_blockUpdates = false;
+
+static qreal findNext(const QVector<qreal> &vector, qreal current)
+{
+ for (qreal n : vector)
+ if (n > current)
+ return n;
+ return current;
+}
+
+static qreal findPrev(const QVector<qreal> &vector, qreal current)
+{
+ for (qreal n : vector)
+ if (n < current)
+ return n;
+ return current;
+}
+
+static QVector<qreal> getPositions(const QmlTimelineKeyframeGroup &frames)
+{
+ const QList<ModelNode> keyframes = frames.keyframePositions();
+ QVector<qreal> positions;
+ for (const ModelNode &modelNode : keyframes)
+ positions.append(modelNode.variantProperty("frame").value().toReal());
+ return positions;
+}
+
+static ModelNode getModelNodeForFrame(const QmlTimelineKeyframeGroup &frames, qreal frame)
+{
+ if (frames.isValid()) {
+ const QList<ModelNode> keyframes = frames.keyframePositions();
+ for (const ModelNode &modelNode : keyframes)
+ if (qFuzzyCompare(modelNode.variantProperty("frame").value().toReal(), frame))
+ return modelNode;
+ }
+
+ return {};
+}
+
+static void setEasingCurve(TimelineGraphicsScene *scene, const QList<ModelNode> &keys)
+{
+ QTC_ASSERT(scene, return );
+ EasingCurveDialog::runDialog(keys);
+}
+
+static void editValue(const ModelNode &frame, const QString &propertyName)
+{
+ const QVariant value = frame.variantProperty("value").value();
+ auto dialog = new SetFrameValueDialog(Core::ICore::dialogParent());
+
+ dialog->lineEdit()->setText(value.toString());
+ dialog->setPropertName(propertyName);
+
+ QObject::connect(dialog, &SetFrameValueDialog::rejected, [dialog]() { dialog->deleteLater(); });
+
+ QObject::connect(dialog, &SetFrameValueDialog::accepted, [dialog, frame, value]() {
+ dialog->deleteLater();
+ int userType = value.userType();
+ const QVariant result = dialog->lineEdit()->text();
+
+ if (result.canConvert(userType)) {
+ QVariant newValue = result;
+ newValue.convert(userType);
+ // canConvert gives true in case if the result is a double but the usertype was interger
+ // try to fix that with a workaround to convert it to double if convertion resulted in isNull
+ if (newValue.isNull()) {
+ newValue = result;
+ newValue.convert(QMetaType::Double);
+ }
+ frame.variantProperty("value").setValue(result);
+ }
+ });
+
+ dialog->show();
+}
+
+TimelinePropertyItem *TimelinePropertyItem::create(const QmlTimelineKeyframeGroup &frames,
+ TimelineSectionItem *parent)
+{
+ ModelNode modelnode = frames.target();
+
+ bool isRecording = false;
+
+ if (frames.isValid())
+ isRecording = frames.isRecording();
+
+ auto item = new TimelinePropertyItem(parent);
+
+ auto sectionItem = new QGraphicsWidget(item);
+
+ sectionItem->setGeometry(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight);
+
+ sectionItem->setZValue(10);
+ sectionItem->setCursor(Qt::ArrowCursor);
+
+ item->m_frames = frames;
+ item->setToolTip(item->propertyName());
+ item->resize(parent->size());
+ item->setupKeyframes();
+
+ TimelineToolButton *buttonPrev
+ = new TimelineToolButton(new QAction(TimelineIcons::PREVIOUS_KEYFRAME.icon(),
+ tr("Previous Frame")),
+ sectionItem);
+ buttonPrev->setToolTip("Jump to previous frame.");
+
+ TimelineToolButton *buttonNext
+ = new TimelineToolButton(new QAction(TimelineIcons::NEXT_KEYFRAME.icon(), tr("Next Frame")),
+ sectionItem);
+ buttonNext->setToolTip("Jump to next frame.");
+
+ connect(buttonPrev, &TimelineToolButton::clicked, item, [item]() {
+ if (item->m_frames.isValid()) {
+ QVector<qreal> positions = getPositions(item->m_frames);
+ std::sort(positions.begin(), positions.end(), std::greater<qreal>());
+ const qreal prev = findPrev(positions, item->currentFrame());
+ item->timelineScene()->commitCurrentFrame(prev);
+ }
+ });
+
+ connect(buttonNext, &TimelineToolButton::clicked, item, [item]() {
+ if (item->m_frames.isValid()) {
+ QVector<qreal> positions = getPositions(item->m_frames);
+ std::sort(positions.begin(), positions.end(), std::less<qreal>());
+ const qreal next = findNext(positions, item->currentFrame());
+ item->timelineScene()->commitCurrentFrame(next);
+ }
+ });
+
+ QIcon autoKeyIcon = TimelineUtils::mergeIcons(TimelineIcons::GLOBAL_RECORD_KEYFRAMES,
+ TimelineIcons::GLOBAL_RECORD_KEYFRAMES_OFF);
+ auto recact = new QAction(autoKeyIcon, tr("Auto Record"));
+ recact->setCheckable(true);
+ recact->setChecked(isRecording);
+
+ auto toggleRecord = [frames](bool check) { frames.toogleRecording(check); };
+ connect(recact, &QAction::toggled, toggleRecord);
+ item->m_recording = new TimelineToolButton(recact, sectionItem);
+ item->m_recording->setToolTip("Per property recording");
+
+ const int buttonsY = (TimelineConstants::sectionHeight - 1 - TimelineConstants::toolButtonSize)
+ / 2;
+ buttonPrev->setPos(2, buttonsY);
+ buttonNext->setPos(buttonPrev->size().width() + TimelineConstants::toolButtonSize + 4, buttonsY);
+ item->m_recording->setPos(buttonNext->geometry().right() + 2, buttonsY);
+
+ QRectF hideToolTipDummy(buttonPrev->geometry().topRight(), buttonNext->geometry().bottomLeft());
+
+ auto *dummy = new QGraphicsRectItem(sectionItem);
+ dummy->setPen(Qt::NoPen);
+ dummy->setRect(hideToolTipDummy);
+ dummy->setToolTip("Frame indicator");
+
+ if (!item->m_frames.isValid())
+ return item;
+
+ QmlObjectNode objectNode(modelnode);
+ if (!objectNode.isValid())
+ return item;
+
+ auto nameOfType = objectNode.modelNode().metaInfo().propertyTypeName(
+ item->m_frames.propertyName());
+ item->m_control = createTimelineControl(nameOfType);
+ if (item->m_control) {
+ item->m_control->setSize((TimelineConstants::sectionWidth / 2.6) - 10,
+ item->size().height() - 2 + 1);
+ item->m_control->connect(item);
+ QGraphicsProxyWidget *proxy = item->timelineScene()->addWidget(item->m_control->widget());
+ proxy->setParentItem(sectionItem);
+ proxy->setPos(qreal(TimelineConstants::sectionWidth) * 2.0 / 3, 0);
+ item->updateTextEdit();
+ }
+
+ updateRecordButtonStatus(item);
+
+ return item;
+}
+
+int TimelinePropertyItem::type() const
+{
+ return Type;
+}
+
+void TimelinePropertyItem::updateData()
+{
+ for (auto child : childItems())
+ delete qgraphicsitem_cast<TimelineMovableAbstractItem *>(child);
+
+ setupKeyframes();
+ updateTextEdit();
+}
+
+void TimelinePropertyItem::updateFrames()
+{
+ for (auto child : (childItems())) {
+ if (auto frameItem = qgraphicsitem_cast<TimelineMovableAbstractItem *>(child))
+ static_cast<TimelineKeyframeItem *>(frameItem)->updateFrame();
+ }
+}
+
+bool TimelinePropertyItem::isSelected() const
+{
+ if (m_frames.isValid() && m_frames.target().isValid())
+ return m_frames.target().isSelected();
+
+ return false;
+}
+
+QString convertVariant(const QVariant &variant)
+{
+ if (variant.userType() == QMetaType::QColor)
+ return variant.toString();
+
+ return QString::number(variant.toFloat(), 'f', 2);
+}
+
+void TimelinePropertyItem::updateTextEdit()
+{
+ if (!m_frames.isValid())
+ return;
+
+ QmlObjectNode objectNode(m_frames.target());
+ if (objectNode.isValid() && m_control)
+ m_control->setControlValue(objectNode.instanceValue(m_frames.propertyName()));
+}
+
+void TimelinePropertyItem::updateTextEdit(QGraphicsItem *item)
+{
+ if (auto timelinePropertyItem = qgraphicsitem_cast<TimelinePropertyItem *>(item))
+ timelinePropertyItem->updateTextEdit();
+}
+
+void TimelinePropertyItem::updateRecordButtonStatus(QGraphicsItem *item)
+{
+ if (auto timelinePropertyItem = qgraphicsitem_cast<TimelinePropertyItem *>(item)) {
+ auto frames = timelinePropertyItem->m_frames;
+ if (frames.isValid()) {
+ timelinePropertyItem->m_recording->setChecked(frames.isRecording());
+ if (frames.timeline().isValid())
+ timelinePropertyItem->m_recording->setDisabled(frames.timeline().isRecording());
+ }
+ }
+}
+
+QmlTimelineKeyframeGroup TimelinePropertyItem::frames() const
+{
+ return m_frames;
+}
+
+QString TimelinePropertyItem::propertyName() const
+{
+ if (m_frames.isValid())
+ return QString::fromUtf8(m_frames.propertyName());
+ return QString();
+}
+
+void TimelinePropertyItem::changePropertyValue(const QVariant &value)
+{
+ Q_ASSERT(m_frames.isValid());
+
+ auto timeline = timelineScene()->currentTimeline();
+
+ if (timelineScene()->toolBar()->recording() || m_recording->isChecked()) {
+ QmlTimelineKeyframeGroup frames = m_frames;
+ auto deferredFunc = [frames, value, timeline]() {
+ auto constFrames = frames;
+ qreal frame = timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal();
+ try {
+ constFrames.setValue(value, frame);
+ } catch (const RewritingException &e) {
+ e.showException();
+ }
+ };
+
+ // QmlTimelineKeyframeGroup::setValue might create a new keyframe.
+ // This might result in a temporal cleanup of the graphicsscene and
+ // therefore a deletion of this property item.
+ // Adding a keyframe to this already deleted item results in a crash.
+ QTimer::singleShot(0, deferredFunc);
+
+ } else {
+ QmlObjectNode objectNode(m_frames.target());
+ objectNode.setVariantProperty(m_frames.propertyName(), value);
+ }
+}
+
+static int devicePixelHeight(const QPixmap &pixmap)
+{
+ return pixmap.height() / pixmap.devicePixelRatioF();
+}
+
+void TimelinePropertyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ painter->save();
+
+ static const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ static const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ static const QColor backgroundColor = Theme::instance()
+ ->qmlDesignerBackgroundColorDarkAlternate();
+
+ static const QPixmap keyframe = TimelineIcons::KEYFRAME.pixmap();
+ static const QPixmap isKeyframe = TimelineIcons::IS_KEYFRAME.pixmap();
+
+ painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColor);
+ painter->fillRect(TimelineConstants::textIndentationProperties - 4,
+ 0,
+ TimelineConstants::sectionWidth - TimelineConstants::textIndentationProperties
+ + 4,
+ size().height(),
+ backgroundColor.darker(110));
+
+ painter->setPen(penColor);
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height());
+
+ drawLine(painter,
+ TimelineConstants::textIndentationProperties - 4,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ painter->setPen(textColor);
+
+ const QFontMetrics metrics(font());
+
+ const QString elidedText = metrics.elidedText(propertyName(),
+ Qt::ElideMiddle,
+ qreal(TimelineConstants::sectionWidth) * 2.0 / 3
+ - TimelineConstants::textIndentationProperties,
+ 0);
+
+ painter->drawText(TimelineConstants::textIndentationProperties, 12, elidedText);
+
+ const bool onKeyFrame = m_frames.isValid() && getPositions(m_frames).contains(currentFrame());
+ painter->drawPixmap(TimelineConstants::toolButtonSize + 3,
+ (TimelineConstants::sectionHeight - 1 - devicePixelHeight(isKeyframe)) / 2,
+ onKeyFrame ? isKeyframe : keyframe);
+ painter->restore();
+}
+
+void TimelinePropertyItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+ if (event->pos().x() < TimelineConstants::toolButtonSize * 2 + 3
+ && event->pos().x() > TimelineConstants::toolButtonSize) {
+ QMenu mainMenu;
+
+ const ModelNode currentFrameNode = getModelNodeForFrame(m_frames, currentFrame());
+
+ QAction *insertAction = mainMenu.addAction(tr("Insert Keyframe"));
+ QObject::connect(insertAction, &QAction::triggered, [this]() {
+ timelineScene()->handleKeyframeInsertion(m_frames.target(), propertyName().toUtf8());
+ });
+
+ QAction *removeAction = mainMenu.addAction(tr("Delete Keyframe"));
+ QObject::connect(removeAction, &QAction::triggered, [this, currentFrameNode]() {
+ timelineScene()->deleteKeyframes({currentFrameNode});
+ });
+
+ QAction *editEasingAction = mainMenu.addAction(tr("Edit Easing Curve..."));
+ QObject::connect(editEasingAction, &QAction::triggered, [this, currentFrameNode]() {
+ setEasingCurve(timelineScene(), {currentFrameNode});
+ });
+
+ QAction *editValueAction = mainMenu.addAction(tr("Edit Value for Keyframe..."));
+ QObject::connect(editValueAction, &QAction::triggered, [this, currentFrameNode]() {
+ editValue(currentFrameNode, propertyName());
+ });
+
+ const bool hasKeyframe = currentFrameNode.isValid();
+
+ insertAction->setEnabled(!hasKeyframe);
+ removeAction->setEnabled(hasKeyframe);
+ editEasingAction->setEnabled(hasKeyframe);
+ editValueAction->setEnabled(hasKeyframe);
+
+ mainMenu.exec(event->screenPos());
+ event->accept();
+ } else if (event->pos().x() > TimelineConstants::toolButtonSize * 3 + 3
+ && event->pos().x() < TimelineConstants::sectionWidth) {
+ QMenu mainMenu;
+ QAction *deleteAction = mainMenu.addAction(tr("Remove Property"));
+
+ QObject::connect(deleteAction, &QAction::triggered, [this]() {
+ auto deleteKeyframeGroup = [this]() { timelineScene()->deleteKeyframeGroup(m_frames); };
+ QTimer::singleShot(0, deleteKeyframeGroup);
+ });
+
+ mainMenu.exec(event->screenPos());
+ event->accept();
+ }
+}
+
+TimelinePropertyItem::TimelinePropertyItem(TimelineSectionItem *parent)
+ : TimelineItem(parent)
+{
+ setPreferredHeight(TimelineConstants::sectionHeight);
+ setMinimumHeight(TimelineConstants::sectionHeight);
+ setMaximumHeight(TimelineConstants::sectionHeight);
+}
+
+void TimelinePropertyItem::setupKeyframes()
+{
+ for (const ModelNode &frame : m_frames.keyframePositions())
+ new TimelineKeyframeItem(this, frame);
+}
+
+qreal TimelinePropertyItem::currentFrame()
+{
+ QmlTimeline timeline = timelineScene()->currentTimeline();
+ if (timeline.isValid())
+ return timeline.currentKeyframe();
+ return 0;
+}
+
+TimelineKeyframeItem::TimelineKeyframeItem(TimelinePropertyItem *parent, const ModelNode &frame)
+ : TimelineMovableAbstractItem(parent)
+ , m_frame(frame)
+
+{
+ setPosition(frame.variantProperty("frame").value().toReal());
+ setCursor(Qt::ClosedHandCursor);
+}
+
+TimelineKeyframeItem::~TimelineKeyframeItem()
+{
+ timelineScene()->selectKeyframes(SelectionMode::Remove, {this});
+}
+
+void TimelineKeyframeItem::updateFrame()
+{
+ if (s_blockUpdates)
+ return;
+
+ QTC_ASSERT(m_frame.isValid(), return );
+ setPosition(m_frame.variantProperty("frame").value().toReal());
+}
+
+void TimelineKeyframeItem::setPosition(qreal position)
+{
+ int offset = (TimelineConstants::sectionHeight - TimelineConstants::keyFrameSize) / 2;
+ const qreal scenePostion = mapFromFrameToScene(position);
+
+ setRect(scenePostion - TimelineConstants::keyFrameSize / 2,
+ offset,
+ TimelineConstants::keyFrameSize,
+ TimelineConstants::keyFrameSize);
+}
+
+void TimelineKeyframeItem::setPositionInteractive(const QPointF &postion)
+{
+ qreal left = postion.x() - qreal(TimelineConstants::keyFrameSize) / qreal(2);
+ setRect(left, rect().y(), rect().width(), rect().height());
+}
+
+void TimelineKeyframeItem::commitPosition(const QPointF &point)
+{
+ setPositionInteractive(point);
+
+ const qreal frame = qRound(mapFromSceneToFrame(rect().center().x()));
+
+ setPosition(frame);
+
+ QTC_ASSERT(m_frame.isValid(), return );
+
+ blockUpdates();
+
+ m_frame.view()->executeInTransaction("TimelineKeyframeItem::commitPosition", [this, frame](){
+ m_frame.variantProperty("frame").setValue(frame);
+ });
+
+ enableUpdates();
+}
+
+TimelineKeyframeItem *TimelineKeyframeItem::asTimelineKeyframeItem()
+{
+ return this;
+}
+
+void TimelineKeyframeItem::blockUpdates()
+{
+ s_blockUpdates = true;
+}
+
+void TimelineKeyframeItem::enableUpdates()
+{
+ s_blockUpdates = false;
+}
+
+bool TimelineKeyframeItem::hasManualBezier() const
+{
+ return m_frame.isValid() && m_frame.hasProperty("easing.bezierCurve");
+}
+
+void TimelineKeyframeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ if (rect().x() < TimelineConstants::sectionWidth - rect().width() / 2)
+ return;
+
+ painter->save();
+
+ Utils::Icon icon([this]() {
+ const bool itemIsSelected = propertyItem()->isSelected();
+ const bool manualBezier = hasManualBezier();
+
+ if (m_highlight && manualBezier) {
+ return TimelineIcons::KEYFRAME_MANUALBEZIER_SELECTED;
+ } else if (m_highlight) {
+ return TimelineIcons::KEYFRAME_LINEAR_SELECTED;
+ } else if (itemIsSelected && manualBezier) {
+ return TimelineIcons::KEYFRAME_MANUALBEZIER_ACTIVE;
+ } else if (itemIsSelected) {
+ return TimelineIcons::KEYFRAME_LINEAR_ACTIVE;
+ } else if (manualBezier) {
+ return TimelineIcons::KEYFRAME_MANUALBEZIER_INACTIVE;
+ }
+
+ return TimelineIcons::KEYFRAME_LINEAR_INACTIVE;
+ }());
+
+ painter->drawPixmap(rect().topLeft() - QPointF(0, 1), icon.pixmap());
+
+ painter->restore();
+}
+
+ModelNode TimelineKeyframeItem::frameNode() const
+{
+ return m_frame;
+}
+
+void TimelineKeyframeItem::setHighlighted(bool b)
+{
+ m_highlight = b;
+ update();
+}
+
+TimelinePropertyItem *TimelineKeyframeItem::propertyItem() const
+{
+ /* The parentItem is always a TimelinePropertyItem. See constructor */
+ return qgraphicsitem_cast<TimelinePropertyItem *>(parentItem());
+}
+
+void TimelineKeyframeItem::scrollOffsetChanged()
+{
+ updateFrame();
+}
+
+void TimelineKeyframeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+ QMenu mainMenu;
+ QAction *removeAction = mainMenu.addAction(tr("Delete Keyframe"));
+ QObject::connect(removeAction, &QAction::triggered, [this]() {
+ timelineScene()->handleKeyframeDeletion();
+ });
+
+ QAction *editEasingAction = mainMenu.addAction(tr("Edit Easing Curve..."));
+ QObject::connect(editEasingAction, &QAction::triggered, [this]() {
+ const QList<ModelNode> keys = Utils::transform(timelineScene()->selectedKeyframes(),
+ &TimelineKeyframeItem::m_frame);
+
+ setEasingCurve(timelineScene(), keys);
+ });
+
+ QAction *editValueAction = mainMenu.addAction(tr("Edit Value for Keyframe..."));
+ QObject::connect(editValueAction, &QAction::triggered, [this]() {
+ editValue(m_frame, propertyItem()->propertyName());
+ });
+
+ mainMenu.exec(event->screenPos());
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h
new file mode 100644
index 0000000000..2b8c00c59b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelinecontrols.h"
+#include "timelinesectionitem.h"
+
+#include <qmltimelinekeyframegroup.h>
+
+#include <modelnode.h>
+
+#include <QGraphicsRectItem>
+
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+
+namespace QmlDesigner {
+
+class TimelinePropertyItem;
+class TimelineGraphicsScene;
+class TimelineToolButton;
+
+class TimelineKeyframeItem : public TimelineMovableAbstractItem
+{
+ Q_DECLARE_TR_FUNCTIONS(TimelineKeyframeItem)
+
+public:
+ explicit TimelineKeyframeItem(TimelinePropertyItem *parent, const ModelNode &frame);
+ ~TimelineKeyframeItem() override;
+
+ static void blockUpdates();
+ static void enableUpdates();
+
+ ModelNode frameNode() const;
+
+ void updateFrame();
+
+ void setHighlighted(bool b);
+
+ void setPosition(qreal position);
+
+ void commitPosition(const QPointF &point) override;
+
+ TimelineKeyframeItem *asTimelineKeyframeItem() override;
+
+protected:
+ bool hasManualBezier() const;
+
+ void scrollOffsetChanged() override;
+
+ void setPositionInteractive(const QPointF &postion) override;
+
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ TimelinePropertyItem *propertyItem() const;
+
+ ModelNode m_frame;
+
+ bool m_highlight = false;
+};
+
+class TimelinePropertyItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TimelineConstants::timelinePropertyItemUserType };
+
+ static TimelinePropertyItem *create(const QmlTimelineKeyframeGroup &frames,
+ TimelineSectionItem *parent = nullptr);
+
+ int type() const override;
+
+ void updateData();
+ void updateFrames();
+ bool isSelected() const;
+
+ static void updateTextEdit(QGraphicsItem *item);
+ static void updateRecordButtonStatus(QGraphicsItem *item);
+
+ QmlTimelineKeyframeGroup frames() const;
+
+ QString propertyName() const;
+
+ void changePropertyValue(const QVariant &value);
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ TimelinePropertyItem(TimelineSectionItem *parent = nullptr);
+
+ void setupKeyframes();
+ qreal currentFrame();
+ void updateTextEdit();
+
+ QmlTimelineKeyframeGroup m_frames;
+ TimelineControl *m_control = nullptr;
+ TimelineToolButton *m_recording = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp
new file mode 100644
index 0000000000..7bd784a7dd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp
@@ -0,0 +1,1062 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinesectionitem.h"
+
+#include "abstractview.h"
+#include "timelineactions.h"
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelineicons.h"
+#include "timelinepropertyitem.h"
+#include "timelinetoolbutton.h"
+#include "timelineutils.h"
+
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <rewritingexception.h>
+
+#include <theme.h>
+
+#include <utils/qtcassert.h>
+
+#include <QAction>
+#include <QColorDialog>
+#include <QComboBox>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QMenu>
+#include <QPainter>
+#include <QToolBar>
+
+#include <QGraphicsView>
+
+#include <QDebug>
+
+#include <cmath>
+
+static int textOffset = 8;
+
+namespace QmlDesigner {
+
+class ClickDummy : public TimelineItem
+{
+public:
+ explicit ClickDummy(TimelineSectionItem *parent)
+ : TimelineItem(parent)
+ {
+ setGeometry(0, 0, TimelineConstants::sectionWidth, TimelineConstants::sectionHeight);
+
+ setZValue(10);
+ setCursor(Qt::ArrowCursor);
+ }
+
+protected:
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+};
+
+TimelineSectionItem::TimelineSectionItem(TimelineItem *parent)
+ : TimelineItem(parent)
+{}
+
+TimelineSectionItem *TimelineSectionItem::create(const QmlTimeline &timeline,
+ const ModelNode &target,
+ TimelineItem *parent)
+{
+ auto item = new TimelineSectionItem(parent);
+
+ if (target.isValid())
+ item->setToolTip(target.id());
+
+ item->m_targetNode = target;
+ item->m_timeline = timeline;
+
+ item->createPropertyItems();
+
+ item->m_dummyItem = new ClickDummy(item);
+ item->m_dummyItem->update();
+
+ item->m_barItem = new TimelineBarItem(item);
+ item->invalidateBar();
+ item->invalidateHeight();
+
+ return item;
+}
+
+void TimelineSectionItem::invalidateBar()
+{
+ qreal min = m_timeline.minActualKeyframe(m_targetNode);
+ qreal max = m_timeline.maxActualKeyframe(m_targetNode);
+
+ const qreal sceneMin = m_barItem->mapFromFrameToScene(min);
+
+ QRectF barRect(sceneMin,
+ 0,
+ (max - min) * m_barItem->rulerScaling(),
+ TimelineConstants::sectionHeight - 1);
+
+ m_barItem->setRect(barRect);
+}
+
+int TimelineSectionItem::type() const
+{
+ return Type;
+}
+
+void TimelineSectionItem::updateData(QGraphicsItem *item)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TimelineSectionItem *>(item))
+ sectionItem->updateData();
+}
+
+void TimelineSectionItem::updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b)
+{
+ if (!target.isValid())
+ return;
+
+ if (auto sectionItem = qgraphicsitem_cast<TimelineSectionItem *>(item)) {
+ if (sectionItem->m_targetNode == target) {
+ sectionItem->updateData();
+ if (b)
+ *b = true;
+ }
+ }
+}
+
+void TimelineSectionItem::updateFramesForTarget(QGraphicsItem *item, const ModelNode &target)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TimelineSectionItem *>(item)) {
+ if (sectionItem->m_targetNode == target)
+ sectionItem->updateFrames();
+ }
+}
+
+void TimelineSectionItem::moveAllFrames(qreal offset)
+{
+ if (m_timeline.isValid())
+ m_timeline.moveAllKeyframes(m_targetNode, offset);
+}
+
+void TimelineSectionItem::scaleAllFrames(qreal scale)
+{
+ if (m_timeline.isValid())
+ m_timeline.scaleAllKeyframes(m_targetNode, scale);
+}
+
+qreal TimelineSectionItem::firstFrame()
+{
+ if (!m_timeline.isValid())
+ return 0;
+
+ return m_timeline.minActualKeyframe(m_targetNode);
+}
+
+AbstractView *TimelineSectionItem::view() const
+{
+ return m_timeline.view();
+}
+
+bool TimelineSectionItem::isSelected() const
+{
+ return m_targetNode.isValid() && m_targetNode.isSelected();
+}
+
+ModelNode TimelineSectionItem::targetNode() const
+{
+ return m_targetNode;
+}
+
+QVector<qreal> TimelineSectionItem::keyframePositions() const
+{
+ QVector<qreal> out;
+ for (auto frame : m_timeline.keyframeGroupsForTarget(m_targetNode))
+ out.append(timelineScene()->keyframePositions(frame));
+
+ return out;
+}
+
+QTransform rotatationTransform(qreal degrees)
+{
+ QTransform transform;
+ transform.rotate(degrees);
+
+ return transform;
+}
+
+QPixmap rotateby90(const QPixmap &pixmap)
+{
+ QImage sourceImage = pixmap.toImage();
+ QImage destImage(pixmap.height(), pixmap.width(), sourceImage.format());
+
+ for (int x = 0; x < pixmap.width(); x++)
+ for (int y = 0; y < pixmap.height(); y++)
+ destImage.setPixel(y, x, sourceImage.pixel(x, y));
+
+ QPixmap result = QPixmap::fromImage(destImage);
+
+ result.setDevicePixelRatio(pixmap.devicePixelRatio());
+
+ return result;
+}
+
+static int devicePixelHeight(const QPixmap &pixmap)
+{
+ return pixmap.height() / pixmap.devicePixelRatioF();
+}
+
+void TimelineSectionItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem * /*option*/,
+ QWidget *)
+{
+ if (m_targetNode.isValid()) {
+ painter->save();
+
+ const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ QColor brushColor = Theme::getColor(Theme::BackgroundColorDark);
+
+ int fillOffset = 0;
+ if (isSelected()) {
+ brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ fillOffset = 1;
+ }
+
+ painter->fillRect(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - fillOffset,
+ brushColor);
+ painter->fillRect(TimelineConstants::sectionWidth,
+ 0,
+ size().width() - TimelineConstants::sectionWidth,
+ size().height(),
+ Theme::instance()->qmlDesignerBackgroundColorDarkAlternate());
+
+ painter->setPen(penColor);
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height() - 1);
+ drawLine(painter,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ static const QPixmap arrow = Theme::getPixmap("down-arrow");
+
+ static const QPixmap arrow90 = rotateby90(arrow);
+
+ const QPixmap rotatedArrow = collapsed() ? arrow90 : arrow;
+
+ const int textOffset = QFontMetrics(font()).ascent()
+ + (TimelineConstants::sectionHeight - QFontMetrics(font()).height())
+ / 2;
+
+ painter->drawPixmap(collapsed() ? 6 : 4,
+ (TimelineConstants::sectionHeight - devicePixelHeight(rotatedArrow)) / 2,
+ rotatedArrow);
+
+ painter->setPen(textColor);
+
+ QFontMetrics fm(painter->font());
+ const QString elidedId = fm.elidedText(m_targetNode.id(),
+ Qt::ElideMiddle,
+ TimelineConstants::sectionWidth
+ - TimelineConstants::textIndentationSections);
+ painter->drawText(TimelineConstants::textIndentationSections, textOffset, elidedId);
+
+ painter->restore();
+ }
+}
+
+void TimelineSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight
+ || event->pos().x() < TimelineConstants::textIndentationSections) {
+ TimelineItem::mouseDoubleClickEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton) {
+ event->accept();
+ toggleCollapsed();
+ }
+}
+
+void TimelineSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mousePressEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton)
+ event->accept();
+}
+
+void TimelineSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mouseReleaseEvent(event);
+ return;
+ }
+
+ if (event->button() != Qt::LeftButton)
+ return;
+
+ event->accept();
+
+ if (event->pos().x() > TimelineConstants::textIndentationSections
+ && event->button() == Qt::LeftButton) {
+ if (m_targetNode.isValid())
+ m_targetNode.view()->setSelectedModelNode(m_targetNode);
+ } else {
+ toggleCollapsed();
+ }
+ update();
+}
+
+void TimelineSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event)
+{
+ TimelineItem::resizeEvent(event);
+
+ for (auto child : propertyItems()) {
+ TimelinePropertyItem *item = static_cast<TimelinePropertyItem *>(child);
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+}
+
+void TimelineSectionItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+ if (event->pos().x() < TimelineConstants::sectionWidth
+ && event->pos().y() < TimelineConstants::sectionHeight) {
+ QMenu mainMenu;
+
+ auto timeline = timelineScene()->currentTimeline();
+
+ QAction *removeAction = mainMenu.addAction(
+ TimelineConstants::timelineDeleteKeyframesDisplayName);
+ QObject::connect(removeAction, &QAction::triggered, [this]() {
+ timelineScene()->deleteAllKeyframesForTarget(m_targetNode);
+ });
+
+ QAction *addKeyframesAction = mainMenu.addAction(
+ TimelineConstants::timelineInsertKeyframesDisplayName);
+ QObject::connect(addKeyframesAction, &QAction::triggered, [this]() {
+ timelineScene()->insertAllKeyframesForTarget(m_targetNode);
+ });
+
+ QAction *copyAction = mainMenu.addAction(
+ TimelineConstants::timelineCopyKeyframesDisplayName);
+ QObject::connect(copyAction, &QAction::triggered, [this]() {
+ timelineScene()->copyAllKeyframesForTarget(m_targetNode);
+ });
+
+ QAction *pasteAction = mainMenu.addAction(
+ TimelineConstants::timelinePasteKeyframesDisplayName);
+ QObject::connect(pasteAction, &QAction::triggered, [this]() {
+ timelineScene()->pasteKeyframesToTarget(m_targetNode);
+ });
+
+ pasteAction->setEnabled(TimelineActions::clipboardContainsKeyframes());
+
+ mainMenu.exec(event->screenPos());
+ event->accept();
+ }
+}
+
+void TimelineSectionItem::updateData()
+{
+ invalidateBar();
+ resize(rulerWidth(), size().height());
+ invalidateProperties();
+ update();
+}
+
+void TimelineSectionItem::updateFrames()
+{
+ invalidateBar();
+ invalidateFrames();
+ update();
+}
+
+void TimelineSectionItem::invalidateHeight()
+{
+ int height = 0;
+ bool visible = true;
+
+ if (collapsed()) {
+ height = TimelineConstants::sectionHeight;
+ visible = false;
+ } else {
+ height = TimelineConstants::sectionHeight
+ + m_timeline.keyframeGroupsForTarget(m_targetNode).count()
+ * TimelineConstants::sectionHeight;
+ visible = true;
+ }
+
+ for (auto child : propertyItems())
+ child->setVisible(visible);
+
+ setPreferredHeight(height);
+ setMinimumHeight(height);
+ setMaximumHeight(height);
+ timelineScene()->activateLayout();
+}
+
+void TimelineSectionItem::invalidateProperties()
+{
+ for (auto child : propertyItems()) {
+ delete child;
+ }
+
+ createPropertyItems();
+
+ for (auto child : propertyItems()) {
+ TimelinePropertyItem *item = static_cast<TimelinePropertyItem *>(child);
+ item->updateData();
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+ invalidateHeight();
+}
+
+void TimelineSectionItem::invalidateFrames()
+{
+ for (auto child : propertyItems()) {
+ TimelinePropertyItem *item = static_cast<TimelinePropertyItem *>(child);
+ item->updateFrames();
+ }
+}
+
+bool TimelineSectionItem::collapsed() const
+{
+ return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded");
+}
+
+void TimelineSectionItem::createPropertyItems()
+{
+ auto framesList = m_timeline.keyframeGroupsForTarget(m_targetNode);
+
+ int yPos = TimelineConstants::sectionHeight;
+ for (const auto &frames : framesList) {
+ auto item = TimelinePropertyItem::create(frames, this);
+ item->setY(yPos);
+ yPos = yPos + TimelineConstants::sectionHeight;
+ }
+}
+
+qreal TimelineSectionItem::rulerWidth() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene())->rulerWidth();
+}
+
+void TimelineSectionItem::toggleCollapsed()
+{
+ QTC_ASSERT(m_targetNode.isValid(), return );
+
+ if (collapsed())
+ m_targetNode.setAuxiliaryData("timeline_expanded", true);
+ else
+ m_targetNode.removeAuxiliaryData("timeline_expanded");
+
+ invalidateHeight();
+}
+
+QList<QGraphicsItem *> TimelineSectionItem::propertyItems() const
+{
+ QList<QGraphicsItem *> list;
+
+ for (auto child : childItems()) {
+ if (m_barItem != child && m_dummyItem != child)
+ list.append(child);
+ }
+
+ return list;
+}
+
+TimelineRulerSectionItem::TimelineRulerSectionItem(TimelineItem *parent)
+ : TimelineItem(parent)
+{
+ setPreferredHeight(TimelineConstants::rulerHeight);
+ setMinimumHeight(TimelineConstants::rulerHeight);
+ setMaximumHeight(TimelineConstants::rulerHeight);
+ setZValue(10);
+}
+
+static void drawCenteredText(QPainter *p, int x, int y, const QString &text)
+{
+ QRect rect(x - 16, y - 4, 32, 8);
+ p->drawText(rect, Qt::AlignCenter, text);
+}
+
+TimelineRulerSectionItem *TimelineRulerSectionItem::create(QGraphicsScene *parentScene,
+ TimelineItem *parent)
+{
+ auto item = new TimelineRulerSectionItem(parent);
+ item->setMaximumHeight(TimelineConstants::rulerHeight);
+
+ auto widget = new QWidget;
+ widget->setFixedWidth(TimelineConstants::sectionWidth);
+
+ auto toolBar = new QToolBar;
+ toolBar->setFixedHeight(TimelineConstants::rulerHeight);
+
+ auto layout = new QHBoxLayout(widget);
+ layout->addWidget(toolBar);
+ layout->setMargin(0);
+
+ layout->addWidget(toolBar);
+ layout->setMargin(0);
+
+ QGraphicsProxyWidget *proxy = parentScene->addWidget(widget);
+ proxy->setParentItem(item);
+
+ return item;
+}
+
+void TimelineRulerSectionItem::invalidateRulerSize(const QmlTimeline &timeline)
+{
+ m_duration = timeline.duration();
+ m_start = timeline.startKeyframe();
+ m_end = timeline.endKeyframe();
+}
+
+void TimelineRulerSectionItem::setRulerScaleFactor(int scaling)
+{
+ qreal blend = qreal(scaling) / 100.0;
+
+ qreal width = size().width() - qreal(TimelineConstants::sectionWidth);
+ qreal duration = rulerDuration();
+
+ qreal offset = duration * 0.1;
+ qreal maxCount = duration + offset;
+ qreal minCount = width
+ / qreal(TimelineConstants::keyFrameSize
+ + 2 * TimelineConstants::keyFrameMargin);
+
+ qreal count = maxCount < minCount ? maxCount : TimelineUtils::lerp(blend, minCount, maxCount);
+
+ if (count > std::numeric_limits<qreal>::min() && count <= maxCount)
+ m_scaling = width / count;
+ else
+ m_scaling = 1.0;
+
+ update();
+}
+
+int TimelineRulerSectionItem::getRulerScaleFactor() const
+{
+ qreal width = size().width() - qreal(TimelineConstants::sectionWidth);
+ qreal duration = rulerDuration();
+
+ qreal offset = duration * 0.1;
+ qreal maxCount = duration + offset;
+ qreal minCount = width
+ / qreal(TimelineConstants::keyFrameSize
+ + 2 * TimelineConstants::keyFrameMargin);
+
+ if (maxCount < minCount)
+ return -1;
+
+ qreal rcount = width / m_scaling;
+ qreal rblend = TimelineUtils::reverseLerp(rcount, minCount, maxCount);
+
+ int rfactor = std::round(rblend * 100);
+ return TimelineUtils::clamp(rfactor, 0, 100);
+}
+
+qreal TimelineRulerSectionItem::rulerScaling() const
+{
+ return m_scaling;
+}
+
+qreal TimelineRulerSectionItem::rulerDuration() const
+{
+ return m_duration;
+}
+
+qreal TimelineRulerSectionItem::durationViewportLength() const
+{
+ return m_duration * m_scaling;
+}
+
+qreal TimelineRulerSectionItem::startFrame() const
+{
+ return m_start;
+}
+
+qreal TimelineRulerSectionItem::endFrame() const
+{
+ return m_end;
+}
+
+void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ static const QColor backgroundColor = Theme::instance()
+ ->qmlDesignerBackgroundColorDarkAlternate();
+ static const QColor penColor = Theme::getColor(Theme::PanelTextColorLight);
+ static const QColor highlightColor = Theme::instance()->Theme::qmlDesignerButtonColor();
+ static const QColor handleColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+
+ painter->save();
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->translate(-timelineScene()->scrollOffset(), 0);
+ painter->fillRect(TimelineConstants::sectionWidth,
+ 0,
+ size().width() - TimelineConstants::sectionWidth,
+ size().height(),
+ backgroundColor);
+
+ painter->translate(TimelineConstants::timelineLeftOffset, 0);
+
+ const QRectF rangeRect(TimelineConstants::sectionWidth,
+ 0,
+ m_duration * m_scaling,
+ size().height());
+
+ const qreal radius = 5;
+ const qreal handleWidth = TimelineConstants::timelineBounds * 2;
+ QRectF boundsRect(0, rangeRect.y(), handleWidth, rangeRect.height());
+
+ boundsRect.moveRight(rangeRect.left() + TimelineConstants::timelineBounds);
+
+ QPainterPath leftBoundsPath;
+ leftBoundsPath.addRoundedRect(boundsRect, radius, radius);
+ painter->fillPath(leftBoundsPath, handleColor);
+
+ boundsRect.moveLeft(rangeRect.right() - TimelineConstants::timelineBounds);
+
+ QPainterPath rightBoundsPath;
+ rightBoundsPath.addRoundedRect(boundsRect, radius, radius);
+ painter->fillPath(rightBoundsPath, handleColor);
+
+ painter->fillRect(rangeRect, highlightColor);
+
+ painter->setPen(penColor);
+
+ const int height = size().height() - 1;
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth + timelineScene()->scrollOffset()
+ - TimelineConstants::timelineLeftOffset,
+ height,
+ size().width() + timelineScene()->scrollOffset(),
+ height);
+
+ QFont font = painter->font();
+ font.setPixelSize(8);
+ painter->setFont(font);
+
+ paintTicks(painter);
+
+ painter->restore();
+
+ painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColor);
+ painter->restore();
+}
+
+void TimelineRulerSectionItem::paintTicks(QPainter *painter)
+{
+ const int totalWidth = size().width() / m_scaling + timelineScene()->scrollOffset() / m_scaling;
+
+ QFontMetrics fm(painter->font());
+
+ int minSpacingText = fm.horizontalAdvance(QString("X%1X").arg(rulerDuration()));
+ int minSpacingLine = 5;
+
+ int deltaText = 0;
+ int deltaLine = 0;
+
+ // Marks possibly at [1, 5, 10, 50, 100, ...]
+ int spacing = 1;
+ bool toggle = true;
+ while (deltaText == 0) {
+ int distance = spacing * m_scaling;
+
+ if (distance > minSpacingLine && deltaLine == 0)
+ deltaLine = spacing;
+
+ if (distance > minSpacingText) {
+ deltaText = spacing;
+ break;
+ }
+
+ if (toggle) {
+ spacing *= 5;
+ toggle = false;
+ } else {
+ spacing *= 2;
+ toggle = true;
+ }
+ }
+
+ int height = size().height();
+
+ for (int i = timelineScene()->scrollOffset() / m_scaling; i < totalWidth; ++i) {
+ if ((i % deltaText) == 0) {
+ drawCenteredText(painter,
+ TimelineConstants::sectionWidth + i * m_scaling,
+ textOffset,
+ QString::number(m_start + i));
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth + i * m_scaling,
+ height - 2,
+ TimelineConstants::sectionWidth + i * m_scaling,
+ height * 0.6);
+
+ } else if ((i % deltaLine) == 0) {
+ drawLine(painter,
+ TimelineConstants::sectionWidth + i * m_scaling,
+ height - 2,
+ TimelineConstants::sectionWidth + i * m_scaling,
+ height * 0.75);
+ }
+ }
+}
+
+void TimelineRulerSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ TimelineItem::mousePressEvent(event);
+ emit rulerClicked(event->pos());
+}
+
+void TimelineRulerSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event)
+{
+ QGraphicsWidget::resizeEvent(event);
+
+ auto factor = getRulerScaleFactor();
+
+ if (factor < 0) {
+ if (event->oldSize().width() < event->newSize().width())
+ factor = 0;
+ else
+ factor = 100;
+ }
+
+ emit scaleFactorChanged(factor);
+}
+
+void TimelineRulerSectionItem::setSizeHints(int width)
+{
+ const int rulerWidth = width;
+ setPreferredWidth(rulerWidth);
+ setMinimumWidth(rulerWidth);
+ setMaximumWidth(rulerWidth);
+}
+
+TimelineBarItem::TimelineBarItem(TimelineSectionItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ setAcceptHoverEvents(true);
+ setPen(Qt::NoPen);
+}
+
+void TimelineBarItem::itemMoved(const QPointF &start, const QPointF &end)
+{
+ if (isActiveHandle(Location::Undefined))
+ dragInit(rect(), start);
+
+ const qreal min = qreal(TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ - scrollOffset());
+ const qreal max = qreal(timelineScene()->rulerWidth() - TimelineConstants::sectionWidth
+ + rect().width());
+
+ if (isActiveHandle(Location::Center))
+ dragCenter(rect(), end, min, max);
+ else
+ dragHandle(rect(), end, min, max);
+
+ timelineScene()->statusBarMessageChanged(
+ tr("Range from %1 to %2")
+ .arg(qRound(mapFromSceneToFrame(rect().x())))
+ .arg(qRound(mapFromSceneToFrame(rect().width() + rect().x()))));
+}
+
+void TimelineBarItem::commitPosition(const QPointF & /*point*/)
+{
+ if (sectionItem()->view()) {
+ if (m_handle != Location::Undefined) {
+ sectionItem()->view()->executeInTransaction("TimelineBarItem::commitPosition", [this](){
+ qreal scaleFactor = rect().width() / m_oldRect.width();
+
+ qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling();
+ qreal supposedFirstFrame = qRound(sectionItem()->firstFrame() + moved);
+
+ sectionItem()->scaleAllFrames(scaleFactor);
+ sectionItem()->moveAllFrames(supposedFirstFrame - sectionItem()->firstFrame());
+ });
+ }
+ }
+
+ m_handle = Location::Undefined;
+ m_bounds = Location::Undefined;
+ m_pivot = 0.0;
+ m_oldRect = QRectF();
+}
+
+void TimelineBarItem::scrollOffsetChanged()
+{
+ sectionItem()->invalidateBar();
+}
+
+void TimelineBarItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+
+ QColor brushColorSelected = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ QColor brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor).darker(120);
+ const QColor indicatorColor = Theme::getColor(Theme::PanelTextColorLight);
+
+ ModelNode target = sectionItem()->targetNode();
+ if (target.isValid()) {
+ QColor overrideColor = target.auxiliaryData(TimelineConstants::C_BAR_ITEM_OVERRIDE).value<QColor>();
+ if (overrideColor.isValid()) {
+ brushColorSelected = overrideColor;
+ brushColor = brushColorSelected.darker(120);
+ }
+ }
+
+ const QRectF itemRect = rect();
+
+ painter->save();
+ painter->setClipRect(TimelineConstants::sectionWidth,
+ 0,
+ itemRect.width() + itemRect.x(),
+ itemRect.height());
+
+ if (sectionItem()->isSelected())
+ painter->fillRect(itemRect, brushColorSelected);
+ else
+ painter->fillRect(itemRect, brushColor);
+
+ auto positions = sectionItem()->keyframePositions();
+ std::sort(positions.begin(), positions.end());
+
+ auto fcompare = [](auto v1, auto v2) { return qFuzzyCompare(v1, v2); };
+ auto unique = std::unique(positions.begin(), positions.end(), fcompare);
+ positions.erase(unique, positions.end());
+
+ painter->setPen(indicatorColor);
+ auto margin = itemRect.height() * 0.166;
+ auto p1y = itemRect.top() + margin;
+ auto p2y = itemRect.bottom() - margin;
+ for (auto pos : positions) {
+ auto px = mapFromFrameToScene(pos) + 0.5;
+ painter->drawLine(QLineF(px, p1y, px, p2y));
+ }
+ painter->restore();
+}
+
+void TimelineBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
+{
+ const auto p = event->pos();
+
+ QRectF left, right;
+ if (handleRects(rect(), left, right)) {
+ if (left.contains(p) || right.contains(p)) {
+ if (cursor().shape() != Qt::SizeHorCursor)
+ setCursor(QCursor(Qt::SizeHorCursor));
+ } else if (rect().contains(p)) {
+ if (cursor().shape() != Qt::ClosedHandCursor)
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+ } else {
+ if (rect().contains(p))
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+}
+
+void TimelineBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
+{
+ QMenu menu;
+ QAction* overrideColor = menu.addAction(tr("Override Color"));
+
+ auto setColor = [this] () {
+ ModelNode target = sectionItem()->targetNode();
+ if (target.isValid()) {
+ QColor current = target.auxiliaryData(TimelineConstants::C_BAR_ITEM_OVERRIDE).value<QColor>();
+ QColor color = QColorDialog::getColor(current, nullptr);
+ if (color.isValid())
+ target.setAuxiliaryData(TimelineConstants::C_BAR_ITEM_OVERRIDE, color);
+ }
+ };
+
+ QObject::connect(overrideColor, &QAction::triggered, setColor);
+
+ QAction* resetColor = menu.addAction(tr("Reset Color"));
+ auto reset = [this]() {
+ ModelNode target = sectionItem()->targetNode();
+ if (target.isValid())
+ target.removeAuxiliaryData(TimelineConstants::C_BAR_ITEM_OVERRIDE);
+ };
+ QObject::connect(resetColor, &QAction::triggered, reset);
+
+ menu.exec(event->screenPos());
+}
+
+TimelineSectionItem *TimelineBarItem::sectionItem() const
+{
+ /* The parentItem is always a TimelineSectionItem. See constructor */
+ return qgraphicsitem_cast<TimelineSectionItem *>(parentItem());
+}
+
+void TimelineBarItem::dragInit(const QRectF &rect, const QPointF &pos)
+{
+ QRectF left, right;
+ m_oldRect = rect;
+ if (handleRects(rect, left, right)) {
+ if (left.contains(pos)) {
+ m_handle = Location::Left;
+ m_pivot = pos.x() - left.topLeft().x();
+ } else if (right.contains(pos)) {
+ m_handle = Location::Right;
+ m_pivot = pos.x() - right.topRight().x();
+ } else if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+
+ } else {
+ if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+ }
+}
+
+void TimelineBarItem::dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ if (validateBounds(pos.x() - rect.topLeft().x())) {
+ rect.moveLeft(pos.x() - m_pivot);
+ if (rect.topLeft().x() < min) {
+ rect.moveLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.topRight().x() > max) {
+ rect.moveRight(max);
+ setOutOfBounds(Location::Right);
+ }
+ setRect(rect);
+ }
+}
+
+void TimelineBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ QRectF left, right;
+ handleRects(rect, left, right);
+
+ if (isActiveHandle(Location::Left)) {
+ if (validateBounds(pos.x() - left.topLeft().x())) {
+ rect.setLeft(pos.x() - m_pivot);
+ if (rect.left() < min) {
+ rect.setLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.left() >= rect.right() - minimumBarWidth)
+ rect.setLeft(rect.right() - minimumBarWidth);
+
+ setRect(rect);
+ }
+ } else if (isActiveHandle(Location::Right)) {
+ if (validateBounds(pos.x() - right.topRight().x())) {
+ rect.setRight(pos.x() - m_pivot);
+ if (rect.right() > max) {
+ rect.setRight(max);
+ setOutOfBounds(Location::Right);
+ } else if (rect.right() <= rect.left() + minimumBarWidth)
+ rect.setRight(rect.left() + minimumBarWidth);
+
+ setRect(rect);
+ }
+ }
+}
+
+bool TimelineBarItem::handleRects(const QRectF &rect, QRectF &left, QRectF &right) const
+{
+ if (rect.width() < minimumBarWidth)
+ return false;
+
+ const qreal handleSize = rect.height();
+
+ auto handleRect = QRectF(0, 0, handleSize, handleSize);
+ handleRect.moveCenter(rect.center());
+
+ handleRect.moveLeft(rect.left());
+ left = handleRect;
+
+ handleRect.moveRight(rect.right());
+ right = handleRect;
+
+ return true;
+}
+
+bool TimelineBarItem::isActiveHandle(Location location) const
+{
+ return m_handle == location;
+}
+
+void TimelineBarItem::setOutOfBounds(Location location)
+{
+ m_bounds = location;
+}
+
+bool TimelineBarItem::validateBounds(qreal distance)
+{
+ if (m_bounds == Location::Left) {
+ if (distance > m_pivot)
+ m_bounds = Location::Center;
+ return false;
+
+ } else if (m_bounds == Location::Right) {
+ if (distance < m_pivot)
+ m_bounds = Location::Center;
+ return false;
+ }
+ return true;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h
new file mode 100644
index 0000000000..26db04f757
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h
@@ -0,0 +1,192 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineitem.h"
+#include "timelinemovableabstractitem.h"
+
+#include <modelnode.h>
+#include <qmltimeline.h>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QPainter)
+
+namespace QmlDesigner {
+
+class TimelineSectionItem;
+
+class TimelineBarItem : public TimelineMovableAbstractItem
+{
+ Q_DECLARE_TR_FUNCTIONS(TimelineBarItem)
+
+ enum class Location { Undefined, Center, Left, Right };
+
+public:
+ explicit TimelineBarItem(TimelineSectionItem *parent);
+
+ void itemMoved(const QPointF &start, const QPointF &end) override;
+ void commitPosition(const QPointF &point) override;
+
+protected:
+ void scrollOffsetChanged() override;
+ void paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent * event) override;
+private:
+ TimelineSectionItem *sectionItem() const;
+
+ void dragInit(const QRectF &rect, const QPointF &pos);
+ void dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ void dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ bool handleRects(const QRectF &rect, QRectF &left, QRectF &right) const;
+ bool isActiveHandle(Location location) const;
+
+ void setOutOfBounds(Location location);
+ bool validateBounds(qreal pivot);
+
+private:
+ Location m_handle = Location::Undefined;
+
+ Location m_bounds = Location::Undefined;
+
+ qreal m_pivot = 0.0;
+
+ QRectF m_oldRect;
+
+ static constexpr qreal minimumBarWidth = 2.0
+ * static_cast<qreal>(TimelineConstants::sectionHeight);
+};
+
+class TimelineSectionItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TimelineConstants::timelineSectionItemUserType };
+
+ static TimelineSectionItem *create(const QmlTimeline &timelineScene,
+ const ModelNode &target,
+ TimelineItem *parent);
+
+ void invalidateBar();
+
+ int type() const override;
+
+ static void updateData(QGraphicsItem *item);
+ static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b);
+ static void updateFramesForTarget(QGraphicsItem *item, const ModelNode &target);
+
+ void moveAllFrames(qreal offset);
+ void scaleAllFrames(qreal scale);
+ qreal firstFrame();
+ AbstractView *view() const;
+ bool isSelected() const;
+
+ ModelNode targetNode() const;
+ QVector<qreal> keyframePositions() const;
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void resizeEvent(QGraphicsSceneResizeEvent *event) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ void updateData();
+ void updateFrames();
+ void invalidateHeight();
+ void invalidateProperties();
+ void invalidateFrames();
+ bool collapsed() const;
+ void createPropertyItems();
+ qreal rulerWidth() const;
+ void toggleCollapsed();
+ QList<QGraphicsItem *> propertyItems() const;
+
+ TimelineSectionItem(TimelineItem *parent = nullptr);
+ ModelNode m_targetNode;
+ QmlTimeline m_timeline;
+
+ TimelineBarItem *m_barItem;
+ TimelineItem *m_dummyItem;
+};
+
+class TimelineRulerSectionItem : public TimelineItem
+{
+ Q_OBJECT
+
+signals:
+ void rulerClicked(const QPointF &pos);
+
+ void scaleFactorChanged(int scale);
+
+public:
+ static TimelineRulerSectionItem *create(QGraphicsScene *parentScene, TimelineItem *parent);
+
+ void invalidateRulerSize(const QmlTimeline &timeline);
+
+ void setRulerScaleFactor(int scaling);
+
+ int getRulerScaleFactor() const;
+
+ qreal rulerScaling() const;
+ qreal rulerDuration() const;
+ qreal durationViewportLength() const;
+ qreal startFrame() const;
+ qreal endFrame() const;
+
+ QComboBox *comboBox() const;
+
+ void setSizeHints(int width);
+
+signals:
+ void addTimelineClicked();
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void resizeEvent(QGraphicsSceneResizeEvent *event) override;
+
+private:
+ TimelineRulerSectionItem(TimelineItem *parent = nullptr);
+
+ void paintTicks(QPainter *painter);
+
+ QComboBox *m_comboBox = nullptr;
+
+ qreal m_duration = 0;
+ qreal m_start = 0;
+ qreal m_end = 0;
+ qreal m_scaling = 1;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp
new file mode 100644
index 0000000000..53367c9b67
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineselectiontool.h"
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinepropertyitem.h"
+#include "timelinetooldelegate.h"
+
+#include <QGraphicsRectItem>
+#include <QGraphicsSceneMouseEvent>
+#include <QPen>
+
+namespace QmlDesigner {
+
+TimelineSelectionTool::TimelineSelectionTool(TimelineGraphicsScene *scene,
+ TimelineToolDelegate *delegate)
+ : TimelineAbstractTool(scene, delegate)
+ , m_selectionRect(new QGraphicsRectItem)
+{
+ scene->addItem(m_selectionRect);
+ QPen pen(Qt::black);
+ pen.setJoinStyle(Qt::MiterJoin);
+ pen.setCosmetic(true);
+ m_selectionRect->setPen(pen);
+ m_selectionRect->setBrush(QColor(128, 128, 128, 50));
+ m_selectionRect->setZValue(100);
+ m_selectionRect->hide();
+}
+
+TimelineSelectionTool::~TimelineSelectionTool() = default;
+
+SelectionMode TimelineSelectionTool::selectionMode(QGraphicsSceneMouseEvent *event)
+{
+ if (event->modifiers().testFlag(Qt::ControlModifier)) {
+ if (event->modifiers().testFlag(Qt::ShiftModifier))
+ return SelectionMode::Add;
+ else
+ return SelectionMode::Toggle;
+ }
+
+ return SelectionMode::New;
+}
+
+void TimelineSelectionTool::mousePressEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+
+ if (event->buttons() == Qt::LeftButton && selectionMode(event) == SelectionMode::New)
+ deselect();
+}
+
+void TimelineSelectionTool::mouseMoveEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+
+ if (event->buttons() == Qt::LeftButton) {
+ auto endPoint = event->scenePos();
+ if (endPoint.x() < 0)
+ endPoint.rx() = 0;
+ if (endPoint.y() < 0)
+ endPoint.ry() = 0;
+ m_selectionRect->setRect(QRectF(startPosition(), endPoint).normalized());
+ m_selectionRect->show();
+
+ aboutToSelect(selectionMode(event),
+ scene()->items(m_selectionRect->rect(), Qt::ContainsItemShape));
+ }
+}
+
+void TimelineSelectionTool::mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+
+ commitSelection(selectionMode(event));
+
+ reset();
+}
+
+void TimelineSelectionTool::mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(item);
+ Q_UNUSED(event);
+
+ reset();
+}
+
+void TimelineSelectionTool::keyPressEvent(QKeyEvent *keyEvent)
+{
+ Q_UNUSED(keyEvent);
+}
+
+void TimelineSelectionTool::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ Q_UNUSED(keyEvent);
+}
+
+void TimelineSelectionTool::deselect()
+{
+ resetHighlights();
+ scene()->clearSelection();
+ delegate()->clearSelection();
+}
+
+void TimelineSelectionTool::reset()
+{
+ m_selectionRect->hide();
+ m_selectionRect->setRect(0, 0, 0, 0);
+ resetHighlights();
+}
+
+void TimelineSelectionTool::resetHighlights()
+{
+ for (auto *keyframe : m_aboutToSelectBuffer)
+ if (scene()->isKeyframeSelected(keyframe))
+ keyframe->setHighlighted(true);
+ else
+ keyframe->setHighlighted(false);
+
+ m_aboutToSelectBuffer.clear();
+}
+
+void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsItem *> items)
+{
+ resetHighlights();
+
+ for (auto *item : items) {
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) {
+ if (mode == SelectionMode::Remove)
+ keyframe->setHighlighted(false);
+ else if (mode == SelectionMode::Toggle)
+ if (scene()->isKeyframeSelected(keyframe))
+ keyframe->setHighlighted(false);
+ else
+ keyframe->setHighlighted(true);
+ else
+ keyframe->setHighlighted(true);
+
+ m_aboutToSelectBuffer << keyframe;
+ }
+ }
+}
+
+void TimelineSelectionTool::commitSelection(SelectionMode mode)
+{
+ scene()->selectKeyframes(mode, m_aboutToSelectBuffer);
+ m_aboutToSelectBuffer.clear();
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h
new file mode 100644
index 0000000000..3485e087ec
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineabstracttool.h"
+
+#include <QList>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsItem)
+QT_FORWARD_DECLARE_CLASS(QGraphicsRectItem)
+
+namespace QmlDesigner {
+
+class TimelineToolDelegate;
+
+class TimelineKeyframeItem;
+
+class TimelineGraphicsScene;
+
+enum class SelectionMode { New, Add, Remove, Toggle };
+
+class TimelineSelectionTool : public TimelineAbstractTool
+{
+public:
+ explicit TimelineSelectionTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+
+ ~TimelineSelectionTool() override;
+
+ static SelectionMode selectionMode(QGraphicsSceneMouseEvent *event);
+
+ void mousePressEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+
+ void mouseMoveEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) override;
+
+ void mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+
+ void mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event) override;
+
+ void keyPressEvent(QKeyEvent *keyEvent) override;
+
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+private:
+ void deselect();
+
+ void reset();
+
+ void resetHighlights();
+
+ void aboutToSelect(SelectionMode mode, QList<QGraphicsItem *> items);
+
+ void commitSelection(SelectionMode mode);
+
+private:
+ QGraphicsRectItem *m_selectionRect;
+
+ QList<TimelineKeyframeItem *> m_aboutToSelectBuffer;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.cpp
new file mode 100644
index 0000000000..960c409553
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.cpp
@@ -0,0 +1,272 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinesettingsdialog.h"
+#include "ui_timelinesettingsdialog.h"
+
+#include "timelineanimationform.h"
+#include "timelineform.h"
+#include "timelineicons.h"
+#include "timelinesettingsmodel.h"
+#include "timelineview.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QKeyEvent>
+#include <QToolBar>
+
+namespace QmlDesigner {
+
+static void deleteAllTabs(QTabWidget *tabWidget)
+{
+ while (tabWidget->count() > 0) {
+ QWidget *w = tabWidget->widget(0);
+ tabWidget->removeTab(0);
+ delete w;
+ }
+}
+
+static ModelNode getAnimationFromTabWidget(QTabWidget *tabWidget)
+{
+ QWidget *w = tabWidget->currentWidget();
+ if (w)
+ return qobject_cast<TimelineAnimationForm *>(w)->animation();
+ return ModelNode();
+}
+
+static QmlTimeline getTimelineFromTabWidget(QTabWidget *tabWidget)
+{
+ QWidget *w = tabWidget->currentWidget();
+ if (w)
+ return qobject_cast<TimelineForm *>(w)->timeline();
+ return QmlTimeline();
+}
+
+static void setTabForTimeline(QTabWidget *tabWidget, const QmlTimeline &timeline)
+{
+ for (int i = 0; i < tabWidget->count(); ++i) {
+ QWidget *w = tabWidget->widget(i);
+ if (qobject_cast<TimelineForm *>(w)->timeline() == timeline) {
+ tabWidget->setCurrentIndex(i);
+ return;
+ }
+ }
+}
+
+static void setTabForAnimation(QTabWidget *tabWidget, const ModelNode &animation)
+{
+ for (int i = 0; i < tabWidget->count(); ++i) {
+ QWidget *w = tabWidget->widget(i);
+ if (qobject_cast<TimelineAnimationForm *>(w)->animation() == animation) {
+ tabWidget->setCurrentIndex(i);
+ return;
+ }
+ }
+}
+
+TimelineSettingsDialog::TimelineSettingsDialog(QWidget *parent, TimelineView *view)
+ : QDialog(parent)
+ , ui(new Ui::TimelineSettingsDialog)
+ , m_timelineView(view)
+{
+ m_timelineSettingsModel = new TimelineSettingsModel(this, view);
+
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ ui->setupUi(this);
+
+ auto *timelineCornerWidget = new QToolBar;
+
+ auto *timelineAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), tr("Add Timeline"));
+ auto *timelineRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
+ tr("Remove Timeline"));
+
+ connect(timelineAddAction, &QAction::triggered, this, [this]() {
+ setupTimelines(m_timelineView->addNewTimeline());
+ });
+
+ connect(timelineRemoveAction, &QAction::triggered, this, [this]() {
+ QmlTimeline timeline = getTimelineFromTabWidget(ui->timelineTab);
+ if (timeline.isValid()) {
+ timeline.destroy();
+ setupTimelines(QmlTimeline());
+ }
+ });
+
+ timelineCornerWidget->addAction(timelineAddAction);
+ timelineCornerWidget->addAction(timelineRemoveAction);
+
+ ui->timelineTab->setCornerWidget(timelineCornerWidget, Qt::TopRightCorner);
+
+ auto *animationCornerWidget = new QToolBar;
+
+ auto *animationAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), tr("Add Animation"));
+ auto *animationRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
+ tr("Remove Animation"));
+
+ animationCornerWidget->addAction(animationAddAction);
+ animationCornerWidget->addAction(animationRemoveAction);
+
+ connect(animationAddAction, &QAction::triggered, this, [this]() {
+ setupAnimations(m_timelineView->addAnimation(m_currentTimeline));
+ });
+
+ connect(animationRemoveAction, &QAction::triggered, this, [this]() {
+ ModelNode node = getAnimationFromTabWidget(ui->animationTab);
+ if (node.isValid()) {
+ node.destroy();
+ setupAnimations(m_currentTimeline);
+ }
+ });
+
+ ui->animationTab->setCornerWidget(animationCornerWidget, Qt::TopRightCorner);
+ ui->buttonBox->clearFocus();
+
+ setupTimelines(QmlTimeline());
+ setupAnimations(m_currentTimeline);
+
+ connect(ui->timelineTab, &QTabWidget::currentChanged, this, [this]() {
+ m_currentTimeline = getTimelineFromTabWidget(ui->timelineTab);
+ setupAnimations(m_currentTimeline);
+ });
+ setupTableView();
+}
+
+void TimelineSettingsDialog::setCurrentTimeline(const QmlTimeline &timeline)
+{
+ m_currentTimeline = timeline;
+ setTabForTimeline(ui->timelineTab, m_currentTimeline);
+}
+
+TimelineSettingsDialog::~TimelineSettingsDialog()
+{
+ delete ui;
+}
+
+void TimelineSettingsDialog::keyPressEvent(QKeyEvent *event)
+{
+ switch (event->key()) {
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ /* ignore */
+ break;
+
+ default:
+ QDialog::keyPressEvent(event);
+ }
+}
+
+void TimelineSettingsDialog::setupTableView()
+{
+ ui->tableView->setModel(m_timelineSettingsModel);
+ m_timelineSettingsModel->setupDelegates(ui->tableView);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableView->verticalHeader()->hide();
+ ui->tableView->setSelectionMode(QAbstractItemView::NoSelection);
+ m_timelineSettingsModel->resetModel();
+}
+
+void TimelineSettingsDialog::setupTimelines(const QmlTimeline &timeline)
+{
+ deleteAllTabs(ui->timelineTab);
+
+ const QList<QmlTimeline> &timelines = m_timelineView->getTimelines();
+
+ if (timelines.isEmpty()) {
+ m_currentTimeline = QmlTimeline();
+ auto timelineForm = new TimelineForm(this);
+ timelineForm->setDisabled(true);
+ ui->timelineTab->addTab(timelineForm, tr("No Timeline"));
+ return;
+ }
+
+ for (const auto &timeline : timelines)
+ addTimelineTab(timeline);
+
+ if (timeline.isValid()) {
+ m_currentTimeline = timeline;
+ } else {
+ m_currentTimeline = timelines.constFirst();
+ }
+
+ setTabForTimeline(ui->timelineTab, m_currentTimeline);
+ setupAnimations(m_currentTimeline);
+ m_timelineSettingsModel->resetModel();
+}
+
+void TimelineSettingsDialog::setupAnimations(const ModelNode &animation)
+{
+ deleteAllTabs(ui->animationTab);
+
+ const QList<ModelNode> animations = m_timelineView->getAnimations(m_currentTimeline);
+
+ for (const auto &animation : animations)
+ addAnimationTab(animation);
+
+ if (animations.isEmpty()) {
+ auto animationForm = new TimelineAnimationForm(this);
+ animationForm->setDisabled(true);
+ ui->animationTab->addTab(animationForm, tr("No Animation"));
+ if (currentTimelineForm())
+ currentTimelineForm()->setHasAnimation(false);
+ } else {
+ if (currentTimelineForm())
+ currentTimelineForm()->setHasAnimation(true);
+ }
+
+ if (animation.isValid())
+ setTabForAnimation(ui->animationTab, animation);
+ m_timelineSettingsModel->resetModel();
+}
+
+void TimelineSettingsDialog::addTimelineTab(const QmlTimeline &node)
+{
+ auto timelineForm = new TimelineForm(this);
+ ui->timelineTab->addTab(timelineForm, node.modelNode().displayName());
+ timelineForm->setTimeline(node);
+ setupAnimations(ModelNode());
+}
+
+void TimelineSettingsDialog::addAnimationTab(const ModelNode &node)
+{
+ auto timelineAnimationForm = new TimelineAnimationForm(this);
+ ui->animationTab->addTab(timelineAnimationForm, node.displayName());
+ timelineAnimationForm->setup(node);
+}
+
+TimelineForm *TimelineSettingsDialog::currentTimelineForm() const
+{
+ return qobject_cast<TimelineForm *>(ui->timelineTab->currentWidget());
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.h
new file mode 100644
index 0000000000..da4ddac4f6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QDialog>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+class TimelineForm;
+class TimelineAnimationForm;
+class TimelineView;
+class TimelineSettingsModel;
+
+namespace Ui {
+class TimelineSettingsDialog;
+}
+
+class TimelineSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineSettingsDialog(QWidget *parent, TimelineView *view);
+ void setCurrentTimeline(const QmlTimeline &timeline);
+ ~TimelineSettingsDialog() override;
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void setupTableView();
+ void setupTimelines(const QmlTimeline &node);
+ void setupAnimations(const ModelNode &node);
+
+ void addTimelineTab(const QmlTimeline &node);
+ void addAnimationTab(const ModelNode &node);
+
+ TimelineForm *currentTimelineForm() const;
+
+ Ui::TimelineSettingsDialog *ui;
+
+ TimelineView *m_timelineView;
+ QmlTimeline m_currentTimeline;
+ TimelineSettingsModel *m_timelineSettingsModel;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.ui b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.ui
new file mode 100644
index 0000000000..f3dfa6f094
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsdialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TimelineSettingsDialog</class>
+ <widget class="QDialog" name="QmlDesigner::TimelineSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>519</width>
+ <height>582</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Timeline Settings</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="timelineTab">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="animationTab">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTableView" name="tableView"/>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QmlDesigner::TimelineSettingsDialog</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>QmlDesigner::TimelineSettingsDialog</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/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp
new file mode 100644
index 0000000000..f75d129983
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp
@@ -0,0 +1,484 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinesettingsmodel.h"
+
+#include "timelineview.h"
+
+#include <modelnode.h>
+#include <variantproperty.h>
+#include <qmlitemnode.h>
+
+#include <utils/qtcassert.h>
+
+#include <QComboBox>
+#include <QItemEditorFactory>
+#include <QMessageBox>
+#include <QStyledItemDelegate>
+#include <QTimer>
+
+namespace QmlDesigner {
+
+class CustomDelegate : public QStyledItemDelegate
+{
+public:
+ explicit CustomDelegate(QWidget *parent = nullptr);
+ void paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+CustomDelegate::CustomDelegate(QWidget *parent)
+ : QStyledItemDelegate(parent)
+{}
+
+void CustomDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItem opt = option;
+ opt.state &= ~QStyle::State_HasFocus;
+ QStyledItemDelegate::paint(painter, opt, index);
+}
+
+class TimelineEditorDelegate : public CustomDelegate
+{
+public:
+ TimelineEditorDelegate(QWidget *parent = nullptr);
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+TimelineEditorDelegate::TimelineEditorDelegate(QWidget *parent)
+ : CustomDelegate(parent)
+{
+ static QItemEditorFactory *factory = nullptr;
+ if (factory == nullptr) {
+ factory = new QItemEditorFactory;
+ QItemEditorCreatorBase *creator = new QItemEditorCreator<QComboBox>("currentText");
+ factory->registerEditor(QVariant::String, creator);
+ }
+
+ setItemEditorFactory(factory);
+}
+
+QWidget *TimelineEditorDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index);
+
+ const auto timelineSettingsModel = qobject_cast<const TimelineSettingsModel *>(index.model());
+
+ auto comboBox = qobject_cast<QComboBox *>(widget);
+
+ QTC_ASSERT(timelineSettingsModel, return widget);
+ QTC_ASSERT(timelineSettingsModel->timelineView(), return widget);
+
+ QmlTimeline qmlTimeline = timelineSettingsModel->timelineForRow(index.row());
+
+ switch (index.column()) {
+ case TimelineSettingsModel::TimelineRow: {
+ QTC_ASSERT(comboBox, return widget);
+ comboBox->addItem(TimelineSettingsModel::tr("None"));
+ for (const auto &timeline : timelineSettingsModel->timelineView()->getTimelines()) {
+ if (!timeline.modelNode().id().isEmpty())
+ comboBox->addItem(timeline.modelNode().id());
+ }
+ } break;
+ case TimelineSettingsModel::AnimationRow: {
+ QTC_ASSERT(comboBox, return widget);
+ comboBox->addItem(TimelineSettingsModel::tr("None"));
+ for (const auto &animation :
+ timelineSettingsModel->timelineView()->getAnimations(qmlTimeline)) {
+ if (!animation.id().isEmpty())
+ comboBox->addItem(animation.id());
+ }
+ } break;
+ case TimelineSettingsModel::FixedFrameRow: {
+ } break;
+
+ default:
+ qWarning() << "TimelineEditorDelegate::createEditor column" << index.column();
+ }
+
+ if (comboBox) {
+ connect(comboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
+ auto delegate = const_cast<TimelineEditorDelegate *>(this);
+ emit delegate->commitData(comboBox);
+ });
+ }
+
+ return widget;
+}
+
+TimelineSettingsModel::TimelineSettingsModel(QObject *parent, TimelineView *view)
+ : QStandardItemModel(parent)
+ , m_timelineView(view)
+{
+ connect(this, &QStandardItemModel::dataChanged, this, &TimelineSettingsModel::handleDataChanged);
+}
+
+void TimelineSettingsModel::resetModel()
+{
+ beginResetModel();
+ clear();
+ setHorizontalHeaderLabels(
+ QStringList({tr("State"), tr("Timeline"), tr("Animation"), tr("Fixed Frame")}));
+
+ if (timelineView()->isAttached() && timelineView()->rootModelNode().hasId()) {
+ addState(ModelNode());
+ for (const QmlModelState &state :
+ QmlItemNode(timelineView()->rootModelNode()).states().allStates())
+ addState(state);
+ }
+
+ endResetModel();
+}
+
+TimelineView *TimelineSettingsModel::timelineView() const
+{
+ return m_timelineView;
+}
+
+void TimelineSettingsModel::setupDelegates(QAbstractItemView *view)
+{
+ view->setItemDelegate(new TimelineEditorDelegate);
+}
+
+static int propertyValueForState(const ModelNode &modelNode,
+ QmlModelState state,
+ const PropertyName &propertyName)
+{
+ if (!modelNode.isValid())
+ return -1;
+
+ if (state.isBaseState()) {
+ if (modelNode.hasVariantProperty(propertyName))
+ return modelNode.variantProperty(propertyName).value().toInt();
+ return -1;
+ }
+
+ if (state.hasPropertyChanges(modelNode)) {
+ QmlPropertyChanges propertyChanges(state.propertyChanges(modelNode));
+ if (propertyChanges.modelNode().hasVariantProperty(propertyName))
+ return propertyChanges.modelNode().variantProperty(propertyName).value().toInt();
+ }
+
+ return -1;
+}
+
+static QStandardItem *createStateItem(const ModelNode &state)
+{
+ if (state.isValid())
+ return new QStandardItem(state.variantProperty("name").value().toString());
+ else
+ return new QStandardItem(TimelineSettingsModel::tr("Base State"));
+}
+
+void TimelineSettingsModel::addState(const ModelNode &state)
+{
+ QList<QStandardItem *> items;
+
+ QmlTimeline timeline = timelineView()->timelineForState(state);
+ const QString timelineId = timeline.isValid() ? timeline.modelNode().id() : QString("");
+ ModelNode animation = animationForTimelineAndState(timeline, state);
+ const QString animationId = animation.isValid() ? animation.id() : QString("");
+
+ QStandardItem *stateItem = createStateItem(state);
+ auto *timelinelItem = new QStandardItem(timelineId);
+ auto *animationItem = new QStandardItem(animationId);
+ auto *fixedFrameItem = new QStandardItem("");
+
+ stateItem->setData(state.internalId());
+ stateItem->setFlags(Qt::ItemIsEnabled);
+
+ int fixedValue = propertyValueForState(timeline, state, "currentFrame");
+ fixedFrameItem->setData(fixedValue, Qt::EditRole);
+
+ items.append(stateItem);
+ items.append(timelinelItem);
+ items.append(animationItem);
+ items.append(fixedFrameItem);
+
+ appendRow(items);
+}
+
+void TimelineSettingsModel::handleException()
+{
+ QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
+ resetModel();
+}
+
+ModelNode TimelineSettingsModel::animationForTimelineAndState(const QmlTimeline &timeline,
+ const ModelNode &state)
+{
+ QmlModelState modelState(state);
+
+ if (!timeline.isValid())
+ return ModelNode();
+
+ const QList<ModelNode> &animations = timelineView()->getAnimations(timeline);
+
+ if (modelState.isBaseState()) {
+ for (const auto &animation : animations) {
+ if (animation.hasVariantProperty("running")
+ && animation.variantProperty("running").value().toBool())
+ return animation;
+ }
+ return ModelNode();
+ }
+
+ for (const auto &animation : animations) {
+ if (modelState.affectsModelNode(animation)) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
+
+ if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running")
+ && propertyChanges.modelNode().variantProperty("running").value().toBool())
+ return animation;
+ }
+ }
+ return ModelNode();
+}
+
+void TimelineSettingsModel::updateTimeline(int row)
+{
+
+ timelineView()->executeInTransaction("TimelineSettingsModel::updateTimeline", [this, row](){
+ QmlModelState modelState(stateForRow(row));
+ QmlTimeline timeline(timelineForRow(row));
+ ModelNode animation(animationForRow(row));
+ QmlTimeline oldTimeline = timelineView()->timelineForState(modelState);
+
+ if (modelState.isBaseState()) {
+ if (oldTimeline.isValid())
+ oldTimeline.modelNode().variantProperty("enabled").setValue(false);
+ if (timeline.isValid())
+ timeline.modelNode().variantProperty("enabled").setValue(true);
+ } else {
+ if (oldTimeline.isValid() && modelState.affectsModelNode(oldTimeline)) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(oldTimeline));
+ if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("enabled"))
+ propertyChanges.modelNode().removeProperty("enabled");
+ }
+
+ QmlTimeline baseTimeline(timelineForRow(0));
+
+ if (baseTimeline.isValid()) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(baseTimeline));
+ if (propertyChanges.isValid())
+ propertyChanges.modelNode().variantProperty("enabled").setValue(false);
+ }
+
+ if (timeline.isValid()) { /* If timeline is invalid 'none' was selected */
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(timeline));
+ if (propertyChanges.isValid())
+ propertyChanges.modelNode().variantProperty("enabled").setValue(true);
+ }
+ }
+ });
+
+ resetRow(row);
+}
+
+void TimelineSettingsModel::updateAnimation(int row)
+{
+ timelineView()->executeInTransaction("TimelineSettingsModel::updateAnimation", [this, row](){
+ QmlModelState modelState(stateForRow(row));
+ QmlTimeline timeline(timelineForRow(row));
+ ModelNode animation(animationForRow(row));
+ QmlTimeline oldTimeline = timelineView()->timelineForState(modelState);
+ ModelNode oldAnimation = animationForTimelineAndState(oldTimeline, modelState);
+
+ if (modelState.isBaseState()) {
+ if (oldAnimation.isValid())
+ oldAnimation.variantProperty("running").setValue(false);
+ if (animation.isValid())
+ animation.variantProperty("running").setValue(true);
+ if (timeline.isValid() && timeline.modelNode().hasProperty("currentFrame"))
+ timeline.modelNode().removeProperty("currentFrame");
+ } else {
+ if (oldAnimation.isValid() && modelState.affectsModelNode(oldAnimation)) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(oldAnimation));
+ if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running"))
+ propertyChanges.modelNode().removeProperty("running");
+ }
+
+ ModelNode baseAnimation(animationForRow(0));
+
+ if (baseAnimation.isValid()) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(baseAnimation));
+ if (propertyChanges.isValid()) {
+ propertyChanges.modelNode().variantProperty("running").setValue(false);
+ if (propertyChanges.modelNode().hasProperty("currentFrame"))
+ propertyChanges.modelNode().removeProperty("currentFrame");
+ }
+ }
+
+ if (animation.isValid()) { /* If animation is invalid 'none' was selected */
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
+ if (propertyChanges.isValid())
+ propertyChanges.modelNode().variantProperty("running").setValue(true);
+ }
+ }
+ });
+ resetRow(row);
+}
+
+void TimelineSettingsModel::updateFixedFrameRow(int row)
+{
+ timelineView()->executeInTransaction("TimelineSettingsModel::updateFixedFrameRow", [this, row](){
+ QmlModelState modelState(stateForRow(row));
+ QmlTimeline timeline(timelineForRow(row));
+
+ ModelNode animation = animationForTimelineAndState(timeline, modelState);
+
+ int fixedFrame = fixedFrameForRow(row);
+
+ if (modelState.isBaseState()) {
+ if (animation.isValid())
+ animation.variantProperty("running").setValue(false);
+ if (timeline.isValid())
+ timeline.modelNode().variantProperty("currentFrame").setValue(fixedFrame);
+ } else {
+ if (animation.isValid() && modelState.affectsModelNode(animation)) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
+ if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running"))
+ propertyChanges.modelNode().removeProperty("running");
+ }
+
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(timeline));
+ if (propertyChanges.isValid())
+ propertyChanges.modelNode().variantProperty("currentFrame").setValue(fixedFrame);
+ }
+
+ });
+
+ resetRow(row);
+}
+
+void TimelineSettingsModel::resetRow(int row)
+{
+ m_lock = true;
+ QStandardItem *animationItem = item(row, AnimationRow);
+ QStandardItem *fixedFrameItem = item(row, FixedFrameRow);
+
+ QmlModelState modelState(stateForRow(row));
+ QmlTimeline timeline(timelineForRow(row));
+ ModelNode animation = animationForTimelineAndState(timeline, modelState);
+
+ if (animationItem) {
+ const QString animationId = animation.isValid() ? animation.id() : QString();
+ animationItem->setText(animationId);
+ }
+
+ if (fixedFrameItem) {
+ int fixedValue = propertyValueForState(timeline, modelState, "currentFrame");
+ if (fixedFrameItem->data(Qt::EditRole).toInt() != fixedValue)
+ fixedFrameItem->setData(fixedValue, Qt::EditRole);
+ }
+
+ m_lock = false;
+}
+
+QmlTimeline TimelineSettingsModel::timelineForRow(int row) const
+{
+ QStandardItem *standardItem = item(row, TimelineRow);
+
+ if (standardItem)
+ return QmlTimeline(timelineView()->modelNodeForId(standardItem->text()));
+
+ return QmlTimeline();
+}
+
+ModelNode TimelineSettingsModel::animationForRow(int row) const
+{
+ QStandardItem *standardItem = item(row, AnimationRow);
+
+ if (standardItem)
+ return timelineView()->modelNodeForId(standardItem->text());
+
+ return ModelNode();
+}
+
+ModelNode TimelineSettingsModel::stateForRow(int row) const
+{
+ QStandardItem *standardItem = item(row, StateRow);
+
+ if (standardItem)
+ return timelineView()->modelNodeForInternalId(standardItem->data().toInt());
+
+ return ModelNode();
+}
+
+int TimelineSettingsModel::fixedFrameForRow(int row) const
+{
+ QStandardItem *standardItem = item(row, FixedFrameRow);
+
+ if (standardItem)
+ return standardItem->data(Qt::EditRole).toInt();
+
+ return -1;
+}
+
+void TimelineSettingsModel::handleDataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight)
+{
+ if (topLeft != bottomRight) {
+ qWarning() << "TimelineSettingsModel::handleDataChanged multi edit?";
+ return;
+ }
+
+ if (m_lock)
+ return;
+
+ m_lock = true;
+
+ int currentColumn = topLeft.column();
+ int currentRow = topLeft.row();
+
+ switch (currentColumn) {
+ case StateRow: {
+ /* read only */
+ } break;
+ case TimelineRow: {
+ updateTimeline(currentRow);
+ } break;
+ case AnimationRow: {
+ updateAnimation(currentRow);
+ } break;
+ case FixedFrameRow: {
+ updateFixedFrameRow(currentRow);
+ } break;
+
+ default:
+ qWarning() << "ConnectionModel::handleDataChanged column" << currentColumn;
+ }
+
+ m_lock = false;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.h
new file mode 100644
index 0000000000..afd4b58e1b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QAbstractItemView>
+#include <QStandardItemModel>
+
+namespace QmlDesigner {
+
+class ModelNode;
+class BindingProperty;
+class SignalHandlerProperty;
+class VariantProperty;
+
+class TimelineView;
+
+class TimelineSettingsModel : public QStandardItemModel
+{
+ Q_OBJECT
+public:
+ enum ColumnRoles { StateRow = 0, TimelineRow = 1, AnimationRow = 2, FixedFrameRow = 3 };
+ TimelineSettingsModel(QObject *parent, TimelineView *view);
+ void resetModel();
+
+ TimelineView *timelineView() const;
+
+ QStringList getSignalsForRow(int row) const;
+ ModelNode getTargetNodeForConnection(const ModelNode &connection) const;
+
+ void addConnection();
+
+ void bindingPropertyChanged(const BindingProperty &bindingProperty);
+ void variantPropertyChanged(const VariantProperty &variantProperty);
+
+ void setupDelegates(QAbstractItemView *view);
+
+ QmlTimeline timelineForRow(int row) const;
+ ModelNode animationForRow(int row) const;
+ ModelNode stateForRow(int row) const;
+ int fixedFrameForRow(int row) const;
+
+protected:
+ void addState(const ModelNode &modelNode);
+
+private:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ void handleException();
+ ModelNode animationForTimelineAndState(const QmlTimeline &timeline, const ModelNode &state);
+
+ void updateTimeline(int row);
+ void updateAnimation(int row);
+ void updateFixedFrameRow(int row);
+
+ void resetRow(int row);
+
+private:
+ TimelineView *m_timelineView;
+ bool m_lock = false;
+ QString m_exceptionError;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp
new file mode 100644
index 0000000000..adc49a97b1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp
@@ -0,0 +1,460 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinetoolbar.h"
+
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelineicons.h"
+
+#include "timelinewidget.h"
+
+#include <designeractionmanager.h>
+#include <nodelistproperty.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
+
+#include <utils/algorithm.h>
+
+#include <QApplication>
+#include <QLabel>
+#include <QLineEdit>
+#include <QResizeEvent>
+#include <QSlider>
+#include <QIntValidator>
+
+namespace QmlDesigner {
+
+bool isSpacer(QObject *object)
+{
+ return object->property("spacer_widget").toBool();
+}
+
+QWidget *createSpacer()
+{
+ QWidget *spacer = new QWidget();
+ spacer->setProperty("spacer_widget", true);
+ return spacer;
+}
+
+int controlWidth(QToolBar *bar, QObject *control)
+{
+ QWidget *widget = nullptr;
+
+ if (auto *action = qobject_cast<QAction *>(control))
+ widget = bar->widgetForAction(action);
+
+ if (widget == nullptr)
+ widget = qobject_cast<QWidget *>(control);
+
+ if (widget)
+ return widget->width();
+
+ return 0;
+}
+
+QAction *createAction(const Core::Id &id,
+ const QIcon &icon,
+ const QString &name,
+ const QKeySequence &shortcut)
+{
+ QString text = QString("%1 (%2)").arg(name).arg(shortcut.toString());
+
+ Core::Context context(TimelineConstants::C_QMLTIMELINE);
+
+ auto *action = new QAction(icon, text);
+ auto *command = Core::ActionManager::registerAction(action, id, context);
+ command->setDefaultKeySequence(shortcut);
+
+ return action;
+}
+
+TimelineToolBar::TimelineToolBar(QWidget *parent)
+ : QToolBar(parent)
+ , m_grp()
+{
+ setContentsMargins(0, 0, 0, 0);
+ createLeftControls();
+ createCenterControls();
+ createRightControls();
+}
+
+void TimelineToolBar::reset()
+{
+ if (recording())
+ m_recording->setChecked(false);
+}
+
+bool TimelineToolBar::recording() const
+{
+ if (m_recording)
+ return m_recording->isChecked();
+
+ return false;
+}
+
+int TimelineToolBar::scaleFactor() const
+{
+ if (m_scale)
+ return m_scale->value();
+ return 0;
+}
+
+QString TimelineToolBar::currentTimelineId() const
+{
+ return m_timelineLabel->text();
+}
+
+void TimelineToolBar::setCurrentState(const QString &name)
+{
+ if (name.isEmpty())
+ m_stateLabel->setText(tr("Base State"));
+ else
+ m_stateLabel->setText(name);
+}
+
+void TimelineToolBar::setCurrentTimeline(const QmlTimeline &timeline)
+{
+ if (timeline.isValid()) {
+ setStartFrame(timeline.startKeyframe());
+ setEndFrame(timeline.endKeyframe());
+ m_timelineLabel->setText(timeline.modelNode().id());
+ } else {
+ m_timelineLabel->setText("");
+ }
+}
+
+void TimelineToolBar::setStartFrame(qreal frame)
+{
+ auto text = QString::number(frame, 'f', 0);
+ m_firstFrame->setText(text);
+ setupCurrentFrameValidator();
+}
+
+void TimelineToolBar::setCurrentFrame(qreal frame)
+{
+ auto text = QString::number(frame, 'f', 0);
+ m_currentFrame->setText(text);
+}
+
+void TimelineToolBar::setEndFrame(qreal frame)
+{
+ auto text = QString::number(frame, 'f', 0);
+ m_lastFrame->setText(text);
+ setupCurrentFrameValidator();
+}
+
+void TimelineToolBar::setScaleFactor(int factor)
+{
+ const QSignalBlocker blocker(m_scale);
+ m_scale->setValue(factor);
+}
+
+void TimelineToolBar::setActionEnabled(const QString &name, bool enabled)
+{
+ for (auto *action : actions())
+ if (action->objectName() == name)
+ action->setEnabled(enabled);
+}
+
+void TimelineToolBar::removeTimeline(const QmlTimeline &timeline)
+{
+ if (timeline.modelNode().id() == m_timelineLabel->text())
+ setCurrentTimeline(QmlTimeline());
+}
+
+void TimelineToolBar::createLeftControls()
+{
+ auto addActionToGroup = [&](QAction *action) {
+ addAction(action);
+ m_grp << action;
+ };
+
+ auto addWidgetToGroup = [&](QWidget *widget) {
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ auto addSpacingToGroup = [&](int width) {
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ addSpacingToGroup(5);
+
+ auto *settingsAction = createAction(TimelineConstants::C_SETTINGS,
+ TimelineIcons::ANIMATION.icon(),
+ tr("Timeline Settings"),
+ QKeySequence(Qt::Key_S));
+
+ connect(settingsAction, &QAction::triggered, this, &TimelineToolBar::settingDialogClicked);
+
+ addActionToGroup(settingsAction);
+
+ addWidgetToGroup(createSpacer());
+
+ m_timelineLabel = new QLabel(this);
+ m_timelineLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
+ addWidgetToGroup(m_timelineLabel);
+}
+
+static QLineEdit *createToolBarLineEdit(QWidget *parent)
+{
+ auto lineEdit = new QLineEdit(parent);
+ lineEdit->setStyleSheet("* { background-color: rgba(0, 0, 0, 0); }");
+ lineEdit->setFixedWidth(48);
+ lineEdit->setAlignment(Qt::AlignCenter);
+
+ QPalette pal = parent->palette();
+ pal.setColor(QPalette::Text, Theme::instance()->color(Utils::Theme::PanelTextColorLight));
+ lineEdit->setPalette(pal);
+ QValidator *validator = new QIntValidator(-100000, 100000, lineEdit);
+ lineEdit->setValidator(validator);
+
+ return lineEdit;
+}
+
+void TimelineToolBar::createCenterControls()
+{
+ addSpacing(5);
+
+ auto *toStart = createAction(TimelineConstants::C_TO_START,
+ TimelineIcons::TO_FIRST_FRAME.icon(),
+ tr("To Start"),
+ QKeySequence(Qt::Key_Home));
+
+ connect(toStart, &QAction::triggered, this, &TimelineToolBar::toFirstFrameTriggered);
+ addAction(toStart);
+
+ addSpacing(2);
+
+ auto *previous = createAction(TimelineConstants::C_PREVIOUS,
+ TimelineIcons::BACK_ONE_FRAME.icon(),
+ tr("Previous"),
+ QKeySequence(Qt::Key_Comma));
+
+ connect(previous, &QAction::triggered, this, &TimelineToolBar::previousFrameTriggered);
+ addAction(previous);
+
+ addSpacing(2);
+
+ auto *play = createAction(TimelineConstants::C_PLAY,
+ TimelineIcons::START_PLAYBACK.icon(),
+ tr("Play"),
+ QKeySequence(Qt::Key_Space));
+
+ connect(play, &QAction::triggered, this, &TimelineToolBar::playTriggered);
+ addAction(play);
+
+ addSpacing(2);
+
+ auto *next = createAction(TimelineConstants::C_NEXT,
+ TimelineIcons::FORWARD_ONE_FRAME.icon(),
+ tr("Next"),
+ QKeySequence(Qt::Key_Period));
+
+ connect(next, &QAction::triggered, this, &TimelineToolBar::nextFrameTriggered);
+ addAction(next);
+
+ addSpacing(2);
+
+ auto *toEnd = createAction(TimelineConstants::C_TO_END,
+ TimelineIcons::TO_LAST_FRAME.icon(),
+ tr("To End"),
+ QKeySequence(Qt::Key_End));
+
+ connect(toEnd, &QAction::triggered, this, &TimelineToolBar::toLastFrameTriggered);
+ addAction(toEnd);
+
+#if 0
+ auto *loop = new QAction(TimelineIcons::LOOP_PLAYBACK.icon(), tr("Loop"), this);
+ addAction(loop);
+#endif
+
+ addSpacing(5);
+
+ addSeparator();
+
+ m_currentFrame = createToolBarLineEdit(this);
+ addWidget(m_currentFrame);
+
+ auto emitCurrentChanged = [this]() { emit currentFrameChanged(m_currentFrame->text().toInt()); };
+ connect(m_currentFrame, &QLineEdit::editingFinished, emitCurrentChanged);
+
+ addSeparator();
+
+ addSpacing(10);
+
+ QIcon autoKeyIcon = TimelineUtils::mergeIcons(TimelineIcons::GLOBAL_RECORD_KEYFRAMES,
+ TimelineIcons::GLOBAL_RECORD_KEYFRAMES_OFF);
+
+ m_recording = createAction(TimelineConstants::C_AUTO_KEYFRAME,
+ autoKeyIcon,
+ tr("Auto Key"),
+ QKeySequence(Qt::Key_K));
+
+ m_recording->setCheckable(true);
+ connect(m_recording, &QAction::toggled, [&](bool value) { emit recordToggled(value); });
+
+ addAction(m_recording);
+
+ addSpacing(10);
+
+ addSeparator();
+
+ addSpacing(10);
+
+ auto *curvePicker = createAction(TimelineConstants::C_CURVE_PICKER,
+ TimelineIcons::CURVE_EDITOR.icon(),
+ tr("Curve Picker"),
+ QKeySequence(Qt::Key_C));
+
+ curvePicker->setObjectName("Curve Picker");
+ connect(curvePicker, &QAction::triggered, this, &TimelineToolBar::openEasingCurveEditor);
+ addAction(curvePicker);
+
+ addSpacing(10);
+
+#if 0
+ addSeparator();
+
+ addSpacing(10);
+
+ auto *curveEditor = new QAction(TimelineIcons::CURVE_PICKER.icon(), tr("Curve Editor"));
+ addAction(curveEditor);
+#endif
+}
+
+void TimelineToolBar::createRightControls()
+{
+ auto *spacer = createSpacer();
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ addWidget(spacer);
+
+ addSeparator();
+
+ m_firstFrame = createToolBarLineEdit(this);
+ addWidget(m_firstFrame);
+
+ auto emitStartChanged = [this]() { emit startFrameChanged(m_firstFrame->text().toInt()); };
+ connect(m_firstFrame, &QLineEdit::editingFinished, emitStartChanged);
+
+ addSeparator();
+
+ addSpacing(10);
+
+ auto *zoomOut = createAction(TimelineConstants::C_ZOOM_OUT,
+ TimelineIcons::ZOOM_SMALL.icon(),
+ tr("Zoom Out"),
+ QKeySequence(QKeySequence::ZoomOut));
+
+ connect(zoomOut, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() - m_scale->pageStep());
+ });
+ addAction(zoomOut);
+
+ addSpacing(10);
+
+ m_scale = new QSlider(this);
+ m_scale->setOrientation(Qt::Horizontal);
+ m_scale->setMaximumWidth(200);
+ m_scale->setMinimumWidth(100);
+ m_scale->setMinimum(0);
+ m_scale->setMaximum(100);
+ m_scale->setValue(0);
+
+ connect(m_scale, &QSlider::valueChanged, this, &TimelineToolBar::scaleFactorChanged);
+ addWidget(m_scale);
+
+ addSpacing(10);
+
+ auto *zoomIn = createAction(TimelineConstants::C_ZOOM_IN,
+ TimelineIcons::ZOOM_BIG.icon(),
+ tr("Zoom In"),
+ QKeySequence(QKeySequence::ZoomIn));
+
+ connect(zoomIn, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() + m_scale->pageStep());
+ });
+ addAction(zoomIn);
+
+ addSpacing(10);
+
+ addSeparator();
+
+ m_lastFrame = createToolBarLineEdit(this);
+ addWidget(m_lastFrame);
+
+ auto emitEndChanged = [this]() { emit endFrameChanged(m_lastFrame->text().toInt()); };
+ connect(m_lastFrame, &QLineEdit::editingFinished, emitEndChanged);
+
+ addSeparator();
+
+ m_stateLabel = new QLabel(this);
+ m_stateLabel->setFixedWidth(80);
+ m_stateLabel->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+ addWidget(m_stateLabel);
+}
+
+void TimelineToolBar::addSpacing(int width)
+{
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+}
+
+void TimelineToolBar::setupCurrentFrameValidator()
+{
+ auto validator = static_cast<const QIntValidator*>(m_currentFrame->validator());
+ const_cast<QIntValidator*>(validator)->setRange(m_firstFrame->text().toInt(), m_lastFrame->text().toInt());
+}
+
+void TimelineToolBar::resizeEvent(QResizeEvent *event)
+{
+ Q_UNUSED(event)
+
+ int width = 0;
+ QWidget *spacer = nullptr;
+ for (auto *object : m_grp) {
+ if (isSpacer(object))
+ spacer = qobject_cast<QWidget *>(object);
+ else
+ width += controlWidth(this, object);
+ }
+
+ if (spacer) {
+ int spacerWidth = TimelineConstants::sectionWidth - width - 12;
+ spacer->setFixedWidth(spacerWidth > 0 ? spacerWidth : 0);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h
new file mode 100644
index 0000000000..43d42b83f9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QToolBar>
+
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+QT_FORWARD_DECLARE_CLASS(QObject)
+QT_FORWARD_DECLARE_CLASS(QResizeEvent)
+QT_FORWARD_DECLARE_CLASS(QSlider)
+QT_FORWARD_DECLARE_CLASS(QWidget)
+
+namespace QmlDesigner {
+
+class TimelineWidget;
+
+class QmlTimeline;
+
+class TimelineToolBar : public QToolBar
+{
+ Q_OBJECT
+
+signals:
+ void settingDialogClicked();
+ //void addTimelineClicked();
+
+ void openEasingCurveEditor();
+
+ void playTriggered();
+ void previousFrameTriggered();
+ void nextFrameTriggered();
+ void toFirstFrameTriggered();
+ void toLastFrameTriggered();
+
+ void recordToggled(bool val);
+ void loopPlaybackToggled(bool val);
+
+ void scaleFactorChanged(int value);
+ void startFrameChanged(int value);
+ void currentFrameChanged(int value);
+ void endFrameChanged(int value);
+
+public:
+ explicit TimelineToolBar(QWidget *parent = nullptr);
+
+ void reset();
+
+ bool recording() const;
+ int scaleFactor() const;
+ QString currentTimelineId() const;
+
+ void setCurrentState(const QString &name);
+ void setCurrentTimeline(const QmlTimeline &timeline);
+ void setStartFrame(qreal frame);
+ void setCurrentFrame(qreal frame);
+ void setEndFrame(qreal frame);
+ void setScaleFactor(int factor);
+
+ void setActionEnabled(const QString &name, bool enabled);
+ void removeTimeline(const QmlTimeline &timeline);
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void createLeftControls();
+ void createCenterControls();
+ void createRightControls();
+ void addSpacing(int width);
+ void setupCurrentFrameValidator();
+
+ QList<QObject *> m_grp;
+
+ QLabel *m_timelineLabel = nullptr;
+ QLabel *m_stateLabel = nullptr;
+ QSlider *m_scale = nullptr;
+ QLineEdit *m_firstFrame = nullptr;
+ QLineEdit *m_currentFrame = nullptr;
+ QLineEdit *m_lastFrame = nullptr;
+
+ QAction *m_recording = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.cpp
new file mode 100644
index 0000000000..e6a9454186
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.cpp
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinetoolbutton.h"
+
+#include "timelineconstants.h"
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <utils/stylehelper.h>
+#include <utils/theme/theme.h>
+
+#include <QAction>
+#include <QDebug>
+
+namespace QmlDesigner {
+
+TimelineToolButton::TimelineToolButton(QAction *action, QGraphicsItem *parent)
+ : QGraphicsWidget(parent)
+ , m_action(action)
+{
+ resize(TimelineConstants::toolButtonSize, TimelineConstants::toolButtonSize);
+ setPreferredSize(size());
+ setAcceptHoverEvents(true);
+ connect(action, &QAction::changed, this, [this]() {
+ setVisible(m_action->isVisible());
+ update();
+ });
+
+ connect(this, &TimelineToolButton::clicked, action, &QAction::trigger);
+
+ setEnabled(m_action->isEnabled());
+ setVisible(m_action->isVisible());
+ setCursor(Qt::ArrowCursor);
+}
+
+void TimelineToolButton::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ painter->save();
+
+ if (m_state == Normal)
+ setOpacity(0.8);
+ else if (m_state == Pressed)
+ setOpacity(0.3);
+ else
+ setOpacity(1.0);
+
+ if (!isEnabled())
+ setOpacity(0.5);
+
+ if (isCheckable()) {
+ if (isChecked() || isDisabled())
+ m_action->icon().paint(painter,
+ rect().toRect(),
+ Qt::AlignCenter,
+ QIcon::Normal,
+ QIcon::On);
+ else
+ m_action->icon().paint(painter,
+ rect().toRect(),
+ Qt::AlignCenter,
+ QIcon::Normal,
+ QIcon::Off);
+ } else
+ m_action->icon().paint(painter, rect().toRect());
+
+ painter->restore();
+}
+
+QRectF TimelineToolButton::boundingRect() const
+{
+ return QRectF(0, 0, TimelineConstants::toolButtonSize, TimelineConstants::toolButtonSize);
+}
+
+bool TimelineToolButton::isCheckable() const
+{
+ return m_action->isCheckable();
+}
+
+bool TimelineToolButton::isChecked() const
+{
+ return m_action->isChecked();
+}
+
+bool TimelineToolButton::isDisabled() const
+{
+ return !m_action->isEnabled();
+}
+
+void TimelineToolButton::setChecked(bool b)
+{
+ m_action->setChecked(b);
+ update();
+}
+
+void TimelineToolButton::setDisabled(bool b)
+{
+ m_action->setDisabled(b);
+}
+
+void TimelineToolButton::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
+{
+ m_state = Hovered;
+
+ QGraphicsObject::hoverEnterEvent(event);
+ event->accept();
+ update();
+}
+
+void TimelineToolButton::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+{
+ m_state = Normal;
+
+ QGraphicsWidget::hoverLeaveEvent(event);
+ event->accept();
+ update();
+}
+
+void TimelineToolButton::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
+{
+ m_state = Hovered;
+ QGraphicsWidget::hoverMoveEvent(event);
+ update();
+}
+
+void TimelineToolButton::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ m_state = Pressed;
+ event->accept();
+ update();
+}
+
+void TimelineToolButton::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ m_state = Normal;
+
+ event->accept();
+ emit clicked();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.h
new file mode 100644
index 0000000000..cdf024c0b2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbutton.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QGraphicsWidget>
+#include <QIcon>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class TimelineToolButton : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineToolButton(QAction *action, QGraphicsItem *parent = nullptr);
+ void paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ QRectF boundingRect() const override;
+
+ bool isCheckable() const;
+ bool isChecked() const;
+ bool isDisabled() const;
+ void setChecked(bool b);
+ void setDisabled(bool b);
+
+signals:
+ void clicked();
+
+protected:
+ void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+
+private:
+ enum State { Pressed, Hovered, Normal };
+
+ State m_state = Normal;
+ QAction *m_action;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp
new file mode 100644
index 0000000000..51c5a7d088
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinetooldelegate.h"
+
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinemovetool.h"
+#include "timelineselectiontool.h"
+
+#include <QGraphicsSceneMouseEvent>
+
+#include "timelinepropertyitem.h"
+#include <QDebug>
+
+namespace QmlDesigner {
+
+TimelineToolDelegate::TimelineToolDelegate(TimelineGraphicsScene *scene)
+ : m_scene(scene)
+ , m_start()
+ , m_moveTool(new TimelineMoveTool(scene, this))
+ , m_selectTool(new TimelineSelectionTool(scene, this))
+{}
+
+QPointF TimelineToolDelegate::startPoint() const
+{
+ return m_start;
+}
+
+TimelineMovableAbstractItem *TimelineToolDelegate::item() const
+{
+ return m_item;
+}
+
+void TimelineToolDelegate::mousePressEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ if (event->buttons() == Qt::LeftButton && hitCanvas(event)) {
+ m_start = event->scenePos();
+
+ if (item) {
+ setItem(item, event->modifiers());
+ m_currentTool = m_moveTool.get();
+ } else
+ m_currentTool = m_selectTool.get();
+ } else
+ m_currentTool = nullptr;
+
+ if (m_currentTool)
+ m_currentTool->mousePressEvent(item, event);
+}
+
+void TimelineToolDelegate::mouseMoveEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ if (m_currentTool)
+ m_currentTool->mouseMoveEvent(item, event);
+}
+
+void TimelineToolDelegate::mouseReleaseEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ if (m_currentTool)
+ m_currentTool->mouseReleaseEvent(item, event);
+
+ reset();
+}
+
+void TimelineToolDelegate::mouseDoubleClickEvent(TimelineMovableAbstractItem *item,
+ QGraphicsSceneMouseEvent *event)
+{
+ if (m_currentTool)
+ m_currentTool->mouseDoubleClickEvent(item, event);
+
+ reset();
+}
+
+void TimelineToolDelegate::clearSelection()
+{
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(m_item))
+ keyframe->setHighlighted(false);
+
+ m_item = nullptr;
+}
+
+void TimelineToolDelegate::setItem(TimelineMovableAbstractItem *item,
+ const Qt::KeyboardModifiers &modifiers)
+{
+ if (item) {
+ setItem(nullptr);
+
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) {
+ if (modifiers.testFlag(Qt::ControlModifier)) {
+ if (m_scene->isKeyframeSelected(keyframe))
+ m_scene->selectKeyframes(SelectionMode::Remove, {keyframe});
+ else
+ m_scene->selectKeyframes(SelectionMode::Add, {keyframe});
+ } else {
+ if (!m_scene->isKeyframeSelected(keyframe))
+ m_scene->selectKeyframes(SelectionMode::New, {keyframe});
+ }
+ }
+
+ } else if (m_item) {
+ if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(m_item))
+ if (!m_scene->isKeyframeSelected(keyframe))
+ keyframe->setHighlighted(false);
+ }
+
+ m_item = item;
+}
+
+bool TimelineToolDelegate::hitCanvas(QGraphicsSceneMouseEvent *event)
+{
+ return event->scenePos().x() > TimelineConstants::sectionWidth;
+}
+
+void TimelineToolDelegate::reset()
+{
+ setItem(nullptr);
+
+ m_currentTool = nullptr;
+
+ m_start = QPointF();
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h
new file mode 100644
index 0000000000..f945c1a61b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelinemovetool.h"
+#include "timelineselectiontool.h"
+
+#include <memory>
+
+namespace QmlDesigner
+{
+
+class TimelineGraphicsScene;
+
+class TimelineToolDelegate
+{
+public:
+ TimelineToolDelegate(TimelineGraphicsScene* scene);
+
+ QPointF startPoint() const;
+
+ TimelineMovableAbstractItem* item() const;
+
+public:
+ void mousePressEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event);
+
+ void mouseMoveEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event);
+
+ void mouseReleaseEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event);
+
+ void mouseDoubleClickEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event);
+
+ void clearSelection();
+
+private:
+ bool hitCanvas(QGraphicsSceneMouseEvent *event);
+
+ void reset();
+
+ void setItem(TimelineMovableAbstractItem *item, const Qt::KeyboardModifiers& modifiers = Qt::NoModifier);
+
+private:
+ static const int dragDistance = 20;
+
+ TimelineGraphicsScene* m_scene;
+
+ QPointF m_start;
+
+ TimelineMovableAbstractItem *m_item = nullptr;
+
+ std::unique_ptr< TimelineMoveTool > m_moveTool;
+
+ std::unique_ptr< TimelineSelectionTool > m_selectTool;
+
+ TimelineAbstractTool *m_currentTool = nullptr;
+};
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.cpp
new file mode 100644
index 0000000000..6873fc0b65
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.cpp
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineutils.h"
+
+#include <QEvent>
+
+namespace QmlDesigner {
+
+namespace TimelineUtils {
+
+DisableContextMenu::DisableContextMenu(QObject *parent)
+ : QObject(parent)
+{}
+
+bool DisableContextMenu::eventFilter(QObject *watched, QEvent *event)
+{
+ if (event->type() == QEvent::ContextMenu)
+ return true;
+
+ return QObject::eventFilter(watched, event);
+}
+
+} // End namespace TimelineUtils.
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h
new file mode 100644
index 0000000000..0758733769
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <istream>
+#include <utils/icon.h>
+#include <vector>
+#include <QDataStream>
+#include <QObject>
+
+QT_FORWARD_DECLARE_CLASS(QEvent)
+
+namespace QmlDesigner {
+
+namespace TimelineUtils {
+
+enum class Side { Top, Right, Bottom, Left };
+
+template<typename T>
+inline T clamp(const T &value, const T &lo, const T &hi)
+{
+ return value < lo ? lo : value > hi ? hi : value;
+}
+
+template<typename T>
+inline T lerp(const T &blend, const T &lhs, const T &rhs)
+{
+ static_assert(std::is_floating_point<T>::value,
+ "TimelineUtils::lerp: For floating point types only!");
+ return blend * lhs + (1.0 - blend) * rhs;
+}
+
+template<typename T>
+inline T reverseLerp(const T &val, const T &lhs, const T &rhs)
+{
+ static_assert(std::is_floating_point<T>::value,
+ "TimelineUtils::reverseLerp: For floating point types only!");
+ return (val - rhs) / (lhs - rhs);
+}
+
+inline QIcon mergeIcons(const Utils::Icon &on, const Utils::Icon &off)
+{
+ QIcon out;
+ out.addPixmap(on.pixmap(), QIcon::Normal, QIcon::On);
+ out.addPixmap(off.pixmap(), QIcon::Normal, QIcon::Off);
+ return out;
+}
+
+class DisableContextMenu : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit DisableContextMenu(QObject *parent = nullptr);
+
+ bool eventFilter(QObject *watched, QEvent *event) override;
+};
+
+} // End namespace TimelineUtils.
+
+template<typename T>
+inline std::istream &operator>>(std::istream &stream, std::vector<T> &vec)
+{
+ quint64 s;
+ stream >> s;
+
+ vec.clear();
+ vec.reserve(s);
+
+ T val;
+ for (quint64 i = 0; i < s; ++i) {
+ stream >> val;
+ vec.push_back(val);
+ }
+ return stream;
+}
+
+template<typename T>
+inline QDataStream &operator<<(QDataStream &stream, const std::vector<T> &vec)
+{
+ stream << static_cast<quint64>(vec.size());
+ for (const auto &elem : vec)
+ stream << elem;
+
+ return stream;
+}
+
+template<typename T>
+inline QDataStream &operator>>(QDataStream &stream, std::vector<T> &vec)
+{
+ quint64 s;
+ stream >> s;
+
+ vec.clear();
+ vec.reserve(s);
+
+ T val;
+ for (quint64 i = 0; i < s; ++i) {
+ stream >> val;
+ vec.push_back(val);
+ }
+ return stream;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp
new file mode 100644
index 0000000000..f39a17db2f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp
@@ -0,0 +1,610 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelineview.h"
+
+#include "easingcurve.h"
+#include "timelineactions.h"
+#include "timelineconstants.h"
+#include "timelinecontext.h"
+#include "timelinewidget.h"
+
+#include "timelinegraphicsscene.h"
+#include "timelinesettingsdialog.h"
+#include "timelinetoolbar.h"
+
+#include <bindingproperty.h>
+#include <exception.h>
+#include <modelnodecontextmenu_helper.h>
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+#include <qmldesignericons.h>
+#include <qmldesignerplugin.h>
+#include <qmlitemnode.h>
+#include <qmlobjectnode.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+#include <viewmanager.h>
+
+#include <coreplugin/icore.h>
+
+#include <utils/qtcassert.h>
+
+#include <designmodecontext.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QTimer>
+
+namespace QmlDesigner {
+
+TimelineView::TimelineView(QObject *parent)
+ : AbstractView(parent)
+{
+ EasingCurve::registerStreamOperators();
+}
+
+TimelineView::~TimelineView() = default;
+
+void TimelineView::modelAttached(Model *model)
+{
+ AbstractView::modelAttached(model);
+ if (m_timelineWidget)
+ m_timelineWidget->init();
+}
+
+void TimelineView::modelAboutToBeDetached(Model *model)
+{
+ m_timelineWidget->reset();
+ setTimelineRecording(false);
+ AbstractView::modelAboutToBeDetached(model);
+}
+
+void TimelineView::nodeCreated(const ModelNode & /*createdNode*/) {}
+
+void TimelineView::nodeAboutToBeRemoved(const ModelNode &removedNode)
+{
+ if (removedNode.isValid()) {
+ if (QmlTimeline::isValidQmlTimeline(removedNode)) {
+ auto *toolBar = widget()->toolBar();
+
+ QString lastId = toolBar->currentTimelineId();
+ toolBar->removeTimeline(QmlTimeline(removedNode));
+ QString currentId = toolBar->currentTimelineId();
+
+ removedNode.setAuxiliaryData("removed@Internal", true);
+
+ if (currentId.isEmpty())
+ m_timelineWidget->graphicsScene()->clearTimeline();
+ if (lastId != currentId)
+ m_timelineWidget->setTimelineId(currentId);
+ } else if (removedNode.parentProperty().isValid()
+ && QmlTimeline::isValidQmlTimeline(removedNode.parentProperty().parentModelNode())) {
+ if (removedNode.hasBindingProperty("target")) {
+ const ModelNode target = removedNode.bindingProperty("target").resolveToModelNode();
+ if (target.isValid()) {
+ QmlTimeline timeline(removedNode.parentProperty().parentModelNode());
+ if (timeline.hasKeyframeGroupForTarget(target))
+ QTimer::singleShot(0, [this, target, timeline]() {
+ if (timeline.hasKeyframeGroupForTarget(target))
+ m_timelineWidget->graphicsScene()->invalidateSectionForTarget(target);
+ else
+ m_timelineWidget->graphicsScene()->invalidateScene();
+ });
+ }
+ }
+ }
+ }
+}
+
+void TimelineView::nodeRemoved(const ModelNode & /*removedNode*/,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags /*propertyChange*/)
+{
+ if (parentProperty.isValid()
+ && QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(
+ parentProperty.parentModelNode())) {
+ QmlTimelineKeyframeGroup frames(parentProperty.parentModelNode());
+ m_timelineWidget->graphicsScene()->invalidateSectionForTarget(frames.target());
+ }
+}
+
+void TimelineView::nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty & /*oldPropertyParent*/,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ if (newPropertyParent.isValid()
+ && QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(
+ newPropertyParent.parentModelNode())) {
+ QmlTimelineKeyframeGroup frames(newPropertyParent.parentModelNode());
+ m_timelineWidget->graphicsScene()->invalidateSectionForTarget(frames.target());
+ } else if (QmlTimelineKeyframeGroup::checkKeyframesType(
+ node)) { /* During copy and paste type info might be incomplete */
+ QmlTimelineKeyframeGroup frames(node);
+ m_timelineWidget->graphicsScene()->invalidateSectionForTarget(frames.target());
+ }
+}
+
+void TimelineView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList)
+{
+ QmlTimeline timeline = currentTimeline();
+ bool updated = false;
+ for (const auto &pair : propertyList) {
+ if (pair.second == "startFrame" || pair.second == "endFrame") {
+ if (QmlTimeline::isValidQmlTimeline(pair.first)) {
+ m_timelineWidget->invalidateTimelineDuration(pair.first);
+ }
+ } else if (pair.second == "currentFrame") {
+ if (QmlTimeline::isValidQmlTimeline(pair.first)) {
+ m_timelineWidget->invalidateTimelinePosition(pair.first);
+ }
+ } else if (!updated && timeline.hasTimeline(pair.first, pair.second)) {
+ m_timelineWidget->graphicsScene()->invalidateCurrentValues();
+ updated = true;
+ }
+ }
+}
+
+void TimelineView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ for (const auto &property : propertyList) {
+ if (property.name() == "frame"
+ && property.parentModelNode().type() == "QtQuick.Timeline.Keyframe"
+ && property.parentModelNode().isValid()
+ && property.parentModelNode().hasParentProperty()) {
+ const ModelNode framesNode
+ = property.parentModelNode().parentProperty().parentModelNode();
+ if (QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(framesNode)) {
+ QmlTimelineKeyframeGroup frames(framesNode);
+ m_timelineWidget->graphicsScene()->invalidateKeyframesForTarget(frames.target());
+ }
+ }
+ }
+}
+
+void TimelineView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeList*/,
+ const QList<ModelNode> & /*lastSelectedNodeList*/)
+{
+ if (m_timelineWidget)
+ m_timelineWidget->graphicsScene()->update();
+}
+
+void TimelineView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList)
+{
+ for (const auto &property : propertyList) {
+ if (property.isNodeListProperty()) {
+ for (const auto &node : property.toNodeListProperty().toModelNodeList()) {
+ nodeAboutToBeRemoved(node);
+ }
+ }
+ }
+}
+
+void TimelineView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
+{
+ for (const auto &property : propertyList) {
+ if (property.name() == "keyframes" && property.parentModelNode().isValid()) {
+ if (QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(
+ property.parentModelNode())) {
+ QmlTimelineKeyframeGroup frames(property.parentModelNode());
+ m_timelineWidget->graphicsScene()->invalidateSectionForTarget(frames.target());
+ } else if (QmlTimeline::isValidQmlTimeline(property.parentModelNode())) {
+ m_timelineWidget->graphicsScene()->invalidateScene();
+ }
+ }
+ }
+}
+
+bool TimelineView::hasWidget() const
+{
+ return true;
+}
+
+void TimelineView::nodeIdChanged(const ModelNode &node, const QString &, const QString &)
+{
+ if (QmlTimeline::isValidQmlTimeline(node))
+ m_timelineWidget->init();
+}
+
+void TimelineView::currentStateChanged(const ModelNode &)
+{
+ if (m_timelineWidget)
+ m_timelineWidget->init();
+}
+
+TimelineWidget *TimelineView::widget() const
+{
+ return m_timelineWidget;
+}
+
+const QmlTimeline TimelineView::addNewTimeline()
+{
+ const TypeName timelineType = "QtQuick.Timeline.Timeline";
+
+ QTC_ASSERT(isAttached(), return QmlTimeline());
+
+ try {
+ ensureQtQuickTimelineImport();
+ } catch (const Exception &e) {
+ e.showException();
+ }
+
+ NodeMetaInfo metaInfo = model()->metaInfo(timelineType);
+
+ QTC_ASSERT(metaInfo.isValid(), return QmlTimeline());
+
+ ModelNode timelineNode;
+
+ executeInTransaction("TimelineView::addNewTimeline", [=, &timelineNode](){
+ bool hasTimelines = getTimelines().isEmpty();
+
+ timelineNode = createModelNode(timelineType,
+ metaInfo.majorVersion(),
+ metaInfo.minorVersion());
+ timelineNode.validId();
+
+ timelineNode.variantProperty("startFrame").setValue(0);
+ timelineNode.variantProperty("endFrame").setValue(1000);
+ timelineNode.variantProperty("enabled").setValue(hasTimelines);
+
+ rootModelNode().defaultNodeListProperty().reparentHere(timelineNode);
+ });
+
+ return QmlTimeline(timelineNode);
+}
+
+ModelNode TimelineView::addAnimation(QmlTimeline timeline)
+{
+ const TypeName animationType = "QtQuick.Timeline.TimelineAnimation";
+
+ QTC_ASSERT(timeline.isValid(), return ModelNode());
+
+ QTC_ASSERT(isAttached(), return ModelNode());
+
+ NodeMetaInfo metaInfo = model()->metaInfo(animationType);
+
+ QTC_ASSERT(metaInfo.isValid(), return ModelNode());
+
+ ModelNode animationNode;
+
+ executeInTransaction("TimelineView::addAnimation", [=, &animationNode](){
+ animationNode = createModelNode(animationType,
+ metaInfo.majorVersion(),
+ metaInfo.minorVersion());
+ animationNode.variantProperty("duration").setValue(timeline.duration());
+ animationNode.validId();
+
+ animationNode.variantProperty("from").setValue(timeline.startKeyframe());
+ animationNode.variantProperty("to").setValue(timeline.endKeyframe());
+
+ animationNode.variantProperty("loops").setValue(1);
+
+ animationNode.variantProperty("running").setValue(getAnimations(timeline).isEmpty());
+
+ timeline.modelNode().nodeListProperty("animations").reparentHere(animationNode);
+
+ if (timeline.modelNode().hasProperty("currentFrame"))
+ timeline.modelNode().removeProperty("currentFrame");
+ });
+
+ return animationNode;
+}
+
+void TimelineView::addNewTimelineDialog()
+{
+ auto timeline = addNewTimeline();
+ addAnimation(timeline);
+ openSettingsDialog();
+}
+
+void TimelineView::openSettingsDialog()
+{
+ auto dialog = new TimelineSettingsDialog(Core::ICore::dialogParent(), this);
+
+ auto timeline = m_timelineWidget->graphicsScene()->currentTimeline();
+ if (timeline.isValid())
+ dialog->setCurrentTimeline(timeline);
+
+ QObject::connect(dialog, &TimelineSettingsDialog::rejected, [this, dialog]() {
+ m_timelineWidget->init();
+ dialog->deleteLater();
+ });
+
+ QObject::connect(dialog, &TimelineSettingsDialog::accepted, [this, dialog]() {
+ m_timelineWidget->init();
+ dialog->deleteLater();
+ });
+
+ dialog->show();
+}
+
+void TimelineView::setTimelineRecording(bool value)
+{
+ ModelNode node = widget()->graphicsScene()->currentTimeline();
+
+ QTC_ASSERT(node.isValid(), return );
+
+ if (value) {
+ activateTimelineRecording(node);
+ } else {
+ deactivateTimelineRecording();
+ activateTimeline(node);
+ }
+}
+
+void TimelineView::customNotification(const AbstractView * /*view*/,
+ const QString &identifier,
+ const QList<ModelNode> &nodeList,
+ const QList<QVariant> &data)
+{
+ if (identifier == QStringLiteral("reset QmlPuppet")) {
+ QmlTimeline timeline = widget()->graphicsScene()->currentTimeline();
+ if (timeline.isValid())
+ timeline.modelNode().removeAuxiliaryData("currentFrame@NodeInstance");
+ } else if (identifier == "INSERT_KEYFRAME" && !nodeList.isEmpty() && !data.isEmpty()) {
+ insertKeyframe(nodeList.constFirst(), data.constFirst().toString().toUtf8());
+ }
+}
+
+void TimelineView::insertKeyframe(const ModelNode &target, const PropertyName &propertyName)
+{
+ QmlTimeline timeline = widget()->graphicsScene()->currentTimeline();
+ ModelNode targetNode = target;
+ if (timeline.isValid() && targetNode.isValid()
+ && QmlObjectNode::isValidQmlObjectNode(targetNode)) {
+ executeInTransaction("TimelineView::insertKeyframe", [=, &timeline, &targetNode](){
+
+ targetNode.validId();
+
+ QmlTimelineKeyframeGroup timelineFrames(
+ timeline.keyframeGroup(targetNode, propertyName));
+
+ QTC_ASSERT(timelineFrames.isValid(), return );
+
+ const qreal frame
+ = timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal();
+ const QVariant value = QmlObjectNode(targetNode).instanceValue(propertyName);
+
+ timelineFrames.setValue(value, frame);
+
+ });
+ }
+}
+
+QList<QmlTimeline> TimelineView::getTimelines() const
+{
+ QList<QmlTimeline> timelines;
+
+ if (!isAttached())
+ return timelines;
+
+ for (const ModelNode &modelNode : allModelNodes()) {
+ if (QmlTimeline::isValidQmlTimeline(modelNode) && !modelNode.hasAuxiliaryData("removed@Internal")) {
+ timelines.append(modelNode);
+ }
+ }
+ return timelines;
+}
+
+QList<ModelNode> TimelineView::getAnimations(const QmlTimeline &timeline)
+{
+ if (!timeline.isValid())
+ return QList<ModelNode>();
+
+ if (isAttached()) {
+ return Utils::filtered(timeline.modelNode().directSubModelNodes(),
+ [timeline](const ModelNode &node) {
+ if (node.metaInfo().isValid() && node.hasParentProperty()
+ && (node.parentProperty().parentModelNode()
+ == timeline.modelNode()))
+ return node.metaInfo().isSubclassOf(
+ "QtQuick.Timeline.TimelineAnimation");
+ return false;
+ });
+ }
+ return {};
+}
+
+QmlTimeline TimelineView::timelineForState(const ModelNode &state) const
+{
+ QmlModelState modelState(state);
+
+ const QList<QmlTimeline> &timelines = getTimelines();
+
+ if (modelState.isBaseState()) {
+ for (const auto &timeline : timelines) {
+ if (timeline.modelNode().hasVariantProperty("enabled")
+ && timeline.modelNode().variantProperty("enabled").value().toBool())
+ return timeline;
+ }
+ return QmlTimeline();
+ }
+
+ for (const auto &timeline : timelines) {
+ if (modelState.affectsModelNode(timeline)) {
+ QmlPropertyChanges propertyChanges(modelState.propertyChanges(timeline));
+
+ if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("enabled")
+ && propertyChanges.modelNode().variantProperty("enabled").value().toBool())
+ return timeline;
+ }
+ }
+ return QmlTimeline();
+}
+
+QmlModelState TimelineView::stateForTimeline(const QmlTimeline &timeline)
+{
+ if (timeline.modelNode().hasVariantProperty("enabled")
+ && timeline.modelNode().variantProperty("enabled").value().toBool()) {
+ return QmlModelState(rootModelNode());
+ }
+
+ for (const QmlModelState &state : QmlItemNode(rootModelNode()).states().allStates()) {
+ if (timelineForState(state) == timeline)
+ return state;
+ }
+
+ return QmlModelState();
+}
+
+void TimelineView::registerActions()
+{
+ auto &actionManager = QmlDesignerPlugin::instance()->viewManager().designerActionManager();
+
+ SelectionContextPredicate timelineEnabled = [this](const SelectionContext &context) {
+ return context.singleNodeIsSelected()
+ && widget()->graphicsScene()->currentTimeline().isValid();
+ };
+
+ SelectionContextPredicate timelineHasKeyframes =
+ [this](const SelectionContext &context) {
+ auto timeline = widget()->graphicsScene()->currentTimeline();
+ return !timeline.keyframeGroupsForTarget(context.currentSingleSelectedNode()).isEmpty();
+ };
+
+ SelectionContextPredicate timelineHasClipboard = [](const SelectionContext &context) {
+ return !context.fastUpdate() && TimelineActions::clipboardContainsKeyframes();
+ };
+
+ SelectionContextOperation deleteKeyframes = [this](const SelectionContext &context) {
+ auto mutator = widget()->graphicsScene()->currentTimeline();
+ if (mutator.isValid())
+ TimelineActions::deleteAllKeyframesForTarget(context.currentSingleSelectedNode(),
+ mutator);
+ };
+
+ SelectionContextOperation insertKeyframes = [this](const SelectionContext &context) {
+ auto mutator = widget()->graphicsScene()->currentTimeline();
+ if (mutator.isValid())
+ TimelineActions::insertAllKeyframesForTarget(context.currentSingleSelectedNode(),
+ mutator);
+ };
+
+ SelectionContextOperation copyKeyframes = [this](const SelectionContext &context) {
+ auto mutator = widget()->graphicsScene()->currentTimeline();
+ if (mutator.isValid())
+ TimelineActions::copyAllKeyframesForTarget(context.currentSingleSelectedNode(), mutator);
+ };
+
+ SelectionContextOperation pasteKeyframes = [this](const SelectionContext &context) {
+ auto mutator = widget()->graphicsScene()->currentTimeline();
+ if (mutator.isValid())
+ TimelineActions::pasteKeyframesToTarget(context.currentSingleSelectedNode(), mutator);
+ };
+
+ actionManager.addDesignerAction(new ActionGroup(TimelineConstants::timelineCategoryDisplayName,
+ TimelineConstants::timelineCategory,
+ TimelineConstants::priorityTimelineCategory,
+ timelineEnabled,
+ &SelectionContextFunctors::always));
+
+ actionManager.addDesignerAction(
+ new ModelNodeContextMenuAction("commandId timeline delete",
+ TimelineConstants::timelineDeleteKeyframesDisplayName,
+ {},
+ TimelineConstants::timelineCategory,
+ QKeySequence(),
+ 160,
+ deleteKeyframes,
+ timelineHasKeyframes));
+
+ actionManager.addDesignerAction(
+ new ModelNodeContextMenuAction("commandId timeline insert",
+ TimelineConstants::timelineInsertKeyframesDisplayName,
+ {},
+ TimelineConstants::timelineCategory,
+ QKeySequence(),
+ 140,
+ insertKeyframes,
+ timelineHasKeyframes));
+
+ actionManager.addDesignerAction(
+ new ModelNodeContextMenuAction("commandId timeline copy",
+ TimelineConstants::timelineCopyKeyframesDisplayName,
+ {},
+ TimelineConstants::timelineCategory,
+ QKeySequence(),
+ 120,
+ copyKeyframes,
+ timelineHasKeyframes));
+
+ actionManager.addDesignerAction(
+ new ModelNodeContextMenuAction("commandId timeline paste",
+ TimelineConstants::timelinePasteKeyframesDisplayName,
+ {},
+ TimelineConstants::timelineCategory,
+ QKeySequence(),
+ 100,
+ pasteKeyframes,
+ timelineHasClipboard));
+}
+
+TimelineWidget *TimelineView::createWidget()
+{
+ if (!m_timelineWidget)
+ m_timelineWidget = new TimelineWidget(this);
+
+ auto *timelineContext = new TimelineContext(m_timelineWidget);
+ Core::ICore::addContextObject(timelineContext);
+
+ return m_timelineWidget;
+}
+
+WidgetInfo TimelineView::widgetInfo()
+{
+ return createWidgetInfo(createWidget(),
+ nullptr,
+ QStringLiteral("Timelines"),
+ WidgetInfo::BottomPane,
+ 0,
+ tr("Timeline"));
+}
+
+bool TimelineView::hasQtQuickTimelineImport()
+{
+ if (isAttached()) {
+ Import import = Import::createLibraryImport("QtQuick.Timeline", "1.0");
+ return model()->hasImport(import, true, true);
+ }
+
+ return false;
+}
+
+void TimelineView::ensureQtQuickTimelineImport()
+{
+ if (!hasQtQuickTimelineImport()) {
+ Import timelineImport = Import::createLibraryImport("QtQuick.Timeline", "1.0");
+ model()->changeImports({timelineImport}, {});
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h
new file mode 100644
index 0000000000..057ff3047b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <abstractview.h>
+
+#include <QPointer>
+
+namespace QmlDesigner {
+
+class TimelineWidget;
+
+class TimelineView : public AbstractView
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineView(QObject *parent = nullptr);
+ ~TimelineView() override;
+ //Abstract View
+ WidgetInfo widgetInfo() override;
+ void modelAttached(Model *model) override;
+ void modelAboutToBeDetached(Model *model) override;
+ void nodeCreated(const ModelNode &createdNode) override;
+ void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
+ void nodeRemoved(const ModelNode &removedNode,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags propertyChange) override;
+ void nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent,
+ PropertyChangeFlags propertyChange) override;
+ void instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList) override;
+ void variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
+ const QList<ModelNode> &lastSelectedNodeList) override;
+
+ void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override;
+ void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
+
+ bool hasWidget() const override;
+
+ void nodeIdChanged(const ModelNode &node, const QString &, const QString &) override;
+
+ void currentStateChanged(const ModelNode &node) override;
+
+ TimelineWidget *widget() const;
+
+ const QmlTimeline addNewTimeline();
+ ModelNode addAnimation(QmlTimeline timeline);
+ void addNewTimelineDialog();
+ void openSettingsDialog();
+
+ void setTimelineRecording(bool b);
+
+ void customNotification(const AbstractView *view,
+ const QString &identifier,
+ const QList<ModelNode> &nodeList,
+ const QList<QVariant> &data) override;
+ void insertKeyframe(const ModelNode &target, const PropertyName &propertyName);
+
+ QList<QmlTimeline> getTimelines() const;
+ QList<ModelNode> getAnimations(const QmlTimeline &timeline);
+ QmlTimeline timelineForState(const ModelNode &state) const;
+ QmlModelState stateForTimeline(const QmlTimeline &timeline);
+
+ void registerActions();
+
+private:
+ TimelineWidget *createWidget();
+ TimelineWidget *m_timelineWidget = nullptr;
+ bool hasQtQuickTimelineImport();
+ void ensureQtQuickTimelineImport();
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp
new file mode 100644
index 0000000000..8a58eb7dcf
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp
@@ -0,0 +1,443 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "timelinewidget.h"
+#include "easingcurvedialog.h"
+#include "timelineconstants.h"
+#include "timelinegraphicsscene.h"
+#include "timelineicons.h"
+#include "timelinepropertyitem.h"
+#include "timelinetoolbar.h"
+#include "timelineview.h"
+
+#include <qmldesignerplugin.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+
+#include <theme.h>
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMargins>
+#include <QPushButton>
+#include <QResizeEvent>
+#include <QScrollBar>
+#include <QShowEvent>
+#include <QSlider>
+#include <QVBoxLayout>
+#include <QtGlobal>
+
+namespace QmlDesigner {
+
+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;
+ }
+};
+
+static qreal next(const QVector<qreal> &vector, qreal current)
+{
+ auto iter = std::find_if(vector.cbegin(), vector.cend(), [&](qreal val) {
+ return val > current;
+ });
+ if (iter != vector.end())
+ return *iter;
+ return current;
+}
+
+static qreal previous(const QVector<qreal> &vector, qreal current)
+{
+ auto iter = std::find_if(vector.rbegin(), vector.rend(), [&](qreal val) {
+ return val < current;
+ });
+ if (iter != vector.rend())
+ return *iter;
+ return current;
+}
+
+static qreal getcurrentFrame(const QmlTimeline &timeline)
+{
+ if (!timeline.isValid())
+ return 0;
+
+ if (timeline.modelNode().hasAuxiliaryData("currentFrame@NodeInstance"))
+ return timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal();
+ return timeline.currentKeyframe();
+}
+
+TimelineWidget::TimelineWidget(TimelineView *view)
+ : QWidget()
+ , m_toolbar(new TimelineToolBar(this))
+ , m_rulerView(new QGraphicsView(this))
+ , m_graphicsView(new QGraphicsView(this))
+ , m_scrollbar(new QScrollBar(this))
+ , m_statusBar(new QLabel(this))
+ , m_timelineView(view)
+ , m_graphicsScene(new TimelineGraphicsScene(this))
+ , m_addButton(new QPushButton(this))
+{
+ setWindowTitle(tr("Timeline", "Title of timeline view"));
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ const QString css = Theme::replaceCssColors(QString::fromUtf8(
+ Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css"))));
+
+ m_scrollbar->setStyleSheet(css);
+ m_scrollbar->setOrientation(Qt::Horizontal);
+
+ QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ sizePolicy1.setHorizontalStretch(0);
+ sizePolicy1.setVerticalStretch(0);
+ sizePolicy1.setHeightForWidth(m_graphicsView->sizePolicy().hasHeightForWidth());
+
+ m_rulerView->setObjectName("RulerView");
+ m_rulerView->setScene(graphicsScene());
+ m_rulerView->setFixedHeight(TimelineConstants::rulerHeight);
+ m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_rulerView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->viewport()->installEventFilter(new Eventfilter(this));
+ m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus);
+
+ m_graphicsView->setStyleSheet(css);
+ m_graphicsView->setObjectName("SceneView");
+ m_graphicsView->setFrameShape(QFrame::NoFrame);
+ m_graphicsView->setFrameShadow(QFrame::Plain);
+ m_graphicsView->setLineWidth(0);
+ m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ m_graphicsView->setSizePolicy(sizePolicy1);
+ m_graphicsView->setScene(graphicsScene());
+ m_graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ auto *scrollBarLayout = new QHBoxLayout;
+ scrollBarLayout->addSpacing(TimelineConstants::sectionWidth);
+ scrollBarLayout->addWidget(m_scrollbar);
+
+ QMargins margins(0, 0, 0, QApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
+
+ auto *contentLayout = new QVBoxLayout;
+ contentLayout->setContentsMargins(margins);
+ contentLayout->addWidget(m_rulerView);
+ contentLayout->addWidget(m_graphicsView);
+ contentLayout->addLayout(scrollBarLayout);
+ contentLayout->addWidget(m_statusBar);
+ m_statusBar->setIndent(2);
+ m_statusBar->setFixedHeight(TimelineConstants::rulerHeight);
+
+ auto *widgetLayout = new QVBoxLayout;
+ widgetLayout->setContentsMargins(0, 0, 0, 0);
+ widgetLayout->setSpacing(0);
+ widgetLayout->addWidget(m_toolbar);
+ widgetLayout->addWidget(m_addButton);
+
+ m_addButton->setIcon(TimelineIcons::ADD_TIMELINE.icon());
+ m_addButton->setToolTip(tr("Add Timeline"));
+ m_addButton->setFlat(true);
+ m_addButton->setFixedSize(32, 32);
+
+ widgetLayout->addLayout(contentLayout);
+ this->setLayout(widgetLayout);
+
+ connectToolbar();
+
+ auto setScrollOffset = [this]() { graphicsScene()->setScrollOffset(m_scrollbar->value()); };
+ connect(m_scrollbar, &QSlider::valueChanged, this, setScrollOffset);
+
+ connect(graphicsScene(),
+ &TimelineGraphicsScene::statusBarMessageChanged,
+ this,
+ [this](const QString &message) { m_statusBar->setText(message); });
+
+ connect(m_addButton, &QPushButton::clicked, this, [this]() {
+ m_timelineView->addNewTimelineDialog();
+ });
+}
+
+void TimelineWidget::connectToolbar()
+{
+ connect(graphicsScene(),
+ &TimelineGraphicsScene::selectionChanged,
+ this,
+ &TimelineWidget::selectionChanged);
+
+ connect(graphicsScene(), &TimelineGraphicsScene::scroll, this, &TimelineWidget::scroll);
+
+ auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); };
+ connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setRulerScaling);
+
+ auto setToFirstFrame = [this]() {
+ graphicsScene()->setCurrentFrame(graphicsScene()->startFrame());
+ };
+ connect(m_toolbar, &TimelineToolBar::toFirstFrameTriggered, setToFirstFrame);
+
+ auto setToLastFrame = [this]() {
+ graphicsScene()->setCurrentFrame(graphicsScene()->endFrame());
+ };
+ connect(m_toolbar, &TimelineToolBar::toLastFrameTriggered, setToLastFrame);
+
+ auto setToPreviousFrame = [this]() {
+ graphicsScene()->setCurrentFrame(adjacentFrame(&previous));
+ };
+ connect(m_toolbar, &TimelineToolBar::previousFrameTriggered, setToPreviousFrame);
+
+ auto setToNextFrame = [this]() { graphicsScene()->setCurrentFrame(adjacentFrame(&next)); };
+ connect(m_toolbar, &TimelineToolBar::nextFrameTriggered, setToNextFrame);
+
+ auto setCurrentFrame = [this](int frame) { graphicsScene()->setCurrentFrame(frame); };
+ connect(m_toolbar, &TimelineToolBar::currentFrameChanged, setCurrentFrame);
+
+ auto setStartFrame = [this](int start) { graphicsScene()->setStartFrame(start); };
+ connect(m_toolbar, &TimelineToolBar::startFrameChanged, setStartFrame);
+
+ auto setEndFrame = [this](int end) { graphicsScene()->setEndFrame(end); };
+ connect(m_toolbar, &TimelineToolBar::endFrameChanged, setEndFrame);
+
+
+ connect(m_toolbar, &TimelineToolBar::recordToggled,
+ this,
+ &TimelineWidget::setTimelineRecording);
+
+ connect(m_toolbar,
+ &TimelineToolBar::openEasingCurveEditor,
+ this,
+ &TimelineWidget::openEasingCurveEditor);
+
+ connect(m_toolbar,
+ &TimelineToolBar::settingDialogClicked,
+ m_timelineView,
+ &TimelineView::openSettingsDialog);
+
+ for (auto action : QmlDesignerPlugin::instance()->designerActionManager().designerActions()) {
+ if (action->menuId() == "LivePreview") {
+ QObject::connect(m_toolbar,
+ &TimelineToolBar::playTriggered,
+ action->action(),
+ [action]() {
+ action->action()->setChecked(false);
+ action->action()->triggered(true);
+ });
+ }
+ }
+
+ setTimelineActive(false);
+}
+
+int TimelineWidget::adjacentFrame(const std::function<qreal(const QVector<qreal> &, qreal)> &fun) const
+{
+ auto positions = graphicsScene()->keyframePositions();
+ std::sort(positions.begin(), positions.end());
+ return qRound(fun(positions, graphicsScene()->currentFramePosition()));
+}
+
+void TimelineWidget::changeScaleFactor(int factor)
+{
+ m_toolbar->setScaleFactor(factor);
+}
+
+void TimelineWidget::scroll(const TimelineUtils::Side &side)
+{
+ if (side == TimelineUtils::Side::Left)
+ m_scrollbar->setValue(m_scrollbar->value() - m_scrollbar->singleStep());
+ else if (side == TimelineUtils::Side::Right)
+ m_scrollbar->setValue(m_scrollbar->value() + m_scrollbar->singleStep());
+}
+
+void TimelineWidget::selectionChanged()
+{
+ if (graphicsScene()->hasSelection())
+ m_toolbar->setActionEnabled("Curve Picker", true);
+ else
+ m_toolbar->setActionEnabled("Curve Picker", false);
+}
+
+void TimelineWidget::openEasingCurveEditor()
+{
+ if (graphicsScene()->hasSelection()) {
+ QList<ModelNode> frames;
+ for (auto *item : graphicsScene()->selectedKeyframes())
+ frames.append(item->frameNode());
+ EasingCurveDialog::runDialog(frames);
+ }
+}
+
+void TimelineWidget::setTimelineRecording(bool value)
+{
+ ModelNode node = timelineView()->modelNodeForId(m_toolbar->currentTimelineId());
+
+ if (value) {
+ timelineView()->activateTimelineRecording(node);
+ } else {
+ timelineView()->deactivateTimelineRecording();
+ timelineView()->activateTimeline(node);
+ }
+
+ graphicsScene()->invalidateRecordButtonsStatus();
+}
+
+void TimelineWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
+{
+ if (timelineView())
+ timelineView()->contextHelp(callback);
+ else
+ callback({});
+}
+
+void TimelineWidget::init()
+{
+ QmlTimeline currentTimeline = m_timelineView->timelineForState(m_timelineView->currentState());
+ if (currentTimeline.isValid())
+ setTimelineId(currentTimeline.modelNode().id());
+ else
+ setTimelineId({});
+
+ invalidateTimelineDuration(graphicsScene()->currentTimeline());
+
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+
+ // setScaleFactor uses QSignalBlocker.
+ m_toolbar->setScaleFactor(0);
+ m_graphicsScene->setRulerScaling(0);
+}
+
+void TimelineWidget::reset()
+{
+ graphicsScene()->clearTimeline();
+ m_toolbar->reset();
+ m_statusBar->clear();
+}
+
+TimelineGraphicsScene *TimelineWidget::graphicsScene() const
+{
+ return m_graphicsScene;
+}
+
+TimelineToolBar *TimelineWidget::toolBar() const
+{
+ return m_toolbar;
+}
+
+void TimelineWidget::invalidateTimelineDuration(const QmlTimeline &timeline)
+{
+ if (timelineView() && timelineView()->model()) {
+ QmlTimeline currentTimeline = graphicsScene()->currentTimeline();
+ if (currentTimeline.isValid() && currentTimeline == timeline) {
+ m_toolbar->setCurrentTimeline(timeline);
+ graphicsScene()->setTimeline(timeline);
+ graphicsScene()->setCurrenFrame(timeline, getcurrentFrame(timeline));
+ }
+ }
+}
+
+void TimelineWidget::invalidateTimelinePosition(const QmlTimeline &timeline)
+{
+ if (timelineView() && timelineView()->model()) {
+ QmlTimeline currentTimeline = graphicsScene()->currentTimeline();
+ if (currentTimeline.isValid() && currentTimeline == timeline) {
+ qreal frame = getcurrentFrame(timeline);
+ m_toolbar->setCurrentFrame(frame);
+ graphicsScene()->setCurrenFrame(timeline, frame);
+ }
+ }
+}
+
+void TimelineWidget::setupScrollbar(int min, int max, int current)
+{
+ bool b = m_scrollbar->blockSignals(true);
+ m_scrollbar->setMinimum(min);
+ m_scrollbar->setMaximum(max);
+ m_scrollbar->setValue(current);
+ m_scrollbar->setSingleStep((max - min) / 10);
+ m_scrollbar->blockSignals(b);
+}
+
+void TimelineWidget::setTimelineId(const QString &id)
+{
+ setTimelineActive(!m_timelineView->getTimelines().isEmpty());
+ if (m_timelineView->isAttached()) {
+ m_toolbar->setCurrentTimeline(m_timelineView->modelNodeForId(id));
+ m_toolbar->setCurrentState(m_timelineView->currentState().name());
+ m_timelineView->setTimelineRecording(false);
+ }
+}
+
+void TimelineWidget::setTimelineActive(bool b)
+{
+ if (b) {
+ m_toolbar->setVisible(true);
+ m_graphicsView->setVisible(true);
+ m_rulerView->setVisible(true);
+ m_scrollbar->setVisible(true);
+ m_addButton->setVisible(false);
+ m_graphicsView->update();
+ m_rulerView->update();
+ } else {
+ m_toolbar->setVisible(false);
+ m_graphicsView->setVisible(false);
+ m_rulerView->setVisible(false);
+ m_scrollbar->setVisible(false);
+ m_addButton->setVisible(true);
+ }
+}
+
+void TimelineWidget::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event)
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+ graphicsScene()->invalidateLayout();
+ graphicsScene()->invalidate();
+ graphicsScene()->onShow();
+}
+
+void TimelineWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+}
+
+TimelineView *TimelineWidget::timelineView() const
+{
+ return m_timelineView;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h
new file mode 100644
index 0000000000..3fd299a88a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineutils.h"
+#include <coreplugin/icontext.h>
+
+#include <QWidget>
+
+#include <functional>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QGraphicsView)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QResizeEvent)
+QT_FORWARD_DECLARE_CLASS(QScrollBar)
+QT_FORWARD_DECLARE_CLASS(QShowEvent)
+QT_FORWARD_DECLARE_CLASS(QString)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+
+namespace QmlDesigner {
+
+class TimelineToolBar;
+class TimelineView;
+class TimelineGraphicsScene;
+class QmlTimeline;
+
+class TimelineWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TimelineWidget(TimelineView *view);
+ void contextHelp(const Core::IContext::HelpCallback &callback) const;
+
+ TimelineGraphicsScene *graphicsScene() const;
+ TimelineView *timelineView() const;
+ TimelineToolBar *toolBar() const;
+
+ void init();
+ void reset();
+
+ void invalidateTimelineDuration(const QmlTimeline &timeline);
+ void invalidateTimelinePosition(const QmlTimeline &timeline);
+ void setupScrollbar(int min, int max, int current);
+ void setTimelineId(const QString &id);
+
+ void setTimelineActive(bool b);
+
+public slots:
+ void selectionChanged();
+ void openEasingCurveEditor();
+ void setTimelineRecording(bool value);
+ void changeScaleFactor(int factor);
+ void scroll(const TimelineUtils::Side &side);
+
+protected:
+ void showEvent(QShowEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void connectToolbar();
+
+ int adjacentFrame(const std::function<qreal(const QVector<qreal> &, qreal)> &fun) const;
+
+ TimelineToolBar *m_toolbar = nullptr;
+
+ QGraphicsView *m_rulerView = nullptr;
+
+ QGraphicsView *m_graphicsView = nullptr;
+
+ QScrollBar *m_scrollbar = nullptr;
+
+ QLabel *m_statusBar = nullptr;
+
+ TimelineView *m_timelineView = nullptr;
+
+ TimelineGraphicsScene *m_graphicsScene;
+
+ QPushButton *m_addButton = nullptr;
+};
+
+} // namespace QmlDesigner