diff --git a/src/plugins/qmlprofiler/qml/CategoryLabel.qml b/src/plugins/qmlprofiler/qml/CategoryLabel.qml
index 5b635964740..367c2c8730a 100644
--- a/src/plugins/qmlprofiler/qml/CategoryLabel.qml
+++ b/src/plugins/qmlprofiler/qml/CategoryLabel.qml
@@ -36,6 +36,7 @@ Item {
id: labelContainer
property QtObject model
+ property QtObject notesModel
property bool mockup
property string text: model ? model.displayName : ""
property bool expanded: model && model.expanded
@@ -192,9 +193,8 @@ Item {
property var eventIds: []
property var texts: []
property int currentNote: -1
- property var notesModel: qmlProfilerModelProxy.notes
Connections {
- target: notesButton.notesModel
+ target: notesModel
onChanged: {
if (arguments[1] === -1 || arguments[1] === model.modelId)
notesButton.updateNotes();
diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml
index e019ae091ad..4366b6bc332 100644
--- a/src/plugins/qmlprofiler/qml/MainView.qml
+++ b/src/plugins/qmlprofiler/qml/MainView.qml
@@ -29,33 +29,29 @@
****************************************************************************/
import QtQuick 2.1
-import Monitor 1.0
import QtQuick.Controls 1.0
-import QtQml.Models 2.1
Rectangle {
id: root
// ***** properties
- property bool selectionLocked : true
property bool lockItemSelection : false
- property real mainviewTimePerPixel: zoomControl.rangeDuration / root.width
-
signal updateCursorPosition
property string fileName: ""
property int lineNumber: -1
property int columnNumber: 0
- property int typeId: -1
property int selectedModel: -1
property int selectedItem: -1
property bool selectionRangeMode: false
-
property bool selectionRangeReady: selectionRange.ready
property real selectionRangeStart: selectionRange.startTime
property real selectionRangeEnd: selectionRange.startTime + selectionRange.duration
+ property int typeId: content.typeId
+
+ onTypeIdChanged: updateCursorPosition()
color: "#dcdcdc"
@@ -64,10 +60,23 @@ Rectangle {
target: zoomControl
onRangeChanged: {
zoomSliderToolBar.updateZoomLevel();
- flick.updateWindow();
+ content.scroll();
+
+ // If you select something in the main view and then resize the current range by some
+ // other means, the selection should stay where it was.
+ var oldTimePerPixel = selectionRange.viewTimePerPixel;
+ selectionRange.viewTimePerPixel = zoomControl.rangeDuration / content.width;
+ if (selectionRange.creationState === selectionRange.creationFinished &&
+ oldTimePerPixel != selectionRange.viewTimePerPixel) {
+ var newWidth = selectionRange.rangeWidth * oldTimePerPixel /
+ selectionRange.viewTimePerPixel;
+ selectionRange.rangeLeft = selectionRange.rangeLeft * oldTimePerPixel /
+ selectionRange.viewTimePerPixel;
+ selectionRange.rangeRight = selectionRange.rangeLeft + newWidth;
+ }
}
onWindowChanged: {
- flick.updateWindow();
+ content.scroll();
}
}
@@ -75,30 +84,20 @@ Rectangle {
Connections {
target: qmlProfilerModelProxy
onDataAvailable: {
- timelineView.clearChildren();
+ content.clearChildren();
zoomControl.setRange(zoomControl.traceStart,
zoomControl.traceStart + zoomControl.traceDuration / 10);
}
}
+ onSelectionRangeModeChanged: {
+ selectionRange.reset();
+ buttonsBar.updateRangeButton(selectionRangeMode);
+ }
// ***** functions
- function gotoSourceLocation(file,line,column) {
- if (file !== undefined) {
- root.fileName = file;
- root.lineNumber = line;
- root.columnNumber = column;
- }
- }
-
- function recenterOnItem() {
- timelineView.select(selectedModel, selectedItem);
- }
-
function clear() {
- flick.contentY = 0;
- flick.contentX = 0;
- timelineView.clearChildren();
+ content.clearChildren();
rangeDetails.hide();
selectionRangeMode = false;
zoomSlider.externalUpdate = true;
@@ -106,33 +105,6 @@ Rectangle {
overview.clear();
}
- function propagateSelection(newModel, newItem) {
- if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem))
- return;
-
- lockItemSelection = true;
- if (selectedModel !== -1 && selectedModel !== newModel)
- timelineView.select(selectedModel, -1);
-
- selectedItem = newItem
- selectedModel = newModel
- if (selectedItem !== -1) {
- // display details
- rangeDetails.showInfo(selectedModel, selectedItem);
-
- // update in other views
- var model = qmlProfilerModelProxy.models[selectedModel];
- var eventLocation = model.location(selectedItem);
- gotoSourceLocation(eventLocation.file, eventLocation.line,
- eventLocation.column);
- typeId = model.typeId(selectedItem);
- updateCursorPosition();
- } else {
- rangeDetails.hide();
- }
- lockItemSelection = false;
- }
-
function enableButtonsBar(enable) {
buttonsBar.enabled = enable;
}
@@ -166,152 +138,32 @@ Rectangle {
if (itemIndex !== -1) {
// select an item, lock to it, and recenter if necessary
- timelineView.select(modelIndex, itemIndex);
- root.selectionLocked = true;
+ content.select(modelIndex, itemIndex);
+ content.selectionLocked = true;
}
}
- // ***** slots
- onSelectionRangeModeChanged: {
- selectionRangeControl.enabled = selectionRangeMode;
- selectionRange.reset();
- buttonsBar.updateRangeButton(selectionRangeMode);
- }
-
- onSelectionLockedChanged: {
- buttonsBar.updateLockButton(selectionLocked);
- }
-
focus: true
property bool shiftPressed: false;
Keys.onPressed: shiftPressed = (event.key === Qt.Key_Shift);
Keys.onReleased: shiftPressed = false;
- Flickable {
+ TimelineLabels {
id: categories
- flickableDirection: Flickable.VerticalFlick
- interactive: false
anchors.top: buttonsBar.bottom
anchors.bottom: overview.top
anchors.left: parent.left
anchors.right: parent.right
- contentY: flick.contentY
+ contentY: content.contentY
+ selectedModel: root.selectedModel
+ selectedItem: root.selectedItem
+ color: root.color
+ modelProxy: qmlProfilerModelProxy
+ zoomer: zoomControl
+ reverseSelect: shiftPressed
- // reserve some more space than needed to prevent weird effects when resizing
- contentHeight: categoryContent.height + height
-
- // 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
-
- Column {
- id: categoryContent
- anchors.left: parent.left
- anchors.right: parent.right
-
- DelegateModel {
- id: labelsModel
-
- // As we cannot retrieve items by visible index we keep an array of row counts here,
- // for the time marks to draw the row backgrounds in the right colors.
- property var rowCounts: new Array(qmlProfilerModelProxy.models.length)
-
- function updateRowCount(visualIndex, rowCount) {
- if (rowCounts[visualIndex] !== rowCount) {
- rowCounts[visualIndex] = rowCount;
- // Array don't "change" if entries change. We have to signal manually.
- rowCountsChanged();
- }
- }
-
- model: qmlProfilerModelProxy.models
- delegate: Rectangle {
- color: root.color
-
- anchors.left: parent.left
- anchors.right: parent.right
- property int visualIndex: DelegateModel.itemsIndex
- height: label.visible ? label.height : 0
-
- CategoryLabel {
- id: label
- model: modelData
- mockup: qmlProfilerModelProxy.height === 0
- visualIndex: parent.visualIndex
- dragging: categories.dragging
- reverseSelect: root.shiftPressed
- onDragStarted: categories.dragging = true
- onDragStopped: categories.dragging = false
- draggerParent: categories
- width: 150
- dragOffset: parent.y
-
- onDropped: {
- timelineModel.items.move(sourceIndex, targetIndex);
- labelsModel.items.move(sourceIndex, targetIndex);
- }
-
- onSelectById: {
- timelineView.select(index, eventId)
- }
-
- onSelectNextBySelectionId: {
- timelineView.select(index, modelData.nextItemBySelectionId(selectionId,
- zoomControl.rangeStart,
- root.selectedModel === index ? root.selectedItem : -1));
- }
-
- onSelectPrevBySelectionId: {
- timelineView.select(index, modelData.prevItemBySelectionId(selectionId,
- zoomControl.rangeStart,
- root.selectedModel === index ? root.selectedItem : -1));
- }
- }
-
- TimeMarks {
- id: timeMarks
- model: modelData
- mockup: qmlProfilerModelProxy.height === 0
- anchors.right: parent.right
- anchors.left: label.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- property int visualIndex: parent.visualIndex
-
- // Quite a mouthful, but works fine: Add up all the row counts up to the one
- // for this visual index and check if the result is even or odd.
- startOdd: (labelsModel.rowCounts.slice(0, visualIndex).reduce(
- function(prev, rows) {return prev + rows}, 0) % 2) === 0
-
- onRowCountChanged: labelsModel.updateRowCount(visualIndex, rowCount)
- onVisualIndexChanged: labelsModel.updateRowCount(visualIndex, rowCount)
- }
-
- Rectangle {
- visible: label.visible
- opacity: parent.y == 0 ? 0 : 1
- color: "#B0B0B0"
- height: 1
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- }
- }
- }
-
- Repeater {
- model: labelsModel
- }
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: categoryContent.bottom
- height: 1
- color: "#B0B0B0"
- }
+ onMoveCategories: content.moveCategories(sourceIndex, targetIndex)
+ onSelectItem: content.select(modelIndex, eventIndex)
}
TimeDisplay {
@@ -321,7 +173,7 @@ Rectangle {
anchors.right: parent.right
anchors.bottom: overview.top
zoomer: zoomControl
- contentX: flick.contentX
+ contentX: content.contentX
clip: true
}
@@ -337,217 +189,173 @@ Rectangle {
onJumpToNext: {
var next = qmlProfilerModelProxy.nextItem(root.selectedModel, root.selectedItem,
zoomControl.rangeStart);
- timelineView.select(next.model, next.item);
+ content.select(next.model, next.item);
}
onJumpToPrev: {
var prev = qmlProfilerModelProxy.prevItem(root.selectedModel, root.selectedItem,
zoomControl.rangeEnd);
- timelineView.select(prev.model, prev.item);
+ content.select(prev.model, prev.item);
}
onRangeSelectChanged: selectionRangeMode = rangeButtonChecked();
- onLockChanged: selectionLocked = !lockButtonChecked();
+ onLockChanged: content.selectionLocked = !lockButtonChecked();
+ }
+
+ TimelineContent {
+ id: content
+ anchors.left: buttonsBar.right
+ anchors.top: buttonsBar.bottom
+ anchors.bottom: overview.top
+ anchors.right: parent.right
+ selectionLocked: true
+ zoomer: zoomControl
+ modelProxy: qmlProfilerModelProxy
+
+ onSelectionLockedChanged: {
+ buttonsBar.updateLockButton(selectionLocked);
+ }
+
+ onPropagateSelection: {
+ if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem))
+ return;
+
+ lockItemSelection = true;
+ if (selectedModel !== -1 && selectedModel !== newModel)
+ select(selectedModel, -1);
+
+ selectedItem = newItem
+ selectedModel = newModel
+ if (selectedItem !== -1) {
+ // display details
+ rangeDetails.showInfo(selectedModel, selectedItem);
+
+ // update in other views
+ var model = qmlProfilerModelProxy.models[selectedModel];
+ var eventLocation = model.location(selectedItem);
+ gotoSourceLocation(eventLocation.file, eventLocation.line,
+ eventLocation.column);
+ typeId = model.typeId(selectedItem);
+ } else {
+ rangeDetails.hide();
+ }
+ lockItemSelection = false;
+ }
+
+ onGotoSourceLocation: {
+ if (file !== undefined) {
+ root.fileName = file;
+ root.lineNumber = line;
+ root.columnNumber = column;
+ }
+ }
+ }
+
+ MouseArea {
+ id: selectionRangeControl
+ enabled: selectionRangeMode &&
+ selectionRange.creationState !== selectionRange.creationFinished
+ anchors.right: content.right
+ anchors.left: buttonsBar.right
+ anchors.top: content.top
+ anchors.bottom: content.bottom
+ hoverEnabled: enabled
+ z: 2
+
+ onReleased: {
+ if (selectionRange.creationState === selectionRange.creationSecondLimit) {
+ content.stayInteractive = true;
+ selectionRange.creationState = selectionRange.creationFinished;
+ }
+ }
+ onPressed: {
+ if (selectionRange.creationState === selectionRange.creationFirstLimit) {
+ content.stayInteractive = false;
+ selectionRange.setPos(selectionRangeControl.mouseX + content.contentX);
+ selectionRange.creationState = selectionRange.creationSecondLimit;
+ }
+ }
+ onPositionChanged: {
+ if (selectionRange.creationState === selectionRange.creationInactive)
+ selectionRange.creationState = selectionRange.creationFirstLimit;
+
+ if (selectionRangeControl.pressed ||
+ selectionRange.creationState !== selectionRange.creationFinished)
+ selectionRange.setPos(selectionRangeControl.mouseX + content.contentX);
+ }
+ onCanceled: pressed()
}
Flickable {
- id: flick
- contentHeight: categoryContent.height
- flickableDirection: Flickable.HorizontalAndVerticalFlick
- boundsBehavior: Flickable.StopAtBounds
- pixelAligned: true
+ flickableDirection: Flickable.HorizontalFlick
+ clip: true
+ visible: selectionRangeMode &&
+ selectionRange.creationState !== selectionRange.creationInactive
+ interactive: false
+ x: content.x + content.flickableItem.x
+ y: content.y + content.flickableItem.y
+ height: content.flickableItem.height
+ width: content.flickableItem.width
+ contentX: content.contentX
+ contentWidth: content.contentWidth
- // ScrollView will try to deinteractivate it. We don't want that
- // as the horizontal flickable is interactive, too. We do occasionally
- // switch to non-interactive ourselves, though.
- property bool stayInteractive: true
- onInteractiveChanged: interactive = stayInteractive
- onStayInteractiveChanged: interactive = stayInteractive
-
- property bool recursionGuard: false
-
-
- // Update the zoom control on srolling.
- onContentXChanged: {
- if (recursionGuard)
- return;
-
- recursionGuard = true;
-
- var newStartTime = contentX * zoomControl.rangeDuration / scroller.width +
- zoomControl.windowStart;
- if (isFinite(newStartTime) && Math.abs(newStartTime - zoomControl.rangeStart) >= 1) {
- var newEndTime = (contentX + scroller.width) * zoomControl.rangeDuration /
- scroller.width + zoomControl.windowStart;
- if (isFinite(newEndTime))
- zoomControl.setRange(newStartTime, newEndTime);
- }
- recursionGuard = false;
- }
-
- // Scroll when the zoom control is updated
- function updateWindow() {
- if (recursionGuard || zoomControl.rangeDuration <= 0)
- return;
- recursionGuard = true;
-
- var newWidth = zoomControl.windowDuration * scroller.width /
- zoomControl.rangeDuration;
- if (isFinite(newWidth) && Math.abs(newWidth - contentWidth) >= 1)
- contentWidth = newWidth;
-
- var newStartX = (zoomControl.rangeStart - zoomControl.windowStart) * scroller.width /
- zoomControl.rangeDuration;
- if (isFinite(newStartX) && Math.abs(newStartX - contentX) >= 1)
- contentX = newStartX;
-
- recursionGuard = false;
- }
-
- // ***** child items
SelectionRange {
id: selectionRange
- visible: root.selectionRangeMode && creationState !== 0
- z: 2
+ zoomer: zoomControl
+ visible: parent.visible
+
+ onRangeDoubleClicked: {
+ zoomControl.setRange(startTime, endTime);
+ root.selectionRangeMode = false;
+ }
+
}
-
- Column {
- id: timelineView
-
- signal clearChildren
- signal select(int modelIndex, int eventIndex)
-
- DelegateModel {
- id: timelineModel
- model: qmlProfilerModelProxy.models
- delegate: TimelineRenderer {
- id: renderer
- model: modelData
- notes: qmlProfilerModelProxy.notes
- zoomer: zoomControl
- selectionLocked: root.selectionLocked
- x: 0
-
- height: modelData.height
- property int visualIndex: DelegateModel.itemsIndex
-
- // paint "under" the vertical scrollbar, so that it always matches with the
- // timemarks
- width: flick.contentWidth
-
- Connections {
- target: timelineView
- onClearChildren: renderer.clearData()
- onSelect: {
- if (modelIndex === index || modelIndex === -1) {
- renderer.selectedItem = eventIndex;
- if (eventIndex !== -1)
- renderer.recenter();
- }
- }
- }
-
- Connections {
- target: root
- onSelectionLockedChanged: {
- renderer.selectionLocked = root.selectionLocked;
- }
- }
-
- onSelectionLockedChanged: {
- root.selectionLocked = renderer.selectionLocked;
- }
-
- function recenter() {
- if (modelData.endTime(selectedItem) < zoomer.rangeStart ||
- modelData.startTime(selectedItem) > zoomer.rangeEnd) {
-
- var newStart = (modelData.startTime(selectedItem) +
- modelData.endTime(selectedItem) -
- zoomer.rangeDuration) / 2;
- zoomer.setRange(Math.max(newStart, zoomer.traceStart),
- Math.min(newStart + zoomer.rangeDuration,
- zoomer.traceEnd));
- }
-
- var row = modelData.row(selectedItem);
- var rowStart = modelData.rowOffset(row) + y;
- var rowEnd = rowStart + modelData.rowHeight(row);
- if (rowStart < flick.contentY || rowEnd - scroller.height > flick.contentY)
- flick.contentY = (rowStart + rowEnd - scroller.height) / 2;
- }
-
- onSelectedItemChanged: {
- root.propagateSelection(index, selectedItem);
- }
-
- onItemPressed: {
- if (pressedItem === -1) {
- // User clicked on empty space. Remove selection.
- root.propagateSelection(-1, -1);
- } else {
- var location = model.location(pressedItem);
- if (location.hasOwnProperty("file")) // not empty
- root.gotoSourceLocation(location.file, location.line, location.column);
- root.typeId = model.typeId(pressedItem);
- root.updateCursorPosition();
- }
- }
- }
- }
-
- Repeater {
- id: repeater
- model: timelineModel
- }
- }
- MouseArea {
- id: selectionRangeControl
- enabled: false
- width: flick.width
- height: flick.height
- x: flick.contentX
- y: flick.contentY
- hoverEnabled: enabled
- z: 2
-
- onReleased: {
- selectionRange.releasedOnCreation();
- }
- onPressed: {
- selectionRange.pressedOnCreation();
- }
- onCanceled: {
- selectionRange.releasedOnCreation();
- }
- onPositionChanged: {
- selectionRange.movedOnCreation();
- }
- }
- }
-
- ScrollView {
- id: scroller
- contentItem: flick
- anchors.left: buttonsBar.right
- anchors.top: categories.top
- anchors.bottom: overview.top
- anchors.right: parent.right
}
SelectionRangeDetails {
+ z: 1
+ x: 200
+ y: 125
+
id: selectionRangeDetails
visible: selectionRange.visible
- startTime: selectionRange.startTimeString
- duration: selectionRange.durationString
- endTime: selectionRange.endTimeString
+ startTime: selectionRange.startTime
+ duration: selectionRange.duration
+ endTime: selectionRange.endTime
showDuration: selectionRange.rangeWidth > 1
+
+ onRecenter: {
+ if ((selectionRange.startTime < zoomControl.rangeStart) ^
+ (selectionRange.endTime > zoomControl.rangeEnd)) {
+ var center = selectionRange.startTime + selectionRange.duration / 2;
+ var halfDuration = Math.max(selectionRange.duration, zoomControl.rangeDuration / 2);
+ zoomControl.setRange(center - halfDuration, center + halfDuration);
+ }
+ }
+
+ onClose: selectionRangeMode = false;
}
RangeDetails {
id: rangeDetails
- property alias locked: root.selectionLocked
+
+ z: 1
+ visible: false
+ x: 200
+ y: 25
+
+ property alias locked: content.selectionLocked
models: qmlProfilerModelProxy.models
notes: qmlProfilerModelProxy.notes
+ onRecenterOnItem: {
+ content.gotoSourceLocation(file, line, column);
+ content.select(selectedModel, selectedItem)
+ }
+ onToggleSelectionLocked: {
+ content.selectionLocked = !content.selectionLocked;
+ }
+ onClearSelection: {
+ content.propagateSelection(-1, -1);
+ }
}
Rectangle {
@@ -591,7 +399,7 @@ Rectangle {
color: "#9b9b9b"
enabled: buttonsBar.enabled
visible: false
- width: categoryContent.width
+ width: buttonsBar.width
height: buttonsBar.height
anchors.left: parent.left
anchors.top: buttonsBar.bottom
@@ -603,7 +411,6 @@ Rectangle {
1 / zoomSlider.exponent) * zoomSlider.maximumValue;
}
-
Slider {
id: zoomSlider
anchors.fill: parent
@@ -651,5 +458,7 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
+ modelProxy: qmlProfilerModelProxy
+ zoomer: zoomControl
}
}
diff --git a/src/plugins/qmlprofiler/qml/Overview.qml b/src/plugins/qmlprofiler/qml/Overview.qml
index d1db5814729..3fd01825d0b 100644
--- a/src/plugins/qmlprofiler/qml/Overview.qml
+++ b/src/plugins/qmlprofiler/qml/Overview.qml
@@ -37,12 +37,15 @@ Canvas {
objectName: "Overview"
contextType: "2d"
+ property QtObject modelProxy
+ property QtObject zoomer
+
readonly property int eventsPerPass: 512
property int increment: -1
property int offset: -1
readonly property int bump: 10;
- readonly property int blockHeight: (height - bump) / qmlProfilerModelProxy.models.length;
- readonly property double spacing: width / zoomControl.traceDuration
+ readonly property int blockHeight: (height - bump) / modelProxy.models.length;
+ readonly property double spacing: width / zoomer.traceDuration
// ***** properties
height: 50
@@ -64,12 +67,12 @@ Canvas {
if (recursionGuard)
return;
recursionGuard = true;
- var newStartTime = rangeMover.rangeLeft * zoomControl.traceDuration / width +
- zoomControl.traceStart;
- var newEndTime = rangeMover.rangeRight * zoomControl.traceDuration / width +
- zoomControl.traceStart;
+ var newStartTime = rangeMover.rangeLeft * zoomer.traceDuration / width +
+ zoomer.traceStart;
+ var newEndTime = rangeMover.rangeRight * zoomer.traceDuration / width +
+ zoomer.traceStart;
if (isFinite(newStartTime) && isFinite(newEndTime) && newEndTime - newStartTime > 500)
- zoomControl.setRange(newStartTime, newEndTime);
+ zoomer.setRange(newStartTime, newEndTime);
recursionGuard = false;
}
@@ -79,14 +82,14 @@ Canvas {
// ***** connections to external objects
Connections {
- target: zoomControl
+ target: zoomer
onRangeChanged: {
if (recursionGuard)
return;
recursionGuard = true;
- var newRangeX = (zoomControl.rangeStart - zoomControl.traceStart) * width /
- zoomControl.traceDuration;
- var newWidth = zoomControl.rangeDuration * width / zoomControl.traceDuration;
+ var newRangeX = (zoomer.rangeStart - zoomer.traceStart) * width /
+ zoomer.traceDuration;
+ var newWidth = zoomer.rangeDuration * width / zoomer.traceDuration;
var widthChanged = Math.abs(newWidth - rangeMover.rangeWidth) > 1;
var leftChanged = Math.abs(newRangeX - rangeMover.rangeLeft) > 1;
if (leftChanged)
@@ -99,11 +102,11 @@ Canvas {
}
Connections {
- target: qmlProfilerModelProxy
+ target: modelProxy
onDataAvailable: {
dataReady = true;
increment = 0;
- var models = qmlProfilerModelProxy.models;
+ var models = modelProxy.models;
for (var i = 0; i < models.length; ++i)
increment += models[i].count;
increment = Math.ceil(increment / eventsPerPass);
@@ -125,9 +128,9 @@ Canvas {
onPaint: {
var context = (canvas.context === null) ? getContext("2d") : canvas.context;
- Plotter.models = qmlProfilerModelProxy.models;
- Plotter.zoomControl = zoomControl;
- Plotter.notes = qmlProfilerModelProxy.notes;
+ Plotter.models = modelProxy.models;
+ Plotter.zoomControl = zoomer;
+ Plotter.notes = modelProxy.notes;
if (offset < 0) {
context.reset();
diff --git a/src/plugins/qmlprofiler/qml/RangeDetails.qml b/src/plugins/qmlprofiler/qml/RangeDetails.qml
index 5719bcd248a..7bab74a0cfe 100644
--- a/src/plugins/qmlprofiler/qml/RangeDetails.qml
+++ b/src/plugins/qmlprofiler/qml/RangeDetails.qml
@@ -50,12 +50,12 @@ Item {
property var models
property var notes
+ signal recenterOnItem
+ signal toggleSelectionLocked
+ signal clearSelection
+
width: col.width + 25
height: col.height + 30
- z: 1
- visible: false
- x: 200
- y: 25
function hide() {
noteEdit.focus = false;
@@ -70,9 +70,9 @@ Item {
isBindingLoop = false;
}
- // keep inside view
Connections {
- target: root
+ target: rangeDetails.parent
+ // keep inside view
onWidthChanged: fitInView();
onHeightChanged: fitInView();
}
@@ -119,15 +119,15 @@ Item {
function fitInView() {
// don't reposition if it does not fit
- if (root.width < width || root.height < height)
+ if (parent.width < width || parent.height < height)
return;
- if (x + width > root.width)
- x = root.width - width;
+ if (x + width > parent.width)
+ x = parent.width - width;
if (x < 0)
x = 0;
- if (y + height > root.height)
- y = root.height - height;
+ if (y + height > parent.height)
+ y = parent.height - height;
if (y < 0)
y = 0;
}
@@ -249,13 +249,10 @@ Item {
anchors.fill: parent
drag.target: parent
drag.minimumX: 0
- drag.maximumX: root.width - parent.width
+ drag.maximumX: rangeDetails.parent.width - rangeDetails.width
drag.minimumY: 0
- drag.maximumY: root.height - parent.height
- onClicked: {
- root.gotoSourceLocation(file, line, column);
- root.recenterOnItem();
- }
+ drag.maximumY: rangeDetails.parent.height - rangeDetails.height
+ onClicked: rangeDetails.recenterOnItem()
}
Image {
@@ -282,9 +279,7 @@ Item {
height: 12
MouseArea {
anchors.fill: parent
- onClicked: {
- root.selectionLocked = !root.selectionLocked;
- }
+ onClicked: rangeDetails.toggleSelectionLocked()
}
}
@@ -298,7 +293,7 @@ Item {
renderType: Text.NativeRendering
MouseArea {
anchors.fill: parent
- onClicked: root.propagateSelection(-1, -1);
+ onClicked: rangeDetails.clearSelection()
}
}
diff --git a/src/plugins/qmlprofiler/qml/SelectionRange.qml b/src/plugins/qmlprofiler/qml/SelectionRange.qml
index 84a5d389fe1..5e05f15ec42 100644
--- a/src/plugins/qmlprofiler/qml/SelectionRange.qml
+++ b/src/plugins/qmlprofiler/qml/SelectionRange.qml
@@ -32,42 +32,26 @@ import QtQuick 2.1
RangeMover {
id: selectionRange
+ property QtObject zoomer;
- property bool ready: visible && creationState === 3
+ readonly property int creationInactive: 0
+ readonly property int creationFirstLimit: 1
+ readonly property int creationSecondLimit: 2
+ readonly property int creationFinished: 3
- property string startTimeString: detailedPrintTime(startTime)
- property string endTimeString: detailedPrintTime(endTime)
- property string durationString: detailedPrintTime(duration)
+ property bool ready: visible && creationState === creationFinished
- property double startTime: rangeLeft * viewTimePerPixel + zoomControl.windowStart
+ property double startTime: rangeLeft * viewTimePerPixel + zoomer.windowStart
property double duration: Math.max(rangeWidth * viewTimePerPixel, 500)
property double endTime: startTime + duration
property double viewTimePerPixel: 1
property double creationReference : 0
- property int creationState : 0
-
- Connections {
- target: zoomControl
- onRangeChanged: {
- var oldTimePerPixel = selectionRange.viewTimePerPixel;
- selectionRange.viewTimePerPixel = zoomControl.rangeDuration / scroller.width;
- if (creationState === 3 && oldTimePerPixel != selectionRange.viewTimePerPixel) {
- var newWidth = rangeWidth * oldTimePerPixel / viewTimePerPixel;
- rangeLeft = rangeLeft * oldTimePerPixel / viewTimePerPixel;
- rangeRight = rangeLeft + newWidth;
- }
- }
- }
-
- onRangeDoubleClicked: {
- zoomControl.setRange(startTime, endTime);
- root.selectionRangeMode = false;
- }
+ property int creationState : creationInactive
function reset() {
rangeRight = rangeLeft + 1;
- creationState = 0;
+ creationState = creationInactive;
creationReference = 0;
}
@@ -78,12 +62,12 @@ RangeMover {
pos = width;
switch (creationState) {
- case 1:
+ case creationFirstLimit:
creationReference = pos;
rangeLeft = pos;
rangeRight = pos + 1;
break;
- case 2:
+ case creationSecondLimit:
if (pos > creationReference) {
rangeLeft = creationReference;
rangeRight = pos;
@@ -94,43 +78,4 @@ RangeMover {
break;
}
}
-
-
- function detailedPrintTime( t )
- {
- if (t <= 0) return "0";
- if (t<1000) return t+" ns";
- t = Math.floor(t/1000);
- if (t<1000) return t+" μs";
- if (t<1e6) return (t/1000) + " ms";
- return (t/1e6) + " s";
- }
-
- // creation control
- function releasedOnCreation() {
- if (selectionRange.creationState === 2) {
- flick.stayInteractive = true;
- selectionRange.creationState = 3;
- selectionRangeControl.enabled = false;
- }
- }
-
- function pressedOnCreation() {
- if (selectionRange.creationState === 1) {
- flick.stayInteractive = false;
- selectionRange.setPos(selectionRangeControl.mouseX + flick.contentX);
- selectionRange.creationState = 2;
- }
- }
-
- function movedOnCreation() {
- if (selectionRange.creationState === 0) {
- selectionRange.creationState = 1;
- }
-
- if (!selectionRangeControl.pressed && selectionRange.creationState==3)
- return;
-
- selectionRange.setPos(selectionRangeControl.mouseX + flick.contentX);
- }
}
diff --git a/src/plugins/qmlprofiler/qml/SelectionRangeDetails.qml b/src/plugins/qmlprofiler/qml/SelectionRangeDetails.qml
index ff7f903c3b5..9e8dfcff093 100644
--- a/src/plugins/qmlprofiler/qml/SelectionRangeDetails.qml
+++ b/src/plugins/qmlprofiler/qml/SelectionRangeDetails.qml
@@ -34,6 +34,9 @@ import Monitor 1.0
Item {
id: selectionRangeDetails
+ signal recenter
+ signal close
+
property string startTime
property string endTime
property string duration
@@ -41,29 +44,35 @@ Item {
width: Math.max(150, col.width + 25)
height: col.height + 30
- z: 1
- visible: false
- x: 200
- y: 125
// keep inside view
Connections {
- target: root
+ target: selectionRangeDetails.parent
onWidthChanged: fitInView();
onHeightChanged: fitInView();
}
+ function detailedPrintTime( t )
+ {
+ if (t <= 0) return "0";
+ if (t<1000) return t+" ns";
+ t = Math.floor(t/1000);
+ if (t<1000) return t+" μs";
+ if (t<1e6) return (t/1000) + " ms";
+ return (t/1e6) + " s";
+ }
+
function fitInView() {
// don't reposition if it does not fit
- if (root.width < width || root.height < height)
+ if (parent.width < width || parent.height < height)
return;
- if (x + width > root.width)
- x = root.width - width;
+ if (x + width > parent.width)
+ x = parent.width - width;
if (x < 0)
x = 0;
- if (y + height > root.height)
- y = root.height - height;
+ if (y + height > parent.height)
+ y = parent.height - height;
if (y < 0)
y = 0;
}
@@ -138,11 +147,11 @@ Item {
Repeater {
model: [
qsTr("Start") + ":",
- startTime,
+ detailedPrintTime(startTime),
showDuration ? (qsTr("End") + ":") : "",
- showDuration ? endTime : "",
+ showDuration ? detailedPrintTime(endTime) : "",
showDuration ? (qsTr("Duration") + ":") : "",
- showDuration ? duration : ""
+ showDuration ? detailedPrintTime(duration) : ""
]
Detail {
isLabel: index % 2 === 0
@@ -156,17 +165,10 @@ Item {
anchors.fill: parent
drag.target: parent
drag.minimumX: 0
- drag.maximumX: root.width - parent.width
+ drag.maximumX: selectionRangeDetails.parent.width - width
drag.minimumY: 0
- drag.maximumY: root.height - parent.height + 0
- onClicked: {
- if ((selectionRange.startTime < zoomControl.rangeStart) ^
- (selectionRange.endTime > zoomControl.rangeEnd)) {
- var center = selectionRange.startTime + selectionRange.duration / 2;
- var halfDuration = Math.max(selectionRange.duration, zoomControl.rangeDuration / 2);
- zoomControl.setRange(center - halfDuration, center + halfDuration);
- }
- }
+ drag.maximumY: selectionRangeDetails.parent.height - height
+ onClicked: selectionRangeDetails.recenter()
}
Text {
@@ -179,8 +181,7 @@ Item {
MouseArea {
anchors.fill: parent
anchors.leftMargin: -8
- onClicked: root.selectionRangeMode = false;
+ onClicked: selectionRangeDetails.close()
}
}
-
}
diff --git a/src/plugins/qmlprofiler/qml/TimelineContent.qml b/src/plugins/qmlprofiler/qml/TimelineContent.qml
new file mode 100644
index 00000000000..39593f9a454
--- /dev/null
+++ b/src/plugins/qmlprofiler/qml/TimelineContent.qml
@@ -0,0 +1,215 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://www.qt.io/licensing. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+import Monitor 1.0
+import QtQml.Models 2.1
+
+ScrollView {
+ id: scroller
+
+ property QtObject zoomer
+ property QtObject modelProxy
+
+ property int contentX: flick.contentX
+ property int contentY: flick.contentY
+ property int contentWidth: flick.contentWidth
+ property int contentHeight: flick.contentHeight
+ property bool selectionLocked
+ property int typeId
+
+ signal propagateSelection(int newModel, int newItem)
+ signal gotoSourceLocation(string file, int line, int column)
+
+ function clearChildren()
+ {
+ timelineView.clearChildren();
+ }
+
+ function select(modelIndex, eventIndex)
+ {
+ timelineView.select(modelIndex, eventIndex);
+ }
+
+ function moveCategories(sourceIndex, targetIndex)
+ {
+ timelineModel.items.move(sourceIndex, targetIndex)
+ }
+
+ function scroll()
+ {
+ flick.scroll();
+ }
+
+ // ScrollView will try to deinteractivate it. We don't want that
+ // as the horizontal flickable is interactive, too. We do occasionally
+ // switch to non-interactive ourselves, though.
+ property bool stayInteractive: true
+ onStayInteractiveChanged: flick.interactive = stayInteractive
+
+ Flickable {
+ id: flick
+ contentHeight: timelineView.height + height
+ flickableDirection: Flickable.HorizontalAndVerticalFlick
+ boundsBehavior: Flickable.StopAtBounds
+ pixelAligned: true
+
+ onInteractiveChanged: interactive = stayInteractive
+
+ property bool recursionGuard: false
+
+ // Update the zoom control on srolling.
+ onContentXChanged: {
+ if (recursionGuard)
+ return;
+
+ recursionGuard = true;
+
+ var newStartTime = contentX * zoomer.rangeDuration / scroller.width +
+ zoomer.windowStart;
+ if (isFinite(newStartTime) && Math.abs(newStartTime - zoomer.rangeStart) >= 1) {
+ var newEndTime = (contentX + scroller.width) * zoomer.rangeDuration /
+ scroller.width + zoomer.windowStart;
+ if (isFinite(newEndTime))
+ zoomer.setRange(newStartTime, newEndTime);
+ }
+ recursionGuard = false;
+ }
+
+ // Scroll when the zoom control is updated
+ function scroll() {
+ if (recursionGuard || zoomer.rangeDuration <= 0)
+ return;
+ recursionGuard = true;
+
+ var newWidth = zoomer.windowDuration * scroller.width / zoomer.rangeDuration;
+ if (isFinite(newWidth) && Math.abs(newWidth - contentWidth) >= 1)
+ contentWidth = newWidth;
+
+ var newStartX = (zoomer.rangeStart - zoomer.windowStart) * scroller.width /
+ zoomer.rangeDuration;
+ if (isFinite(newStartX) && Math.abs(newStartX - contentX) >= 1)
+ contentX = newStartX;
+
+ recursionGuard = false;
+ }
+
+ Column {
+ id: timelineView
+
+ signal clearChildren
+ signal select(int modelIndex, int eventIndex)
+
+ DelegateModel {
+ id: timelineModel
+ model: modelProxy.models
+ delegate: TimelineRenderer {
+ id: renderer
+ model: modelData
+ notes: modelProxy.notes
+ zoomer: scroller.zoomer
+ selectionLocked: scroller.selectionLocked
+ x: 0
+ height: modelData.height
+ property int visualIndex: DelegateModel.itemsIndex
+
+ // paint "under" the vertical scrollbar, so that it always matches with the
+ // timemarks
+ width: flick.contentWidth
+
+ Connections {
+ target: timelineView
+ onClearChildren: renderer.clearData()
+ onSelect: {
+ if (modelIndex === index || modelIndex === -1) {
+ renderer.selectedItem = eventIndex;
+ if (eventIndex !== -1)
+ renderer.recenter();
+ }
+ }
+ }
+
+ Connections {
+ target: scroller
+ onSelectionLockedChanged: {
+ renderer.selectionLocked = scroller.selectionLocked;
+ }
+ }
+
+ onSelectionLockedChanged: {
+ scroller.selectionLocked = renderer.selectionLocked;
+ }
+
+ function recenter() {
+ if (modelData.endTime(selectedItem) < zoomer.rangeStart ||
+ modelData.startTime(selectedItem) > zoomer.rangeEnd) {
+
+ var newStart = (modelData.startTime(selectedItem) +
+ modelData.endTime(selectedItem) -
+ zoomer.rangeDuration) / 2;
+ zoomer.setRange(Math.max(newStart, zoomer.traceStart),
+ Math.min(newStart + zoomer.rangeDuration,
+ zoomer.traceEnd));
+ }
+
+ var row = modelData.row(selectedItem);
+ var rowStart = modelData.rowOffset(row) + y;
+ var rowEnd = rowStart + modelData.rowHeight(row);
+ if (rowStart < flick.contentY || rowEnd - scroller.height > flick.contentY)
+ flick.contentY = (rowStart + rowEnd - scroller.height) / 2;
+ }
+
+ onSelectedItemChanged: {
+ scroller.propagateSelection(index, selectedItem);
+ }
+
+ onItemPressed: {
+ if (pressedItem === -1) {
+ // User clicked on empty space. Remove selection.
+ scroller.propagateSelection(-1, -1);
+ } else {
+ var location = model.location(pressedItem);
+ if (location.hasOwnProperty("file")) // not empty
+ scroller.gotoSourceLocation(location.file, location.line,
+ location.column);
+ scroller.typeId = model.typeId(pressedItem);
+ }
+ }
+ }
+ }
+
+ Repeater {
+ id: repeater
+ model: timelineModel
+ }
+ }
+ }
+}
diff --git a/src/plugins/qmlprofiler/qml/TimelineLabels.qml b/src/plugins/qmlprofiler/qml/TimelineLabels.qml
new file mode 100644
index 00000000000..34f0accad3b
--- /dev/null
+++ b/src/plugins/qmlprofiler/qml/TimelineLabels.qml
@@ -0,0 +1,165 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://www.qt.io/licensing. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQml.Models 2.1
+
+Flickable {
+ id: categories
+ flickableDirection: Flickable.VerticalFlick
+ interactive: false
+ property color color
+
+ property int selectedModel
+ property int selectedItem
+ property bool reverseSelect
+ property QtObject modelProxy
+ property QtObject zoomer
+
+ signal selectItem(int modelIndex, int eventIndex)
+ signal moveCategories(int sourceIndex, int targetIndex)
+
+ // reserve some more space than needed to prevent weird effects when resizing
+ contentHeight: categoryContent.height + height
+
+ // 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
+
+ Column {
+ id: categoryContent
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ DelegateModel {
+ id: labelsModel
+
+ // As we cannot retrieve items by visible index we keep an array of row counts here,
+ // for the time marks to draw the row backgrounds in the right colors.
+ property var rowCounts: new Array(modelProxy.models.length)
+
+ function updateRowCount(visualIndex, rowCount) {
+ if (rowCounts[visualIndex] !== rowCount) {
+ rowCounts[visualIndex] = rowCount;
+ // Array don't "change" if entries change. We have to signal manually.
+ rowCountsChanged();
+ }
+ }
+
+ model: modelProxy.models
+ delegate: Rectangle {
+ color: categories.color
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ property int visualIndex: DelegateModel.itemsIndex
+ height: label.visible ? label.height : 0
+
+ CategoryLabel {
+ id: label
+ model: modelData
+ mockup: modelProxy.height === 0
+ notesModel: modelProxy.notes
+ visualIndex: parent.visualIndex
+ dragging: categories.dragging
+ reverseSelect: categories.reverseSelect
+ onDragStarted: categories.dragging = true
+ onDragStopped: categories.dragging = false
+ draggerParent: categories
+ width: 150
+ dragOffset: parent.y
+
+ onDropped: {
+ categories.moveCategories(sourceIndex, targetIndex);
+ labelsModel.items.move(sourceIndex, targetIndex);
+ }
+
+ onSelectById: {
+ categories.selectItem(index, eventId)
+ }
+
+ onSelectNextBySelectionId: {
+ categories.selectItem(index, modelData.nextItemBySelectionId(selectionId,
+ zoomer.rangeStart,
+ categories.selectedModel === index ? categories.selectedItem : -1));
+ }
+
+ onSelectPrevBySelectionId: {
+ categories.selectItem(index, modelData.prevItemBySelectionId(selectionId,
+ zoomer.rangeStart,
+ categories.selectedModel === index ? categories.selectedItem : -1));
+ }
+ }
+
+ TimeMarks {
+ id: timeMarks
+ model: modelData
+ mockup: modelProxy.height === 0
+ anchors.right: parent.right
+ anchors.left: label.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ property int visualIndex: parent.visualIndex
+
+ // Quite a mouthful, but works fine: Add up all the row counts up to the one
+ // for this visual index and check if the result is even or odd.
+ startOdd: (labelsModel.rowCounts.slice(0, visualIndex).reduce(
+ function(prev, rows) {return prev + rows}, 0) % 2) === 0
+
+ onRowCountChanged: labelsModel.updateRowCount(visualIndex, rowCount)
+ onVisualIndexChanged: labelsModel.updateRowCount(visualIndex, rowCount)
+ }
+
+ Rectangle {
+ visible: label.visible
+ opacity: parent.y == 0 ? 0 : 1
+ color: "#B0B0B0"
+ height: 1
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ }
+ }
+ }
+
+ Repeater {
+ model: labelsModel
+ }
+ }
+
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: categoryContent.bottom
+ height: 1
+ color: "#B0B0B0"
+ }
+}
diff --git a/src/plugins/qmlprofiler/qml/qmlprofiler.qrc b/src/plugins/qmlprofiler/qml/qmlprofiler.qrc
index 70c6dc13549..7eb625495c0 100644
--- a/src/plugins/qmlprofiler/qml/qmlprofiler.qrc
+++ b/src/plugins/qmlprofiler/qml/qmlprofiler.qrc
@@ -36,5 +36,7 @@
bindingloops.frag
notes.vert
notes.frag
+ TimelineLabels.qml
+ TimelineContent.qml
diff --git a/src/plugins/qmlprofiler/qmlprofiler.pro b/src/plugins/qmlprofiler/qmlprofiler.pro
index c96ce91d51c..75e90ce924f 100644
--- a/src/plugins/qmlprofiler/qmlprofiler.pro
+++ b/src/plugins/qmlprofiler/qmlprofiler.pro
@@ -96,6 +96,8 @@ DISTFILES += \
qml/RangeDetails.qml \
qml/RangeMover.qml \
qml/TimeDisplay.qml \
+ qml/TimelineContent.qml \
+ qml/TimelineLabels.qml \
qml/TimeMarks.qml \
qml/SelectionRange.qml \
qml/SelectionRangeDetails.qml \
diff --git a/src/plugins/qmlprofiler/qmlprofiler.qbs b/src/plugins/qmlprofiler/qmlprofiler.qbs
index f7c67c847a4..c309383a92d 100644
--- a/src/plugins/qmlprofiler/qmlprofiler.qbs
+++ b/src/plugins/qmlprofiler/qmlprofiler.qbs
@@ -77,6 +77,8 @@ QtcPlugin {
"SelectionRange.qml",
"SelectionRangeDetails.qml",
"TimeDisplay.qml",
+ "TimelineContent.qml",
+ "TimelineLabels.qml",
"TimeMarks.qml",
"qmlprofiler.qrc",