From 36d8ad4c390e32ebbc0dc2a1462868c02a9f0e6a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Aug 2014 12:11:26 +0200 Subject: [PATCH] QmlProfiler: drag&drop reordering of models in timeline Task-number: QTCREATORBUG-12337 Change-Id: I399593f44aa8ff8dd79c623108fecb3c317cb63c Reviewed-by: Kai Koehne --- src/plugins/qmlprofiler/qml/CategoryLabel.qml | 96 ++++++++++++++++++- src/plugins/qmlprofiler/qml/MainView.qml | 11 +++ .../qmlprofiler/timelinemodelaggregator.cpp | 8 +- .../qmlprofiler/timelinemodelaggregator.h | 5 +- src/plugins/qmlprofiler/timelinerenderer.cpp | 18 ++++ src/plugins/qmlprofiler/timelinerenderer.h | 2 +- 6 files changed, 136 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmlprofiler/qml/CategoryLabel.qml b/src/plugins/qmlprofiler/qml/CategoryLabel.qml index fd66dfc2f6f..66bbe7eff1f 100644 --- a/src/plugins/qmlprofiler/qml/CategoryLabel.qml +++ b/src/plugins/qmlprofiler/qml/CategoryLabel.qml @@ -33,13 +33,18 @@ import QtQuick.Controls.Styles 1.2 Item { id: labelContainer - property string text: qmlProfilerModelProxy.displayName(modelIndex) + property string text: trigger(1) ? qmlProfilerModelProxy.displayName(modelIndex) : "" property bool expanded: trigger(qmlProfilerModelProxy.expanded(modelIndex)) property int modelIndex: index property int bindingTrigger: 1 property var descriptions: [] property var extdescriptions: [] property var eventIds: [] + property bool dragging + property Item draggerParent + + signal dragStarted; + signal dragStopped; readonly property int dragHeight: 5 @@ -83,6 +88,32 @@ Item { Connections { target: qmlProfilerModelProxy onStateChanged: updateDescriptions() + onModelsChanged: updateDescriptions() + } + + MouseArea { + id: dragArea + anchors.fill: txt + drag.target: dragger + cursorShape: dragging ? Qt.DragMoveCursor : Qt.OpenHandCursor + } + + DropArea { + id: dropArea + + onPositionChanged: { + if ((drag.source.modelIndex > labelContainer.modelIndex && + drag.source.y < labelContainer.y + drag.source.height) || + (drag.source.modelIndex < labelContainer.modelIndex && + drag.source.y > labelContainer.y + labelContainer.height - + drag.source.height)) { + qmlProfilerModelProxy.swapModels(drag.source.modelIndex, + labelContainer.modelIndex); + drag.source.modelIndex = labelContainer.modelIndex; + } + } + + anchors.fill: parent } Text { @@ -178,4 +209,67 @@ Item { } } } + + Rectangle { + id: dragger + property int modelIndex + width: labelContainer.width + height: 0 + color: "black" + opacity: 0.5 + anchors.left: parent.left + + // anchor to top so that it reliably snaps back after dragging + anchors.top: parent.top + + Drag.active: dragArea.drag.active + Drag.onActiveChanged: { + // We don't want height, text, or modelIndex to be changed when reordering occurs, so we + // don't make them properties. + draggerText.text = txt.text; + modelIndex = labelContainer.modelIndex; + if (Drag.active) { + height = labelContainer.height; + labelContainer.dragStarted(); + } else { + height = 0; + labelContainer.dragStopped(); + } + } + + states: [ + State { + when: dragger.Drag.active + ParentChange { + target: dragger + parent: draggerParent + } + PropertyChanges { + target: dragger + anchors.top: undefined + } + } + ] + + Text { + id: draggerText + visible: parent.Drag.active + x: txt.x + font.pixelSize: txt.font.pixelSize + color: "white" + width: txt.width + height: txt.height + verticalAlignment: txt.verticalAlignment + renderType: txt.renderType + } + } + + MouseArea { + anchors.top: dragArea.bottom + anchors.bottom: labelContainer.dragging ? labelContainer.bottom : dragArea.bottom + anchors.left: labelContainer.left + anchors.right: labelContainer.right + cursorShape: dragArea.cursorShape + } + } diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml index f43c1ebb57e..a71f5952ca9 100644 --- a/src/plugins/qmlprofiler/qml/MainView.qml +++ b/src/plugins/qmlprofiler/qml/MainView.qml @@ -84,6 +84,7 @@ Rectangle { view.requestPaint(); } onStateChanged: backgroundMarks.requestPaint() + onModelsChanged: backgroundMarks.requestPaint() onExpandedChanged: backgroundMarks.requestPaint() onRowHeightChanged: backgroundMarks.requestPaint() } @@ -211,10 +212,20 @@ Rectangle { Column { id: col + + // Dispatch the cursor shape to all labels. When dragging the DropArea receiving + // the drag events is not necessarily related to the MouseArea receiving the mouse + // events, so we can't use the drag events to determine the cursor shape. + property bool dragging: false + Repeater { model: labels.rowCount delegate: CategoryLabel { + dragging: col.dragging reverseSelect: root.shiftPressed + onDragStarted: col.dragging = true + onDragStopped: col.dragging = false + draggerParent: labels } } } diff --git a/src/plugins/qmlprofiler/timelinemodelaggregator.cpp b/src/plugins/qmlprofiler/timelinemodelaggregator.cpp index e6c582854ae..f40fee2754a 100644 --- a/src/plugins/qmlprofiler/timelinemodelaggregator.cpp +++ b/src/plugins/qmlprofiler/timelinemodelaggregator.cpp @@ -92,7 +92,7 @@ void TimelineModelAggregator::addModel(AbstractTimelineModel *m) d->modelList << m; connect(m,SIGNAL(expandedChanged()),this,SIGNAL(expandedChanged())); connect(m,SIGNAL(rowHeightChanged()),this,SIGNAL(rowHeightChanged())); - emit modelsChanged(); + emit modelsChanged(d->modelList.length(), d->modelList.length()); } QVariantList TimelineModelAggregator::models() const @@ -255,6 +255,12 @@ int TimelineModelAggregator::eventIdForLocation(int modelIndex, const QString &f return d->modelList[modelIndex]->eventIdForLocation(filename, line, column); } +void TimelineModelAggregator::swapModels(int modelIndex1, int modelIndex2) +{ + qSwap(d->modelList[modelIndex1], d->modelList[modelIndex2]); + emit modelsChanged(modelIndex1, modelIndex2); +} + void TimelineModelAggregator::dataChanged() { // this is a slot connected for every modelproxy diff --git a/src/plugins/qmlprofiler/timelinemodelaggregator.h b/src/plugins/qmlprofiler/timelinemodelaggregator.h index ef60cfd631e..6067649b4a2 100644 --- a/src/plugins/qmlprofiler/timelinemodelaggregator.h +++ b/src/plugins/qmlprofiler/timelinemodelaggregator.h @@ -62,6 +62,7 @@ public: Q_INVOKABLE int rowHeight(int modelIndex, int row) const; Q_INVOKABLE void setRowHeight(int modelIndex, int row, int height); Q_INVOKABLE int rowOffset(int modelIndex, int row) const; + Q_INVOKABLE bool expanded(int modelIndex) const; Q_INVOKABLE void setExpanded(int modelIndex, bool expanded); Q_INVOKABLE int rowCount(int modelIndex) const; @@ -91,12 +92,14 @@ public: Q_INVOKABLE int eventIdForLocation(int modelIndex, const QString &filename, int line, int column) const; + Q_INVOKABLE void swapModels(int modelIndex1, int modelIndex2); + signals: void dataAvailable(); void stateChanged(); void expandedChanged(); void rowHeightChanged(); - void modelsChanged(); + void modelsChanged(int modelIndex1, int modelIndex2); protected slots: void dataChanged(); diff --git a/src/plugins/qmlprofiler/timelinerenderer.cpp b/src/plugins/qmlprofiler/timelinerenderer.cpp index e230b1b344c..7e242b7e0d3 100644 --- a/src/plugins/qmlprofiler/timelinerenderer.cpp +++ b/src/plugins/qmlprofiler/timelinerenderer.cpp @@ -56,12 +56,16 @@ void TimelineRenderer::setProfilerModelProxy(QObject *profilerModelProxy) if (m_profilerModelProxy) { disconnect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint())); disconnect(m_profilerModelProxy, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint())); + disconnect(m_profilerModelProxy, SIGNAL(modelsChanged(int,int)), + this, SLOT(swapSelections(int,int))); } m_profilerModelProxy = qobject_cast(profilerModelProxy); if (m_profilerModelProxy) { connect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint())); connect(m_profilerModelProxy, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint())); + connect(m_profilerModelProxy, SIGNAL(modelsChanged(int,int)), + this, SLOT(swapSelections(int,int))); } emit profilerModelProxyChanged(m_profilerModelProxy); } @@ -84,6 +88,20 @@ void TimelineRenderer::requestPaint() update(); } +void TimelineRenderer::swapSelections(int modelIndex1, int modelIndex2) +{ + // Any hovered event is most likely useless now. Reset it. + resetCurrentSelection(); + + // Explicitly selected events can be tracked in a useful way. + if (m_selectedModel == modelIndex1) + setSelectedModel(modelIndex2); + else if (m_selectedModel == modelIndex2) + setSelectedModel(modelIndex1); + + update(); +} + inline void TimelineRenderer::getItemXExtent(int modelIndex, int i, int ¤tX, int &itemWidth) { qint64 start = m_profilerModelProxy->startTime(modelIndex, i) - m_startTime; diff --git a/src/plugins/qmlprofiler/timelinerenderer.h b/src/plugins/qmlprofiler/timelinerenderer.h index a515228fdea..d4e2d620d4a 100644 --- a/src/plugins/qmlprofiler/timelinerenderer.h +++ b/src/plugins/qmlprofiler/timelinerenderer.h @@ -116,7 +116,7 @@ signals: public slots: void clearData(); void requestPaint(); - + void swapSelections(int modelIndex1, int modelIndex2); void setStartTime(qint64 arg) {