QmlProfiler: Untangle QML code

Split it up into some more files and remove all implicit cross
referencing by ID.

Change-Id: I3f829d6701906b1b054d18680d9e670b35b1716a
Reviewed-by: Kai Koehne <kai.koehne@theqtcompany.com>
This commit is contained in:
Ulf Hermann
2014-12-05 16:00:03 +01:00
parent 56a5ac367b
commit 13c5cee372
11 changed files with 641 additions and 502 deletions

View File

@@ -36,6 +36,7 @@ Item {
id: labelContainer id: labelContainer
property QtObject model property QtObject model
property QtObject notesModel
property bool mockup property bool mockup
property string text: model ? model.displayName : "" property string text: model ? model.displayName : ""
property bool expanded: model && model.expanded property bool expanded: model && model.expanded
@@ -192,9 +193,8 @@ Item {
property var eventIds: [] property var eventIds: []
property var texts: [] property var texts: []
property int currentNote: -1 property int currentNote: -1
property var notesModel: qmlProfilerModelProxy.notes
Connections { Connections {
target: notesButton.notesModel target: notesModel
onChanged: { onChanged: {
if (arguments[1] === -1 || arguments[1] === model.modelId) if (arguments[1] === -1 || arguments[1] === model.modelId)
notesButton.updateNotes(); notesButton.updateNotes();

View File

@@ -29,33 +29,29 @@
****************************************************************************/ ****************************************************************************/
import QtQuick 2.1 import QtQuick 2.1
import Monitor 1.0
import QtQuick.Controls 1.0 import QtQuick.Controls 1.0
import QtQml.Models 2.1
Rectangle { Rectangle {
id: root id: root
// ***** properties // ***** properties
property bool selectionLocked : true
property bool lockItemSelection : false property bool lockItemSelection : false
property real mainviewTimePerPixel: zoomControl.rangeDuration / root.width
signal updateCursorPosition signal updateCursorPosition
property string fileName: "" property string fileName: ""
property int lineNumber: -1 property int lineNumber: -1
property int columnNumber: 0 property int columnNumber: 0
property int typeId: -1
property int selectedModel: -1 property int selectedModel: -1
property int selectedItem: -1 property int selectedItem: -1
property bool selectionRangeMode: false property bool selectionRangeMode: false
property bool selectionRangeReady: selectionRange.ready property bool selectionRangeReady: selectionRange.ready
property real selectionRangeStart: selectionRange.startTime property real selectionRangeStart: selectionRange.startTime
property real selectionRangeEnd: selectionRange.startTime + selectionRange.duration property real selectionRangeEnd: selectionRange.startTime + selectionRange.duration
property int typeId: content.typeId
onTypeIdChanged: updateCursorPosition()
color: "#dcdcdc" color: "#dcdcdc"
@@ -64,10 +60,23 @@ Rectangle {
target: zoomControl target: zoomControl
onRangeChanged: { onRangeChanged: {
zoomSliderToolBar.updateZoomLevel(); 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: { onWindowChanged: {
flick.updateWindow(); content.scroll();
} }
} }
@@ -75,30 +84,20 @@ Rectangle {
Connections { Connections {
target: qmlProfilerModelProxy target: qmlProfilerModelProxy
onDataAvailable: { onDataAvailable: {
timelineView.clearChildren(); content.clearChildren();
zoomControl.setRange(zoomControl.traceStart, zoomControl.setRange(zoomControl.traceStart,
zoomControl.traceStart + zoomControl.traceDuration / 10); zoomControl.traceStart + zoomControl.traceDuration / 10);
} }
} }
onSelectionRangeModeChanged: {
selectionRange.reset();
buttonsBar.updateRangeButton(selectionRangeMode);
}
// ***** functions // ***** 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() { function clear() {
flick.contentY = 0; content.clearChildren();
flick.contentX = 0;
timelineView.clearChildren();
rangeDetails.hide(); rangeDetails.hide();
selectionRangeMode = false; selectionRangeMode = false;
zoomSlider.externalUpdate = true; zoomSlider.externalUpdate = true;
@@ -106,33 +105,6 @@ Rectangle {
overview.clear(); 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) { function enableButtonsBar(enable) {
buttonsBar.enabled = enable; buttonsBar.enabled = enable;
} }
@@ -166,152 +138,32 @@ Rectangle {
if (itemIndex !== -1) { if (itemIndex !== -1) {
// select an item, lock to it, and recenter if necessary // select an item, lock to it, and recenter if necessary
timelineView.select(modelIndex, itemIndex); content.select(modelIndex, itemIndex);
root.selectionLocked = true; content.selectionLocked = true;
} }
} }
// ***** slots
onSelectionRangeModeChanged: {
selectionRangeControl.enabled = selectionRangeMode;
selectionRange.reset();
buttonsBar.updateRangeButton(selectionRangeMode);
}
onSelectionLockedChanged: {
buttonsBar.updateLockButton(selectionLocked);
}
focus: true focus: true
property bool shiftPressed: false; property bool shiftPressed: false;
Keys.onPressed: shiftPressed = (event.key === Qt.Key_Shift); Keys.onPressed: shiftPressed = (event.key === Qt.Key_Shift);
Keys.onReleased: shiftPressed = false; Keys.onReleased: shiftPressed = false;
Flickable { TimelineLabels {
id: categories id: categories
flickableDirection: Flickable.VerticalFlick
interactive: false
anchors.top: buttonsBar.bottom anchors.top: buttonsBar.bottom
anchors.bottom: overview.top anchors.bottom: overview.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right 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 onMoveCategories: content.moveCategories(sourceIndex, targetIndex)
contentHeight: categoryContent.height + height onSelectItem: content.select(modelIndex, eventIndex)
// 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"
}
} }
TimeDisplay { TimeDisplay {
@@ -321,7 +173,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: overview.top anchors.bottom: overview.top
zoomer: zoomControl zoomer: zoomControl
contentX: flick.contentX contentX: content.contentX
clip: true clip: true
} }
@@ -337,217 +189,173 @@ Rectangle {
onJumpToNext: { onJumpToNext: {
var next = qmlProfilerModelProxy.nextItem(root.selectedModel, root.selectedItem, var next = qmlProfilerModelProxy.nextItem(root.selectedModel, root.selectedItem,
zoomControl.rangeStart); zoomControl.rangeStart);
timelineView.select(next.model, next.item); content.select(next.model, next.item);
} }
onJumpToPrev: { onJumpToPrev: {
var prev = qmlProfilerModelProxy.prevItem(root.selectedModel, root.selectedItem, var prev = qmlProfilerModelProxy.prevItem(root.selectedModel, root.selectedItem,
zoomControl.rangeEnd); zoomControl.rangeEnd);
timelineView.select(prev.model, prev.item); content.select(prev.model, prev.item);
} }
onRangeSelectChanged: selectionRangeMode = rangeButtonChecked(); 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 { Flickable {
id: flick flickableDirection: Flickable.HorizontalFlick
contentHeight: categoryContent.height clip: true
flickableDirection: Flickable.HorizontalAndVerticalFlick visible: selectionRangeMode &&
boundsBehavior: Flickable.StopAtBounds selectionRange.creationState !== selectionRange.creationInactive
pixelAligned: true 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 { SelectionRange {
id: selectionRange id: selectionRange
visible: root.selectionRangeMode && creationState !== 0 zoomer: zoomControl
z: 2 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 { SelectionRangeDetails {
z: 1
x: 200
y: 125
id: selectionRangeDetails id: selectionRangeDetails
visible: selectionRange.visible visible: selectionRange.visible
startTime: selectionRange.startTimeString startTime: selectionRange.startTime
duration: selectionRange.durationString duration: selectionRange.duration
endTime: selectionRange.endTimeString endTime: selectionRange.endTime
showDuration: selectionRange.rangeWidth > 1 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 { RangeDetails {
id: rangeDetails id: rangeDetails
property alias locked: root.selectionLocked
z: 1
visible: false
x: 200
y: 25
property alias locked: content.selectionLocked
models: qmlProfilerModelProxy.models models: qmlProfilerModelProxy.models
notes: qmlProfilerModelProxy.notes notes: qmlProfilerModelProxy.notes
onRecenterOnItem: {
content.gotoSourceLocation(file, line, column);
content.select(selectedModel, selectedItem)
}
onToggleSelectionLocked: {
content.selectionLocked = !content.selectionLocked;
}
onClearSelection: {
content.propagateSelection(-1, -1);
}
} }
Rectangle { Rectangle {
@@ -591,7 +399,7 @@ Rectangle {
color: "#9b9b9b" color: "#9b9b9b"
enabled: buttonsBar.enabled enabled: buttonsBar.enabled
visible: false visible: false
width: categoryContent.width width: buttonsBar.width
height: buttonsBar.height height: buttonsBar.height
anchors.left: parent.left anchors.left: parent.left
anchors.top: buttonsBar.bottom anchors.top: buttonsBar.bottom
@@ -603,7 +411,6 @@ Rectangle {
1 / zoomSlider.exponent) * zoomSlider.maximumValue; 1 / zoomSlider.exponent) * zoomSlider.maximumValue;
} }
Slider { Slider {
id: zoomSlider id: zoomSlider
anchors.fill: parent anchors.fill: parent
@@ -651,5 +458,7 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
modelProxy: qmlProfilerModelProxy
zoomer: zoomControl
} }
} }

View File

@@ -37,12 +37,15 @@ Canvas {
objectName: "Overview" objectName: "Overview"
contextType: "2d" contextType: "2d"
property QtObject modelProxy
property QtObject zoomer
readonly property int eventsPerPass: 512 readonly property int eventsPerPass: 512
property int increment: -1 property int increment: -1
property int offset: -1 property int offset: -1
readonly property int bump: 10; readonly property int bump: 10;
readonly property int blockHeight: (height - bump) / qmlProfilerModelProxy.models.length; readonly property int blockHeight: (height - bump) / modelProxy.models.length;
readonly property double spacing: width / zoomControl.traceDuration readonly property double spacing: width / zoomer.traceDuration
// ***** properties // ***** properties
height: 50 height: 50
@@ -64,12 +67,12 @@ Canvas {
if (recursionGuard) if (recursionGuard)
return; return;
recursionGuard = true; recursionGuard = true;
var newStartTime = rangeMover.rangeLeft * zoomControl.traceDuration / width + var newStartTime = rangeMover.rangeLeft * zoomer.traceDuration / width +
zoomControl.traceStart; zoomer.traceStart;
var newEndTime = rangeMover.rangeRight * zoomControl.traceDuration / width + var newEndTime = rangeMover.rangeRight * zoomer.traceDuration / width +
zoomControl.traceStart; zoomer.traceStart;
if (isFinite(newStartTime) && isFinite(newEndTime) && newEndTime - newStartTime > 500) if (isFinite(newStartTime) && isFinite(newEndTime) && newEndTime - newStartTime > 500)
zoomControl.setRange(newStartTime, newEndTime); zoomer.setRange(newStartTime, newEndTime);
recursionGuard = false; recursionGuard = false;
} }
@@ -79,14 +82,14 @@ Canvas {
// ***** connections to external objects // ***** connections to external objects
Connections { Connections {
target: zoomControl target: zoomer
onRangeChanged: { onRangeChanged: {
if (recursionGuard) if (recursionGuard)
return; return;
recursionGuard = true; recursionGuard = true;
var newRangeX = (zoomControl.rangeStart - zoomControl.traceStart) * width / var newRangeX = (zoomer.rangeStart - zoomer.traceStart) * width /
zoomControl.traceDuration; zoomer.traceDuration;
var newWidth = zoomControl.rangeDuration * width / zoomControl.traceDuration; var newWidth = zoomer.rangeDuration * width / zoomer.traceDuration;
var widthChanged = Math.abs(newWidth - rangeMover.rangeWidth) > 1; var widthChanged = Math.abs(newWidth - rangeMover.rangeWidth) > 1;
var leftChanged = Math.abs(newRangeX - rangeMover.rangeLeft) > 1; var leftChanged = Math.abs(newRangeX - rangeMover.rangeLeft) > 1;
if (leftChanged) if (leftChanged)
@@ -99,11 +102,11 @@ Canvas {
} }
Connections { Connections {
target: qmlProfilerModelProxy target: modelProxy
onDataAvailable: { onDataAvailable: {
dataReady = true; dataReady = true;
increment = 0; increment = 0;
var models = qmlProfilerModelProxy.models; var models = modelProxy.models;
for (var i = 0; i < models.length; ++i) for (var i = 0; i < models.length; ++i)
increment += models[i].count; increment += models[i].count;
increment = Math.ceil(increment / eventsPerPass); increment = Math.ceil(increment / eventsPerPass);
@@ -125,9 +128,9 @@ Canvas {
onPaint: { onPaint: {
var context = (canvas.context === null) ? getContext("2d") : canvas.context; var context = (canvas.context === null) ? getContext("2d") : canvas.context;
Plotter.models = qmlProfilerModelProxy.models; Plotter.models = modelProxy.models;
Plotter.zoomControl = zoomControl; Plotter.zoomControl = zoomer;
Plotter.notes = qmlProfilerModelProxy.notes; Plotter.notes = modelProxy.notes;
if (offset < 0) { if (offset < 0) {
context.reset(); context.reset();

View File

@@ -50,12 +50,12 @@ Item {
property var models property var models
property var notes property var notes
signal recenterOnItem
signal toggleSelectionLocked
signal clearSelection
width: col.width + 25 width: col.width + 25
height: col.height + 30 height: col.height + 30
z: 1
visible: false
x: 200
y: 25
function hide() { function hide() {
noteEdit.focus = false; noteEdit.focus = false;
@@ -70,9 +70,9 @@ Item {
isBindingLoop = false; isBindingLoop = false;
} }
// keep inside view
Connections { Connections {
target: root target: rangeDetails.parent
// keep inside view
onWidthChanged: fitInView(); onWidthChanged: fitInView();
onHeightChanged: fitInView(); onHeightChanged: fitInView();
} }
@@ -119,15 +119,15 @@ Item {
function fitInView() { function fitInView() {
// don't reposition if it does not fit // don't reposition if it does not fit
if (root.width < width || root.height < height) if (parent.width < width || parent.height < height)
return; return;
if (x + width > root.width) if (x + width > parent.width)
x = root.width - width; x = parent.width - width;
if (x < 0) if (x < 0)
x = 0; x = 0;
if (y + height > root.height) if (y + height > parent.height)
y = root.height - height; y = parent.height - height;
if (y < 0) if (y < 0)
y = 0; y = 0;
} }
@@ -249,13 +249,10 @@ Item {
anchors.fill: parent anchors.fill: parent
drag.target: parent drag.target: parent
drag.minimumX: 0 drag.minimumX: 0
drag.maximumX: root.width - parent.width drag.maximumX: rangeDetails.parent.width - rangeDetails.width
drag.minimumY: 0 drag.minimumY: 0
drag.maximumY: root.height - parent.height drag.maximumY: rangeDetails.parent.height - rangeDetails.height
onClicked: { onClicked: rangeDetails.recenterOnItem()
root.gotoSourceLocation(file, line, column);
root.recenterOnItem();
}
} }
Image { Image {
@@ -282,9 +279,7 @@ Item {
height: 12 height: 12
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: rangeDetails.toggleSelectionLocked()
root.selectionLocked = !root.selectionLocked;
}
} }
} }
@@ -298,7 +293,7 @@ Item {
renderType: Text.NativeRendering renderType: Text.NativeRendering
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: root.propagateSelection(-1, -1); onClicked: rangeDetails.clearSelection()
} }
} }

View File

@@ -32,42 +32,26 @@ import QtQuick 2.1
RangeMover { RangeMover {
id: selectionRange 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 bool ready: visible && creationState === creationFinished
property string endTimeString: detailedPrintTime(endTime)
property string durationString: detailedPrintTime(duration)
property double startTime: rangeLeft * viewTimePerPixel + zoomControl.windowStart property double startTime: rangeLeft * viewTimePerPixel + zoomer.windowStart
property double duration: Math.max(rangeWidth * viewTimePerPixel, 500) property double duration: Math.max(rangeWidth * viewTimePerPixel, 500)
property double endTime: startTime + duration property double endTime: startTime + duration
property double viewTimePerPixel: 1 property double viewTimePerPixel: 1
property double creationReference : 0 property double creationReference : 0
property int creationState : 0 property int creationState : creationInactive
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;
}
function reset() { function reset() {
rangeRight = rangeLeft + 1; rangeRight = rangeLeft + 1;
creationState = 0; creationState = creationInactive;
creationReference = 0; creationReference = 0;
} }
@@ -78,12 +62,12 @@ RangeMover {
pos = width; pos = width;
switch (creationState) { switch (creationState) {
case 1: case creationFirstLimit:
creationReference = pos; creationReference = pos;
rangeLeft = pos; rangeLeft = pos;
rangeRight = pos + 1; rangeRight = pos + 1;
break; break;
case 2: case creationSecondLimit:
if (pos > creationReference) { if (pos > creationReference) {
rangeLeft = creationReference; rangeLeft = creationReference;
rangeRight = pos; rangeRight = pos;
@@ -94,43 +78,4 @@ RangeMover {
break; 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);
}
} }

View File

@@ -34,6 +34,9 @@ import Monitor 1.0
Item { Item {
id: selectionRangeDetails id: selectionRangeDetails
signal recenter
signal close
property string startTime property string startTime
property string endTime property string endTime
property string duration property string duration
@@ -41,29 +44,35 @@ Item {
width: Math.max(150, col.width + 25) width: Math.max(150, col.width + 25)
height: col.height + 30 height: col.height + 30
z: 1
visible: false
x: 200
y: 125
// keep inside view // keep inside view
Connections { Connections {
target: root target: selectionRangeDetails.parent
onWidthChanged: fitInView(); onWidthChanged: fitInView();
onHeightChanged: 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() { function fitInView() {
// don't reposition if it does not fit // don't reposition if it does not fit
if (root.width < width || root.height < height) if (parent.width < width || parent.height < height)
return; return;
if (x + width > root.width) if (x + width > parent.width)
x = root.width - width; x = parent.width - width;
if (x < 0) if (x < 0)
x = 0; x = 0;
if (y + height > root.height) if (y + height > parent.height)
y = root.height - height; y = parent.height - height;
if (y < 0) if (y < 0)
y = 0; y = 0;
} }
@@ -138,11 +147,11 @@ Item {
Repeater { Repeater {
model: [ model: [
qsTr("Start") + ":", qsTr("Start") + ":",
startTime, detailedPrintTime(startTime),
showDuration ? (qsTr("End") + ":") : "", showDuration ? (qsTr("End") + ":") : "",
showDuration ? endTime : "", showDuration ? detailedPrintTime(endTime) : "",
showDuration ? (qsTr("Duration") + ":") : "", showDuration ? (qsTr("Duration") + ":") : "",
showDuration ? duration : "" showDuration ? detailedPrintTime(duration) : ""
] ]
Detail { Detail {
isLabel: index % 2 === 0 isLabel: index % 2 === 0
@@ -156,17 +165,10 @@ Item {
anchors.fill: parent anchors.fill: parent
drag.target: parent drag.target: parent
drag.minimumX: 0 drag.minimumX: 0
drag.maximumX: root.width - parent.width drag.maximumX: selectionRangeDetails.parent.width - width
drag.minimumY: 0 drag.minimumY: 0
drag.maximumY: root.height - parent.height + 0 drag.maximumY: selectionRangeDetails.parent.height - height
onClicked: { onClicked: selectionRangeDetails.recenter()
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);
}
}
} }
Text { Text {
@@ -179,8 +181,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: -8 anchors.leftMargin: -8
onClicked: root.selectionRangeMode = false; onClicked: selectionRangeDetails.close()
} }
} }
} }

View File

@@ -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
}
}
}
}

View File

@@ -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"
}
}

View File

@@ -36,5 +36,7 @@
<file>bindingloops.frag</file> <file>bindingloops.frag</file>
<file>notes.vert</file> <file>notes.vert</file>
<file>notes.frag</file> <file>notes.frag</file>
<file>TimelineLabels.qml</file>
<file>TimelineContent.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -96,6 +96,8 @@ DISTFILES += \
qml/RangeDetails.qml \ qml/RangeDetails.qml \
qml/RangeMover.qml \ qml/RangeMover.qml \
qml/TimeDisplay.qml \ qml/TimeDisplay.qml \
qml/TimelineContent.qml \
qml/TimelineLabels.qml \
qml/TimeMarks.qml \ qml/TimeMarks.qml \
qml/SelectionRange.qml \ qml/SelectionRange.qml \
qml/SelectionRangeDetails.qml \ qml/SelectionRangeDetails.qml \

View File

@@ -77,6 +77,8 @@ QtcPlugin {
"SelectionRange.qml", "SelectionRange.qml",
"SelectionRangeDetails.qml", "SelectionRangeDetails.qml",
"TimeDisplay.qml", "TimeDisplay.qml",
"TimelineContent.qml",
"TimelineLabels.qml",
"TimeMarks.qml", "TimeMarks.qml",
"qmlprofiler.qrc", "qmlprofiler.qrc",