QmlProfiler: Reorganize timeline into rows

This potentially allows us to use different views for different
categories and reduces the reliance on TimelineModelAggregator.

Change-Id: I486481599d1517abc0087c565358f27405e4108b
Reviewed-by: Kai Koehne <kai.koehne@theqtcompany.com>
This commit is contained in:
Ulf Hermann
2014-10-30 14:57:49 +01:00
committed by Ulf Hermann
parent 3780afc1e0
commit b1f4f56e99
11 changed files with 592 additions and 612 deletions

View File

@@ -34,42 +34,41 @@ import QtQuick.Controls.Styles 1.2
Item {
id: labelContainer
property string text: trigger(1) ? qmlProfilerModelProxy.displayName(modelIndex) : ""
property bool expanded: trigger(qmlProfilerModelProxy.expanded(modelIndex))
property int modelIndex: index
property int bindingTrigger: 1
property QtObject model
property bool mockup
property string text: model ? model.displayName : ""
property bool expanded: model && model.expanded
property var descriptions: []
property var extdescriptions: []
property var selectionIds: []
property bool dragging
property int visualIndex
property int dragOffset
property Item draggerParent
signal dragStarted;
signal dragStopped;
signal dropped(int sourceIndex, int targetIndex)
signal selectById(int eventId)
signal selectNextBySelectionId(int selectionId)
signal selectPrevBySelectionId(int selectionId)
readonly property int dragHeight: 5
function trigger(i) {
return i * bindingTrigger * bindingTrigger;
}
property bool reverseSelect: false
visible: trigger(!qmlProfilerModelProxy.models[modelIndex].hidden &&
!qmlProfilerModelProxy.models[modelIndex].empty)
visible: model && (mockup || (!model.hidden && !model.empty))
height: trigger(qmlProfilerModelProxy.models[modelIndex].height)
height: model ? Math.max(txt.height, model.height) : 0
width: 150
function updateDescriptions() {
bindingTrigger = -bindingTrigger;
if (!visible)
return;
var desc=[];
var ids=[];
var extdesc=[];
var labelList = qmlProfilerModelProxy.labels(modelIndex);
var labelList = model.labels;
for (var i = 0; i < labelList.length; i++ ) {
extdesc[i] = desc[i] = (labelList[i].description || qsTr("<bytecode>"));
ids[i] = labelList[i].id;
@@ -82,16 +81,8 @@ Item {
}
Connections {
target: qmlProfilerModelProxy.models[modelIndex]
onExpandedChanged: updateDescriptions()
onRowHeightChanged: updateDescriptions()
onHiddenChanged: updateDescriptions()
}
Connections {
target: qmlProfilerModelProxy
onStateChanged: updateDescriptions()
onModelsChanged: updateDescriptions()
target: model
onLabelsChanged: updateDescriptions()
}
MouseArea {
@@ -99,20 +90,22 @@ Item {
anchors.fill: txt
drag.target: dragger
cursorShape: dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor
drag.minimumY: dragging ? 0 : -dragOffset // Account for parent change below
drag.maximumY: draggerParent.height - (dragging ? 0 : dragOffset)
}
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;
var sourceIndex = drag.source.visualIndex;
if (drag.source.y + drag.source.height > dragOffset + labelContainer.height &&
sourceIndex !== visualIndex && sourceIndex !== visualIndex + 1) {
var moveTo = sourceIndex > visualIndex ? visualIndex + 1 : visualIndex;
labelContainer.dropped(sourceIndex, moveTo);
} else if (drag.source.y === 0) {
// special case for first position.
labelContainer.dropped(sourceIndex, 0);
}
}
@@ -125,7 +118,7 @@ Item {
font.pixelSize: 12
text: labelContainer.text
color: "#232323"
height: trigger(qmlProfilerModelProxy.rowHeight(modelIndex, 0))
height: model ? model.defaultRowHeight : 0
width: 140
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
@@ -140,19 +133,21 @@ Item {
}
Column {
id: column
property QtObject parentModel: model
anchors.top: txt.bottom
visible: expanded
Repeater {
model: descriptions.length
Button {
width: labelContainer.width
height: trigger(qmlProfilerModelProxy.rowHeight(modelIndex, index + 1))
height: column.parentModel ? column.parentModel.rowHeight(index + 1) : 0
action: Action {
onTriggered: {
if (reverseSelect)
view.selectPrevFromSelectionId(modelIndex,selectionIds[index]);
labelContainer.selectPrevBySelectionId(selectionIds[index]);
else
view.selectNextFromSelectionId(modelIndex,selectionIds[index]);
labelContainer.selectNextBySelectionId(selectionIds[index]);
}
tooltip: extdescriptions[index]
@@ -186,8 +181,10 @@ Item {
cursorShape: Qt.SizeVerCursor
onMouseYChanged: {
if (resizing)
qmlProfilerModelProxy.setRowHeight(modelIndex, index + 1, y + mouseY);
if (resizing) {
column.parentModel.setRowHeight(index + 1, y + mouseY);
parent.height = column.parentModel.rowHeight(index + 1);
}
}
}
}
@@ -203,22 +200,22 @@ Item {
property var eventIds: []
property var texts: []
property int currentNote: -1
property var notesModel: qmlProfilerModelProxy.notes
Connections {
target: qmlProfilerModelProxy
onModelsChanged: notesButton.updateNotes()
onNotesChanged: {
if (arguments[1] === -1 || arguments[1] === modelIndex)
target: notesButton.notesModel
onChanged: {
if (arguments[1] === -1 || arguments[1] === model.modelId)
notesButton.updateNotes();
}
}
function updateNotes() {
var notes = qmlProfilerModelProxy.notesByTimelineModel(modelIndex);
var notes = notesModel.byTimelineModel(model.modelId);
var newTexts = [];
var newEventIds = [];
for (var i in notes) {
newTexts.push(qmlProfilerModelProxy.noteText(notes[i]))
newEventIds.push(qmlProfilerModelProxy.noteTimelineIndex(notes[i]));
newTexts.push(notesModel.text(notes[i]))
newEventIds.push(notesModel.timelineIndex(notes[i]));
}
// Bindings are only triggered when assigning the whole array.
@@ -232,7 +229,7 @@ Item {
onClicked: {
if (++currentNote >= eventIds.length)
currentNote = 0;
view.selectFromEventIndex(modelIndex, eventIds[currentNote]);
labelContainer.selectById(eventIds[currentNote]);
}
}
@@ -242,15 +239,15 @@ Item {
anchors.right: parent.right
implicitWidth: 17
implicitHeight: txt.height - 1
enabled: expanded || trigger(qmlProfilerModelProxy.count(modelIndex)) > 0
enabled: expanded || (model && !model.empty)
iconSource: expanded ? "arrow_down.png" : "arrow_right.png"
tooltip: expanded ? qsTr("Collapse category") : qsTr("Expand category.")
onClicked: qmlProfilerModelProxy.setExpanded(modelIndex, !expanded);
onClicked: model.expanded = !expanded
}
Rectangle {
id: dragger
property int modelIndex
property int visualIndex: labelContainer.visualIndex
width: labelContainer.width
height: 0
color: "black"
@@ -262,10 +259,9 @@ Item {
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.
// We don't want height or text 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();

View File

@@ -31,13 +31,14 @@
import QtQuick 2.1
import Monitor 1.0
import QtQuick.Controls 1.0
import QtQml.Models 2.1
Rectangle {
id: root
// ***** properties
property alias selectionLocked : view.selectionLocked
property bool selectionLocked : true
property bool lockItemSelection : false
property real mainviewTimePerPixel: zoomControl.rangeDuration / root.width
@@ -47,6 +48,8 @@ Rectangle {
property int lineNumber: -1
property int columnNumber: 0
property int typeId: -1
property int selectedModel: -1
property int selectedItem: -1
property bool selectionRangeMode: false
@@ -60,12 +63,11 @@ Rectangle {
Connections {
target: zoomControl
onRangeChanged: {
backgroundMarks.updateMarks(zoomControl.rangeStart, zoomControl.rangeEnd);
zoomSliderToolBar.updateZoomLevel();
view.updateWindow();
flick.updateWindow();
}
onWindowChanged: {
view.updateWindow();
flick.updateWindow();
}
}
@@ -73,15 +75,10 @@ Rectangle {
Connections {
target: qmlProfilerModelProxy
onDataAvailable: {
view.clearData();
timelineView.clearChildren();
zoomControl.setRange(zoomControl.traceStart,
zoomControl.traceStart + zoomControl.traceDuration / 10);
view.requestPaint();
}
onStateChanged: backgroundMarks.requestPaint()
onModelsChanged: backgroundMarks.requestPaint()
onExpandedChanged: backgroundMarks.requestPaint()
onRowHeightChanged: backgroundMarks.requestPaint()
}
@@ -94,11 +91,15 @@ Rectangle {
}
}
function recenterOnItem() {
timelineView.select(selectedModel, selectedItem);
}
function clear() {
flick.contentY = 0;
flick.contentX = 0;
flick.contentWidth = 0;
view.clearData();
timelineView.clearChildren();
rangeDetails.hide();
selectionRangeMode = false;
zoomSlider.externalUpdate = true;
@@ -107,34 +108,35 @@ Rectangle {
timeDisplay.clear();
}
function enableButtonsBar(enable) {
buttonsBar.enabled = enable;
}
function recenter( centerPoint ) {
var newStart = Math.floor(centerPoint - zoomControl.rangeDuration / 2);
zoomControl.setRange(Math.max(newStart, zoomControl.traceStart),
Math.min(newStart + zoomControl.rangeDuration, zoomControl.traceEnd));
}
function recenterOnItem(modelIndex, itemIndex)
{
if (itemIndex === -1)
function propagateSelection(newModel, newItem) {
if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem))
return;
// if item is outside of the view, jump back to its position
if (qmlProfilerModelProxy.endTime(modelIndex, itemIndex) < zoomControl.rangeStart ||
qmlProfilerModelProxy.startTime(modelIndex, itemIndex) > zoomControl.rangeEnd) {
recenter((qmlProfilerModelProxy.startTime(modelIndex, itemIndex) +
qmlProfilerModelProxy.endTime(modelIndex, itemIndex)) / 2);
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();
}
var row = qmlProfilerModelProxy.row(modelIndex, itemIndex);
var totalRowOffset = qmlProfilerModelProxy.modelOffset(modelIndex) +
qmlProfilerModelProxy.rowOffset(modelIndex, row);
if (totalRowOffset > flick.contentY + flick.height ||
totalRowOffset + qmlProfilerModelProxy.rowHeight(modelIndex, row) < flick.contentY)
flick.contentY = Math.min(flick.contentHeight - flick.height,
Math.max(0, totalRowOffset - flick.height / 2));
lockItemSelection = false;
}
function enableButtonsBar(enable) {
buttonsBar.enabled = enable;
}
function selectByTypeId(typeId)
@@ -142,8 +144,6 @@ Rectangle {
if (lockItemSelection || typeId === -1)
return;
lockItemSelection = true;
var itemIndex = -1;
var modelIndex = -1;
var notes = qmlProfilerModelProxy.notesByTypeId(typeId);
@@ -151,15 +151,16 @@ Rectangle {
modelIndex = qmlProfilerModelProxy.noteTimelineModel(notes[0]);
itemIndex = qmlProfilerModelProxy.noteTimelineIndex(notes[0]);
} else {
for (modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); ++modelIndex) {
if (modelIndex === view.selectedModel && view.selectedItem !== -1 &&
typeId === qmlProfilerModelProxy.typeId(modelIndex, view.selectedItem))
for (modelIndex = 0; modelIndex < qmlProfilerModelProxy.models.length; ++modelIndex) {
if (modelIndex === selectedModel && selectedItem !== -1 &&
typeId === qmlProfilerModelProxy.models[modelIndex].typeId(selectedItem))
break;
if (!qmlProfilerModelProxy.handlesTypeId(modelIndex, typeId))
if (!qmlProfilerModelProxy.models[modelIndex].handlesTypeId(typeId))
continue;
itemIndex = view.nextItemFromTypeId(modelIndex, typeId);
itemIndex = qmlProfilerModelProxy.models[modelIndex].nextItemByTypeId(typeId,
zoomControl.rangeStart, selectedItem);
if (itemIndex !== -1)
break;
}
@@ -167,11 +168,9 @@ Rectangle {
if (itemIndex !== -1) {
// select an item, lock to it, and recenter if necessary
view.selectFromEventIndex(modelIndex, itemIndex); // triggers recentering
view.selectionLocked = true;
timelineView.select(modelIndex, itemIndex);
root.selectionLocked = true;
}
lockItemSelection = false;
}
// ***** slots
@@ -210,8 +209,6 @@ Rectangle {
color: root.color
height: col.height
property int rowCount: qmlProfilerModelProxy.modelCount();
Column {
id: col
@@ -220,15 +217,47 @@ Rectangle {
// events, so we can't use the drag events to determine the cursor shape.
property bool dragging: false
Repeater {
model: labels.rowCount
DelegateModel {
id: labelsModel
model: qmlProfilerModelProxy.models
delegate: CategoryLabel {
model: modelData
mockup: qmlProfilerModelProxy.height == 0
visualIndex: DelegateModel.itemsIndex
dragging: col.dragging
reverseSelect: root.shiftPressed
onDragStarted: col.dragging = true
onDragStopped: col.dragging = false
draggerParent: labels
dragOffset: 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));
}
}
}
Repeater {
model: labelsModel
}
}
}
@@ -254,15 +283,16 @@ Rectangle {
onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.visible
onFilterMenuChanged: filterMenu.visible = !filterMenu.visible
onJumpToNext: {
var next = qmlProfilerModelProxy.nextItem(view.selectedModel, view.selectedItem,
var next = qmlProfilerModelProxy.nextItem(root.selectedModel, root.selectedItem,
zoomControl.rangeStart);
view.selectFromEventIndex(next.model, next.item);
timelineView.select(next.model, next.item);
}
onJumpToPrev: {
var prev = qmlProfilerModelProxy.prevItem(view.selectedModel, view.selectedItem,
var prev = qmlProfilerModelProxy.prevItem(root.selectedModel, root.selectedItem,
zoomControl.rangeEnd);
view.selectFromEventIndex(prev.model, prev.item);
timelineView.select(prev.model, prev.item);
}
onRangeSelectChanged: selectionRangeMode = rangeButtonChecked();
onLockChanged: selectionLocked = !lockButtonChecked();
}
@@ -290,40 +320,11 @@ Rectangle {
onInteractiveChanged: interactive = stayInteractive
onStayInteractiveChanged: interactive = stayInteractive
// ***** child items
TimeMarks {
id: backgroundMarks
y: flick.contentY
x: flick.contentX
height: flick.height
width: scroller.width
}
SelectionRange {
id: selectionRange
visible: root.selectionRangeMode && creationState !== 0
z: 2
}
TimelineRenderer {
id: view
profilerModelProxy: qmlProfilerModelProxy
zoomer: zoomControl
x: flick.contentX
y: flick.contentY
// paint "under" the vertical scrollbar, so that it always matches with the timemarks
width: scroller.width
height: flick.height
onYChanged: requestPaint()
onHeightChanged: requestPaint()
property bool recursionGuard: false
property int intX: contentX
property int intWidth: scroller.width
property int intX: x
property int intWidth: width
// Update the zoom control on srolling.
onIntXChanged: {
if (recursionGuard)
return;
@@ -340,6 +341,7 @@ Rectangle {
recursionGuard = false;
}
// Scroll when the zoom control is updated
function updateWindow() {
if (recursionGuard || zoomControl.rangeDuration <= 0)
return;
@@ -360,41 +362,158 @@ Rectangle {
recursionGuard = false;
}
onSelectionChanged: {
if (selectedItem !== -1) {
// display details
rangeDetails.showInfo(selectedModel, selectedItem);
// ***** child items
SelectionRange {
id: selectionRange
visible: root.selectionRangeMode && creationState !== 0
z: 2
}
// center view (horizontally)
recenterOnItem(selectedModel, selectedItem);
if (!lockItemSelection) {
lockItemSelection = true;
// update in other views
var eventLocation = qmlProfilerModelProxy.location(view.selectedModel,
view.selectedItem);
gotoSourceLocation(eventLocation.file, eventLocation.line,
eventLocation.column);
root.typeId = qmlProfilerModelProxy.typeId(view.selectedModel,
view.selectedItem);
root.updateCursorPosition();
lockItemSelection = false;
Column {
id: timelineView
signal clearChildren
signal select(int modelIndex, int eventIndex)
// 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)
DelegateModel {
id: timelineModel
model: qmlProfilerModelProxy.models
delegate: Item {
id: spacer
height: modelData.height
width: flick.contentWidth
property int rowCount: (modelData.empty || modelData.hidden) ?
0 : modelData.rowCount
property int visualIndex: DelegateModel.itemsIndex
function updateRowParentCount() {
if (timelineView.rowCounts[visualIndex] !== rowCount) {
timelineView.rowCounts[visualIndex] = rowCount;
// Array don't "change" if entries change. We have to signal manually.
timelineView.rowCountsChanged();
}
}
onRowCountChanged: updateRowParentCount()
onVisualIndexChanged: updateRowParentCount()
TimeMarks {
model: modelData
id: backgroundMarks
anchors.fill: renderer
startTime: zoomControl.rangeStart
endTime: zoomControl.rangeEnd
// 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: (timelineView.rowCounts.slice(0, spacer.visualIndex).reduce(
function(prev, rows) {return prev + rows}, 0) % 2) === 0
onStartOddChanged: requestPaint()
}
TimelineRenderer {
id: renderer
model: modelData
notes: qmlProfilerModelProxy.notes
zoomer: zoomControl
selectionLocked: root.selectionLocked
x: flick.contentX
// paint "under" the vertical scrollbar, so that it always matches with the
// timemarks
width: scroller.width
property int yScrollStartDiff: flick.contentY - parent.y
property int yScrollEndDiff: flick.height - parent.height + yScrollStartDiff
y: Math.min(parent.height, Math.max(0, yScrollStartDiff))
height: {
if (yScrollStartDiff > 0) {
return Math.max(0, Math.min(flick.height,
parent.height - yScrollStartDiff));
} else if (yScrollEndDiff < 0) {
return Math.max(0, Math.min(flick.height,
parent.height + yScrollEndDiff));
} else {
rangeDetails.hide();
return parent.height;
}
}
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));
}
if (spacer.y + spacer.height < flick.contentY)
flick.contentY = spacer.y + spacer.height;
else if (spacer.y - flick.height > flick.contentY)
flick.contentY = spacer.y - flick.height;
var row = modelData.row(selectedItem);
var rowStart = modelData.rowOffset(row);
var rowEnd = rowStart + modelData.rowHeight(row);
if (rowStart < y)
flick.contentY -= y - rowStart;
else if (rowEnd > y + height)
flick.contentY += rowEnd - y - height;
}
onSelectedItemChanged: {
root.propagateSelection(index, selectedItem);
}
onItemPressed: {
var location = qmlProfilerModelProxy.location(modelIndex, pressedItem);
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 = qmlProfilerModelProxy.typeId(modelIndex, pressedItem);
root.typeId = model.typeId(pressedItem);
root.updateCursorPosition();
}
}
}
}
}
// hack to pass mouse events to the other mousearea if enabled
startDragArea: selectionRange.ready ? selectionRange.rangeLeft : -x
endDragArea: selectionRange.ready ? selectionRange.rangeRight : -x - 1
Repeater {
id: repeater
model: timelineModel
}
}
MouseArea {
id: selectionRangeControl
@@ -441,6 +560,9 @@ Rectangle {
RangeDetails {
id: rangeDetails
property alias locked: root.selectionLocked
models: qmlProfilerModelProxy.models
notes: qmlProfilerModelProxy.notes
}
Rectangle {
@@ -451,7 +573,7 @@ Rectangle {
width: labels.width
anchors.left: parent.left
anchors.top: buttonsBar.bottom
height: qmlProfilerModelProxy.modelCount() * buttonsBar.height
height: qmlProfilerModelProxy.models.length * buttonsBar.height
Repeater {
id: filterMenuInner
@@ -461,10 +583,10 @@ Rectangle {
anchors.right: filterMenu.right
height: buttonsBar.height
y: index * height
text: qmlProfilerModelProxy.models[index].displayName
enabled: !qmlProfilerModelProxy.models[index].empty
checked: enabled && !qmlProfilerModelProxy.models[index].hidden
onCheckedChanged: qmlProfilerModelProxy.models[index].hidden = !checked
text: modelData.displayName
enabled: !modelData.empty
checked: enabled && !modelData.hidden
onCheckedChanged: modelData.hidden = !checked
}
}
@@ -514,9 +636,11 @@ Rectangle {
minWindowLength);
var fixedPoint = (zoomControl.rangeStart + zoomControl.rangeEnd) / 2;
if (view.selectedItem !== -1) {
if (root.selectedItem !== -1) {
// center on selected item if it's inside the current screen
var newFixedPoint = qmlProfilerModelProxy.startTime(view.selectedModel, view.selectedItem);
var model = qmlProfilerModelProxy.models[root.selectedModel]
var newFixedPoint = (model.startTime(root.selectedItem) +
model.endTime(root.selectedItem)) / 2;
if (newFixedPoint >= zoomControl.rangeStart &&
newFixedPoint < zoomControl.rangeEnd)
fixedPoint = newFixedPoint;

View File

@@ -30,7 +30,8 @@
.pragma library
var qmlProfilerModelProxy = 0;
var models = 0;
var notes = 0;
var zoomControl = 0;
//draw background of the graph
@@ -43,28 +44,25 @@ function drawGraph(canvas, ctxt)
//draw the actual data to be graphed
function drawData(canvas, ctxt)
{
if (!zoomControl || !qmlProfilerModelProxy || qmlProfilerModelProxy.isEmpty())
if (!zoomControl || !models)
return;
for (var modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); ++modelIndex) {
for (var ii = canvas.offset; ii < qmlProfilerModelProxy.count(modelIndex);
ii += canvas.increment) {
for (var modelIndex = 0; modelIndex < models.length; ++modelIndex) {
var model = models[modelIndex];
for (var ii = canvas.offset; ii < model.count; ii += canvas.increment) {
var xx = (model.startTime(ii) - zoomControl.traceStart) * canvas.spacing;
var xx = (qmlProfilerModelProxy.startTime(modelIndex,ii) - zoomControl.traceStart) *
canvas.spacing;
var eventWidth = qmlProfilerModelProxy.duration(modelIndex,ii) * canvas.spacing;
var eventWidth = model.duration(ii) * canvas.spacing;
if (eventWidth < 1)
eventWidth = 1;
xx = Math.round(xx);
var itemHeight = qmlProfilerModelProxy.relativeHeight(modelIndex, ii) *
canvas.blockHeight;
var itemHeight = model.relativeHeight(ii) * canvas.blockHeight;
var yy = (modelIndex + 1) * canvas.blockHeight - itemHeight ;
ctxt.fillStyle = qmlProfilerModelProxy.color(modelIndex, ii);
ctxt.fillStyle = model.color(ii);
ctxt.fillRect(xx, canvas.bump + yy, eventWidth, itemHeight);
}
}
@@ -74,12 +72,11 @@ function drawBindingLoops(canvas, ctxt) {
ctxt.strokeStyle = "orange";
ctxt.lineWidth = 2;
var radius = 1;
for (var modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); ++modelIndex) {
for (var ii = canvas.offset - canvas.increment; ii < qmlProfilerModelProxy.count(modelIndex);
ii += canvas.increment) {
if (qmlProfilerModelProxy.bindingLoopDest(modelIndex,ii) >= 0) {
var xcenter = Math.round(qmlProfilerModelProxy.startTime(modelIndex,ii) +
qmlProfilerModelProxy.duration(modelIndex,ii) / 2 -
for (var modelIndex = 0; modelIndex < models.length; ++modelIndex) {
var model = models[modelIndex];
for (var ii = canvas.offset - canvas.increment; ii < model.count; ii += canvas.increment) {
if (model.bindingLoopDest(ii) >= 0) {
var xcenter = Math.round(model.startTime(ii) + model.duration(ii) / 2 -
zoomControl.traceStart) * canvas.spacing;
var ycenter = Math.round(canvas.bump + canvas.blockHeight * modelIndex +
canvas.blockHeight / 2);
@@ -93,25 +90,30 @@ function drawBindingLoops(canvas, ctxt) {
function drawNotes(canvas, ctxt)
{
if (!zoomControl || !qmlProfilerModelProxy || qmlProfilerModelProxy.noteCount() === 0)
if (!zoomControl || !models || !notes || notes.count === 0)
return;
var spacing = canvas.width / zoomControl.traceDuration;
var modelsById = models.reduce(function(prev, model) {
prev[model.modelId] = model;
return prev;
}, {});
// divide canvas height in 7 parts: margin, 3*line, space, dot, margin
var vertSpace = (canvas.height - canvas.bump) / 7;
ctxt.strokeStyle = "orange";
ctxt.lineWidth = 2;
for (var i = 0; i < qmlProfilerModelProxy.noteCount(); ++i) {
var timelineModel = qmlProfilerModelProxy.noteTimelineModel(i);
var timelineIndex = qmlProfilerModelProxy.noteTimelineIndex(i);
for (var i = 0; i < notes.count; ++i) {
var timelineModel = notes.timelineModel(i);
var timelineIndex = notes.timelineIndex(i);
if (timelineIndex === -1)
continue;
var start = Math.max(qmlProfilerModelProxy.startTime(timelineModel, timelineIndex),
var start = Math.max(modelsById[timelineModel].startTime(timelineIndex),
zoomControl.traceStart);
var end = Math.min(qmlProfilerModelProxy.endTime(timelineModel, timelineIndex),
zoomControl.traceStart);
var annoX = Math.round(((start + end) / 2 - zoomControl.traceStart) * spacing);
var end = Math.min(modelsById[timelineModel].endTime(timelineIndex),
zoomControl.traceEnd);
var annoX = Math.round(((start + end) / 2 - zoomControl.traceStart) * canvas.spacing);
ctxt.moveTo(annoX, canvas.bump + vertSpace)
ctxt.lineTo(annoX, canvas.bump + vertSpace * 4)

View File

@@ -98,8 +98,9 @@ Canvas {
onDataAvailable: {
dataReady = true;
increment = 0;
for (var i = 0; i < qmlProfilerModelProxy.modelCount(); ++i)
increment += qmlProfilerModelProxy.count(i);
var models = qmlProfilerModelProxy.models;
for (var i = 0; i < models.length; ++i)
increment += models[i].count;
increment = Math.ceil(increment / eventsPerPass);
offset = -1;
requestPaint();
@@ -119,8 +120,9 @@ Canvas {
onPaint: {
var context = (canvas.context === null) ? getContext("2d") : canvas.context;
Plotter.qmlProfilerModelProxy = qmlProfilerModelProxy;
Plotter.models = qmlProfilerModelProxy.models;
Plotter.zoomControl = zoomControl;
Plotter.notes = qmlProfilerModelProxy.notes;
if (offset < 0) {
context.reset();
@@ -143,6 +145,7 @@ Canvas {
Canvas {
property alias bump: canvas.bump
property alias spacing: canvas.spacing
property bool doPaint: false
onDoPaintChanged: {
if (doPaint)

View File

@@ -45,7 +45,10 @@ Item {
property int selectedModel: -1
property int selectedItem: -1
property bool locked: view.selectionLocked
property bool locked
property var models
property var notes
width: col.width + 25
height: col.height + 30
@@ -84,7 +87,8 @@ Item {
selectedModel = model;
selectedItem = item;
var eventData = qmlProfilerModelProxy.details(selectedModel, selectedItem)
var timelineModel = models[selectedModel];
var eventData = timelineModel.details(selectedItem)
eventInfo.clear();
for (var k in eventData) {
if (k === "displayName") {
@@ -96,7 +100,7 @@ Item {
}
rangeDetails.visible = true;
var location = qmlProfilerModelProxy.location(selectedModel, selectedItem)
var location = timelineModel.location(selectedItem)
if (location.hasOwnProperty("file")) { // not empty
file = location.file;
line = location.line;
@@ -109,7 +113,8 @@ Item {
}
noteEdit.focus = false;
noteEdit.text = qmlProfilerModelProxy.noteText(selectedModel, selectedItem);
var noteId = notes.get(timelineModel.modelId, selectedItem);
noteEdit.text = (noteId !== -1) ? notes.text(noteId) : "";
}
function fitInView() {
@@ -225,7 +230,7 @@ Item {
onFocusChanged: {
if (!focus && selectedModel != -1 && selectedItem != -1) {
saveTimer.stop();
qmlProfilerModelProxy.setNoteText(selectedModel, selectedItem, text);
notes.setText(models[selectedModel].modelId, selectedItem, text);
}
}
@@ -233,8 +238,7 @@ Item {
id: saveTimer
onTriggered: {
if (selectedModel != -1 && selectedItem != -1)
qmlProfilerModelProxy.setNoteText(selectedModel, selectedItem,
noteEdit.text);
notes.setText(models[selectedModel].modelId, selectedItem, noteEdit.text);
}
interval: 1000
}
@@ -250,7 +254,7 @@ Item {
drag.maximumY: root.height - parent.height
onClicked: {
root.gotoSourceLocation(file, line, column);
root.recenterOnItem(view.selectedModel, view.selectedItem);
root.recenterOnItem();
}
}
@@ -294,10 +298,7 @@ Item {
renderType: Text.NativeRendering
MouseArea {
anchors.fill: parent
onClicked: {
rangeDetails.hide();
view.selectFromEventIndex(view.selectedModel, -1);
}
onClicked: root.propagateSelection(-1, -1);
}
}

View File

@@ -51,7 +51,7 @@ RangeMover {
target: zoomControl
onRangeChanged: {
var oldTimePerPixel = selectionRange.viewTimePerPixel;
selectionRange.viewTimePerPixel = zoomControl.rangeDuration / view.intWidth;
selectionRange.viewTimePerPixel = zoomControl.rangeDuration / flick.intWidth;
if (creationState === 3 && oldTimePerPixel != selectionRange.viewTimePerPixel) {
var newWidth = rangeWidth * oldTimePerPixel / viewTimePerPixel;
rangeLeft = rangeLeft * oldTimePerPixel / viewTimePerPixel;

View File

@@ -36,6 +36,9 @@ Canvas {
objectName: "TimeMarks"
contextType: "2d"
property QtObject model
property bool startOdd
readonly property int scaleMinHeight: 60
readonly property int scaleStepping: 30
readonly property string units: " kMGT"
@@ -45,11 +48,14 @@ Canvas {
property real timePerPixel
Connections {
target: labels
target: model
onHeightChanged: requestPaint()
}
onStartTimeChanged: requestPaint()
onEndTimeChanged: requestPaint()
onYChanged: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
var context = (timeMarks.context === null) ? getContext("2d") : timeMarks.context;
@@ -70,36 +76,26 @@ Canvas {
timePerPixel = timePerBlock/pixelsPerBlock;
var lineStart = y < 0 ? -y : 0;
var lineEnd = Math.min(height, labels.height - y);
for (var ii = 0; ii < blockCount+1; ii++) {
var x = Math.floor(ii*pixelsPerBlock - realStartPos);
context.strokeStyle = "#B0B0B0";
context.beginPath();
context.moveTo(x, lineStart);
context.lineTo(x, lineEnd);
context.moveTo(x, 0);
context.lineTo(x, height);
context.stroke();
context.strokeStyle = "#CCCCCC";
for (var jj=1; jj < 5; jj++) {
var xx = Math.floor(ii*pixelsPerBlock + jj*pixelsPerSection - realStartPos);
context.beginPath();
context.moveTo(xx, lineStart);
context.lineTo(xx, lineEnd);
context.moveTo(xx, 0);
context.lineTo(xx, height);
context.stroke();
}
}
}
function updateMarks(start, end) {
if (startTime !== start || endTime !== end) {
startTime = start;
endTime = end;
requestPaint();
}
}
function prettyPrintScale(amount) {
var unitOffset = 0;
for (unitOffset = 0; amount > (1 << ((unitOffset + 1) * 10)); ++unitOffset) {}
@@ -117,26 +113,16 @@ Canvas {
}
function drawBackgroundBars( context, region ) {
var colorIndex = true;
var colorIndex = startOdd;
context.font = "8px sans-serif";
// separators
var cumulatedHeight = y < 0 ? -y : 0;
for (var modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); ++modelIndex) {
var modelHeight = qmlProfilerModelProxy.models[modelIndex].height;
if (modelHeight === 0)
continue;
if (cumulatedHeight + modelHeight < y) {
cumulatedHeight += modelHeight;
if (qmlProfilerModelProxy.rowCount(modelIndex) % 2 !== 0)
colorIndex = !colorIndex;
continue;
}
var cumulatedHeight = 0;
for (var row = 0; row < qmlProfilerModelProxy.rowCount(modelIndex); ++row) {
for (var row = 0; row < model.rowCount; ++row) {
// row background
var rowHeight = qmlProfilerModelProxy.rowHeight(modelIndex, row)
var rowHeight = model.rowHeight(row)
cumulatedHeight += rowHeight;
colorIndex = !colorIndex;
if (cumulatedHeight < y - rowHeight)
@@ -145,8 +131,8 @@ Canvas {
context.fillRect(0, cumulatedHeight - rowHeight - y, width, rowHeight);
if (rowHeight >= scaleMinHeight) {
var minVal = qmlProfilerModelProxy.rowMinValue(modelIndex, row);
var maxVal = qmlProfilerModelProxy.rowMaxValue(modelIndex, row);
var minVal = model.rowMinValue(row);
var maxVal = model.rowMaxValue(row);
if (minVal !== maxVal) {
context.strokeStyle = context.fillStyle = "#B0B0B0";
@@ -188,11 +174,4 @@ Canvas {
context.lineTo(width, cumulatedHeight - y);
context.stroke();
}
// bottom
if (height > labels.height - y) {
context.fillStyle = "#f5f5f5";
context.fillRect(0, labels.height - y, width, height - labels.height + y);
}
}
}

View File

@@ -101,6 +101,8 @@ QmlProfilerTraceView::QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerT
groupLayout->setContentsMargins(0, 0, 0, 0);
groupLayout->setSpacing(0);
qmlRegisterType<TimelineZoomControl>();
qmlRegisterType<QmlProfilerTimelineModel>();
qmlRegisterType<QmlProfilerNotesModel>();
d->m_mainView = new QmlProfilerQuickView(this);
@@ -195,7 +197,8 @@ void QmlProfilerTraceView::selectBySourceLocation(const QString &filename, int l
return;
for (int modelIndex = 0; modelIndex < d->m_modelProxy->modelCount(); ++modelIndex) {
int typeId = d->m_modelProxy->selectionIdForLocation(modelIndex, filename, line, column);
int typeId = d->m_modelProxy->model(modelIndex)->selectionIdForLocation(filename, line,
column);
if (typeId != -1) {
QMetaObject::invokeMethod(rootObject, "selectBySelectionId",
Q_ARG(QVariant,QVariant(modelIndex)),
@@ -264,7 +267,7 @@ void QmlProfilerTraceView::showContextMenu(QPoint position)
if (d->m_viewContainer->hasGlobalStats())
getGlobalStatsAction->setEnabled(false);
if (!d->m_modelProxy->isEmpty()) {
if (d->m_zoomControl->traceDuration() > 0) {
menu.addSeparator();
viewAllAction = menu.addAction(tr("Reset Zoom"));
}

View File

@@ -52,6 +52,7 @@ class QMLPROFILER_EXPORT TimelineModel : public QObject
Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)
Q_PROPERTY(QVariantList labels READ labels NOTIFY labelsChanged)
Q_PROPERTY(int count READ count NOTIFY emptyChanged)
Q_PROPERTY(int defaultRowHeight READ defaultRowHeight CONSTANT)
public:
class TimelineModelPrivate;

View File

@@ -41,45 +41,47 @@
#include <math.h>
using namespace QmlProfiler;
using namespace QmlProfiler::Internal;
TimelineRenderer::TimelineRenderer(QQuickPaintedItem *parent) :
QQuickPaintedItem(parent), m_spacing(0), m_spacedDuration(0), m_profilerModelProxy(0),
m_zoomer(0), m_selectedItem(-1), m_selectedModel(-1), m_selectionLocked(true),
m_startDragArea(-1), m_endDragArea(-1)
QQuickPaintedItem(parent), m_spacing(0), m_spacedDuration(0),
m_model(0), m_zoomer(0), m_notes(0), m_selectedItem(-1), m_selectionLocked(true)
{
resetCurrentSelection();
setAcceptedMouseButtons(Qt::LeftButton);
setAcceptHoverEvents(true);
connect(this, &QQuickItem::yChanged, this, &TimelineRenderer::requestPaint);
connect(this, &QQuickItem::xChanged, this, &TimelineRenderer::requestPaint);
connect(this, &QQuickItem::widthChanged, this, &TimelineRenderer::requestPaint);
connect(this, &QQuickItem::heightChanged, this, &TimelineRenderer::requestPaint);
}
void TimelineRenderer::setProfilerModelProxy(QObject *profilerModelProxy)
void TimelineRenderer::setModel(QmlProfilerTimelineModel *model)
{
if (m_profilerModelProxy) {
disconnect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint()));
disconnect(m_profilerModelProxy, SIGNAL(hiddenChanged()), this, SLOT(requestPaint()));
disconnect(m_profilerModelProxy, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint()));
disconnect(m_profilerModelProxy, SIGNAL(modelsChanged(int,int)),
this, SLOT(swapSelections(int,int)));
disconnect(m_profilerModelProxy, SIGNAL(notesChanged()), this, SLOT(requestPaint()));
}
m_profilerModelProxy = qobject_cast<TimelineModelAggregator *>(profilerModelProxy);
if (m_model == model)
return;
if (m_profilerModelProxy) {
connect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint()));
connect(m_profilerModelProxy, SIGNAL(hiddenChanged()), this, SLOT(requestPaint()));
connect(m_profilerModelProxy, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint()));
connect(m_profilerModelProxy, SIGNAL(modelsChanged(int,int)),
this, SLOT(swapSelections(int,int)));
connect(m_profilerModelProxy, SIGNAL(notesChanged(int,int,int)),
this, SLOT(requestPaint()));
if (m_model) {
disconnect(m_model, SIGNAL(expandedChanged()), this, SLOT(requestPaint()));
disconnect(m_model, SIGNAL(hiddenChanged()), this, SLOT(requestPaint()));
disconnect(m_model, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint()));
}
emit profilerModelProxyChanged(m_profilerModelProxy);
m_model = model;
if (m_model) {
connect(m_model, SIGNAL(expandedChanged()), this, SLOT(requestPaint()));
connect(m_model, SIGNAL(hiddenChanged()), this, SLOT(requestPaint()));
connect(m_model, SIGNAL(rowHeightChanged()), this, SLOT(requestPaint()));
}
emit modelChanged(m_model);
update();
}
void TimelineRenderer::setZoomer(QObject *zoomControl)
void TimelineRenderer::setZoomer(TimelineZoomControl *zoomer)
{
TimelineZoomControl *zoomer = qobject_cast<TimelineZoomControl *>(zoomControl);
if (zoomer != m_zoomer) {
if (m_zoomer != 0)
disconnect(m_zoomer, SIGNAL(rangeChanged(qint64,qint64)), this, SLOT(requestPaint()));
@@ -91,17 +93,20 @@ void TimelineRenderer::setZoomer(QObject *zoomControl)
}
}
void TimelineRenderer::componentComplete()
void TimelineRenderer::setNotes(QmlProfilerNotesModel *notes)
{
const QMetaObject *metaObject = this->metaObject();
int propertyCount = metaObject->propertyCount();
int requestPaintMethod = metaObject->indexOfMethod("requestPaint()");
for (int ii = TimelineRenderer::staticMetaObject.propertyCount(); ii < propertyCount; ++ii) {
QMetaProperty p = metaObject->property(ii);
if (p.hasNotifySignal())
QMetaObject::connect(this, p.notifySignalIndex(), this, requestPaintMethod, 0, 0);
}
QQuickItem::componentComplete();
if (m_notes == notes)
return;
if (m_notes)
disconnect(m_notes, &QmlProfilerNotesModel::changed, this, &TimelineRenderer::requestPaint);
m_notes = notes;
if (m_notes)
connect(m_notes, &QmlProfilerNotesModel::changed, this, &TimelineRenderer::requestPaint);
emit notesChanged(m_notes);
update();
}
void TimelineRenderer::requestPaint()
@@ -109,36 +114,22 @@ void TimelineRenderer::requestPaint()
update();
}
void TimelineRenderer::swapSelections(int modelIndex1, int modelIndex2)
inline void TimelineRenderer::getItemXExtent(int i, int &currentX, int &itemWidth)
{
// 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 &currentX, int &itemWidth)
{
qint64 start = m_profilerModelProxy->startTime(modelIndex, i) - m_zoomer->rangeStart();
qint64 start = m_model->startTime(i) - m_zoomer->rangeStart();
// avoid integer overflows by using floating point for width calculations. m_spacing is qreal,
// too, so for some intermediate calculations we have to use floats anyway.
qreal rawWidth;
if (start > 0) {
currentX = static_cast<int>(start * m_spacing);
rawWidth = m_profilerModelProxy->duration(modelIndex, i) * m_spacing;
rawWidth = m_model->duration(i) * m_spacing;
} else {
currentX = -OutOfScreenMargin;
// Explicitly round the "start" part down, away from 0, to match the implicit rounding of
// currentX in the > 0 case. If we don't do that we get glitches where a pixel is added if
// the element starts outside the screen and subtracted if it starts inside the screen.
rawWidth = m_profilerModelProxy->duration(modelIndex, i) * m_spacing +
rawWidth = m_model->duration(i) * m_spacing +
floor(start * m_spacing) + OutOfScreenMargin;
}
if (rawWidth < MinimumItemWidth) {
@@ -157,12 +148,11 @@ void TimelineRenderer::resetCurrentSelection()
m_currentSelection.endTime = -1;
m_currentSelection.row = -1;
m_currentSelection.eventIndex = -1;
m_currentSelection.modelIndex = -1;
}
void TimelineRenderer::paint(QPainter *p)
{
if (m_zoomer->rangeDuration() <= 0)
if (height() <= 0 || m_zoomer->rangeDuration() <= 0)
return;
m_spacing = width() / m_zoomer->rangeDuration();
@@ -170,56 +160,52 @@ void TimelineRenderer::paint(QPainter *p)
p->setPen(Qt::transparent);
for (int modelIndex = 0; modelIndex < m_profilerModelProxy->modelCount(); modelIndex++) {
if (m_profilerModelProxy->hidden(modelIndex))
continue;
int lastIndex = m_profilerModelProxy->lastIndex(modelIndex, m_zoomer->rangeEnd());
if (lastIndex >= 0 && lastIndex < m_profilerModelProxy->count(modelIndex)) {
int firstIndex = m_profilerModelProxy->firstIndex(modelIndex, m_zoomer->rangeStart());
int lastIndex = m_model->lastIndex(m_zoomer->rangeEnd());
if (lastIndex >= 0 && lastIndex < m_model->count()) {
int firstIndex = m_model->firstIndex(m_zoomer->rangeStart());
if (firstIndex >= 0) {
drawItemsToPainter(p, modelIndex, firstIndex, lastIndex);
if (m_selectedModel == modelIndex)
drawSelectionBoxes(p, modelIndex, firstIndex, lastIndex);
drawBindingLoopMarkers(p, modelIndex, firstIndex, lastIndex);
}
drawItemsToPainter(p, firstIndex, lastIndex);
drawSelectionBoxes(p, firstIndex, lastIndex);
drawBindingLoopMarkers(p, firstIndex, lastIndex);
}
}
drawNotes(p);
}
void TimelineRenderer::drawItemsToPainter(QPainter *p, int modelIndex, int fromIndex, int toIndex)
void TimelineRenderer::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event);
}
void TimelineRenderer::drawItemsToPainter(QPainter *p, int fromIndex, int toIndex)
{
p->save();
p->setPen(Qt::transparent);
int modelRowStart = 0;
for (int mi = 0; mi < modelIndex; mi++)
modelRowStart += m_profilerModelProxy->model(mi)->height();
for (int i = fromIndex; i <= toIndex; i++) {
int currentX, currentY, itemWidth, itemHeight;
int rowNumber = m_profilerModelProxy->row(modelIndex, i);
currentY = modelRowStart + m_profilerModelProxy->rowOffset(modelIndex, rowNumber) - y();
int rowNumber = m_model->row(i);
currentY = m_model->rowOffset(rowNumber) - y();
if (currentY >= height())
continue;
itemHeight = m_profilerModelProxy->rowHeight(modelIndex, rowNumber) *
m_profilerModelProxy->relativeHeight(modelIndex, i);
int rowHeight = m_model->rowHeight(rowNumber);
itemHeight = rowHeight * m_model->relativeHeight(i);
currentY += m_profilerModelProxy->rowHeight(modelIndex, rowNumber) - itemHeight;
currentY += rowHeight - itemHeight;
if (currentY + itemHeight < 0)
continue;
getItemXExtent(modelIndex, i, currentX, itemWidth);
getItemXExtent(i, currentX, itemWidth);
// normal events
p->setBrush(m_profilerModelProxy->color(modelIndex, i));
p->setBrush(m_model->color(i));
p->drawRect(currentX, currentY, itemWidth, itemHeight);
}
p->restore();
}
void TimelineRenderer::drawSelectionBoxes(QPainter *p, int modelIndex, int fromIndex, int toIndex)
void TimelineRenderer::drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex)
{
const uint strongLineWidth = 3;
const uint lightLineWidth = 2;
@@ -231,12 +217,7 @@ void TimelineRenderer::drawSelectionBoxes(QPainter *p, int modelIndex, int fromI
if (m_selectedItem == -1)
return;
int id = m_profilerModelProxy->selectionId(modelIndex, m_selectedItem);
int modelRowStart = 0;
for (int mi = 0; mi < modelIndex; mi++)
modelRowStart += m_profilerModelProxy->model(mi)->height();
int id = m_model->selectionId(m_selectedItem);
p->save();
@@ -249,19 +230,18 @@ void TimelineRenderer::drawSelectionBoxes(QPainter *p, int modelIndex, int fromI
int currentX, currentY, itemWidth;
for (int i = fromIndex; i <= toIndex; i++) {
if (m_profilerModelProxy->selectionId(modelIndex, i) != id)
if (m_model->selectionId(i) != id)
continue;
int row = m_profilerModelProxy->row(modelIndex, i);
int rowHeight = m_profilerModelProxy->rowHeight(modelIndex, row);
int itemHeight = rowHeight * m_profilerModelProxy->relativeHeight(modelIndex, i);
int row = m_model->row(i);
int rowHeight = m_model->rowHeight(row);
int itemHeight = rowHeight * m_model->relativeHeight(i);
currentY = modelRowStart + m_profilerModelProxy->rowOffset(modelIndex, row) + rowHeight -
itemHeight - y();
currentY = m_model->rowOffset(row) + rowHeight - itemHeight - y();
if (currentY + itemHeight < 0 || height() < currentY)
continue;
getItemXExtent(modelIndex, i, currentX, itemWidth);
getItemXExtent(i, currentX, itemWidth);
if (i == m_selectedItem)
p->setPen(strongPen);
@@ -297,7 +277,7 @@ void TimelineRenderer::drawSelectionBoxes(QPainter *p, int modelIndex, int fromI
p->restore();
}
void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int modelIndex, int fromIndex, int toIndex)
void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex)
{
int destindex;
int xfrom, xto, width;
@@ -310,19 +290,17 @@ void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int modelIndex, int f
p->save();
for (int i = fromIndex; i <= toIndex; i++) {
destindex = m_profilerModelProxy->bindingLoopDest(modelIndex, i);
destindex = m_model->bindingLoopDest(i);
if (destindex >= 0) {
// to
getItemXExtent(modelIndex, destindex, xto, width);
getItemXExtent(destindex, xto, width);
xto += width / 2;
yto = getYPosition(modelIndex, destindex) + m_profilerModelProxy->rowHeight(modelIndex,
m_profilerModelProxy->row(modelIndex, destindex)) / 2 - y();
yto = getYPosition(destindex) + m_model->rowHeight(m_model->row(destindex)) / 2 - y();
// from
getItemXExtent(modelIndex, i, xfrom, width);
getItemXExtent(i, xfrom, width);
xfrom += width / 2;
yfrom = getYPosition(modelIndex, i) + m_profilerModelProxy->rowHeight(modelIndex,
m_profilerModelProxy->row(modelIndex, i)) / 2 - y();
yfrom = getYPosition(i) + m_model->rowHeight(m_model->row(i)) / 2 - y();
// radius (derived from width of origin event)
radius = 5;
@@ -365,25 +343,19 @@ void TimelineRenderer::drawNotes(QPainter *p)
static const int annotationSpace = 4;
static const int shadowOffset = 2;
QmlProfilerNotesModel *notes = m_profilerModelProxy->notes();
for (int i = 0; i < notes->count(); ++i) {
int managerIndex = notes->timelineModel(i);
if (managerIndex == -1)
for (int i = 0; i < m_notes->count(); ++i) {
int modelId = m_notes->timelineModel(i);
if (modelId == -1 || modelId != m_model->modelId())
continue;
int modelIndex = m_profilerModelProxy->modelIndexFromManagerIndex(managerIndex);
if (m_profilerModelProxy->hidden(modelIndex))
continue;
int eventIndex = notes->timelineIndex(i);
int row = m_profilerModelProxy->row(modelIndex, eventIndex);
int rowHeight = m_profilerModelProxy->rowHeight(modelIndex, row);
int currentY = m_profilerModelProxy->rowOffset(modelIndex, row) - y();
for (int mi = 0; mi < modelIndex; mi++)
currentY += m_profilerModelProxy->model(mi)->height();
int eventIndex = m_notes->timelineIndex(i);
int row = m_model->row(eventIndex);
int rowHeight = m_model->rowHeight(row);
int currentY = m_model->rowOffset(row) - y();
if (currentY + rowHeight < 0 || height() < currentY)
continue;
int currentX;
int itemWidth;
getItemXExtent(modelIndex, eventIndex, currentX, itemWidth);
getItemXExtent(eventIndex, currentX, itemWidth);
// shadow
int annoX = currentX + (itemWidth - annotationWidth) / 2;
@@ -406,47 +378,20 @@ void TimelineRenderer::drawNotes(QPainter *p)
int TimelineRenderer::rowFromPosition(int y)
{
int ret = 0;
for (int modelIndex = 0; modelIndex < m_profilerModelProxy->modelCount(); modelIndex++) {
int modelHeight = m_profilerModelProxy->model(modelIndex)->height();
if (y < modelHeight) {
for (int row = 0; row < m_profilerModelProxy->rowCount(modelIndex); ++row) {
y -= m_profilerModelProxy->rowHeight(modelIndex, row);
for (int row = 0; row < m_model->rowCount(); ++row) {
y -= m_model->rowHeight(row);
if (y < 0) return ret;
++ret;
}
} else {
y -= modelHeight;
ret += m_profilerModelProxy->rowCount(modelIndex);
}
}
return ret;
}
int TimelineRenderer::modelFromPosition(int y)
{
for (int modelIndex = 0; modelIndex < m_profilerModelProxy->modelCount(); modelIndex++) {
y -= m_profilerModelProxy->model(modelIndex)->height();
if (y < 0)
return modelIndex;
}
return 0;
}
void TimelineRenderer::mousePressEvent(QMouseEvent *event)
{
// special case: if there is a drag area below me, don't accept the
// events unless I'm actually clicking inside an item
if (m_currentSelection.eventIndex == -1 &&
event->pos().x()+x() >= m_startDragArea &&
event->pos().x()+x() <= m_endDragArea)
event->setAccepted(false);
}
void TimelineRenderer::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
if (!m_profilerModelProxy->isEmpty())
if (!m_model->isEmpty())
manageClicked();
}
@@ -467,18 +412,19 @@ void TimelineRenderer::hoverMoveEvent(QHoverEvent *event)
void TimelineRenderer::manageClicked()
{
if (m_currentSelection.eventIndex != -1) {
if (m_currentSelection.eventIndex == m_selectedItem && m_currentSelection.modelIndex == m_selectedModel)
if (m_currentSelection.eventIndex == m_selectedItem)
setSelectionLocked(!m_selectionLocked);
else
setSelectionLocked(true);
// itemPressed() will trigger an update of the events and JavaScript views. Make sure the
// correct event is already selected when that happens, to prevent confusion.
selectFromEventIndex(m_currentSelection.modelIndex, m_currentSelection.eventIndex);
emit itemPressed(m_currentSelection.modelIndex, m_currentSelection.eventIndex);
setSelectedItem(m_currentSelection.eventIndex);
emit itemPressed(m_currentSelection.eventIndex);
} else {
setSelectionLocked(false);
selectFromEventIndex(m_currentSelection.modelIndex, m_currentSelection.eventIndex);
setSelectedItem(-1);
emit itemPressed(-1);
}
}
@@ -492,49 +438,41 @@ void TimelineRenderer::manageHovered(int mouseX, int mouseY)
qint64 startTime = (mouseX - 1) * duration / width() + m_zoomer->rangeStart();
qint64 endTime = (mouseX + 1) * duration / width() + m_zoomer->rangeStart();
int row = rowFromPosition(mouseY + y());
int modelIndex = modelFromPosition(mouseY + y());
// already covered? nothing to do
// already covered? Only recheck selectionLocked and make sure m_selectedItem is correct.
if (m_currentSelection.eventIndex != -1 &&
endTime >= m_currentSelection.startTime &&
startTime <= m_currentSelection.endTime &&
row == m_currentSelection.row) {
if (!m_selectionLocked)
setSelectedItem(m_currentSelection.eventIndex);
return;
}
// find if there's items in the time range
int eventFrom = m_profilerModelProxy->firstIndex(modelIndex, startTime);
int eventTo = m_profilerModelProxy->lastIndex(modelIndex, endTime);
if (eventFrom == -1 ||
eventTo < eventFrom || eventTo >= m_profilerModelProxy->count(modelIndex)) {
int eventFrom = m_model->firstIndex(startTime);
int eventTo = m_model->lastIndex(endTime);
if (eventFrom == -1 || eventTo < eventFrom || eventTo >= m_model->count()) {
m_currentSelection.eventIndex = -1;
return;
}
int modelRowStart = 0;
for (int mi = 0; mi < modelIndex; mi++)
modelRowStart += m_profilerModelProxy->rowCount(mi);
// find if we are in the right column
int itemRow;
for (int i=eventTo; i>=eventFrom; --i) {
itemRow = modelRowStart + m_profilerModelProxy->row(modelIndex, i);
if (itemRow == row) {
if ( m_model->row(i) == row) {
// There can be small events that don't reach the cursor position after large events
// that do but are in a different row.
qint64 itemEnd = m_profilerModelProxy->endTime(modelIndex, i);
qint64 itemEnd = m_model->endTime(i);
if (itemEnd < startTime)
continue;
// match
m_currentSelection.eventIndex = i;
m_currentSelection.startTime = m_profilerModelProxy->startTime(modelIndex, i);
m_currentSelection.startTime = m_model->startTime(i);
m_currentSelection.endTime = itemEnd;
m_currentSelection.row = row;
m_currentSelection.modelIndex = modelIndex;
if (!m_selectionLocked)
selectFromEventIndex(modelIndex, i);
setSelectedItem(i);
return;
}
}
@@ -549,43 +487,26 @@ void TimelineRenderer::clearData()
m_spacedDuration = 0;
resetCurrentSelection();
setSelectedItem(-1);
setSelectedModel(-1);
setSelectionLocked(true);
setStartDragArea(-1);
setEndDragArea(-1);
}
int TimelineRenderer::getYPosition(int modelIndex, int index) const
int TimelineRenderer::getYPosition(int index) const
{
Q_ASSERT(m_profilerModelProxy);
if (index >= m_profilerModelProxy->count(modelIndex))
Q_ASSERT(m_model);
if (index >= m_model->count())
return 0;
int modelRowStart = 0;
for (int mi = 0; mi < modelIndex; mi++)
modelRowStart += m_profilerModelProxy->model(mi)->height();
return modelRowStart + m_profilerModelProxy->rowOffset(modelIndex,
m_profilerModelProxy->row(modelIndex, index));
return m_model->rowOffset(m_model->row(index));
}
void TimelineRenderer::selectFromEventIndex(int modelIndex, int eventIndex)
void TimelineRenderer::selectNextFromSelectionId(int selectionId)
{
if (modelIndex != m_selectedModel || eventIndex != m_selectedItem) {
setSelectedModel(modelIndex);
setSelectedItem(eventIndex);
emit selectionChanged(modelIndex, eventIndex);
}
setSelectedItem(m_model->nextItemBySelectionId(selectionId, m_zoomer->rangeStart(),
m_selectedItem));
}
void TimelineRenderer::selectNextFromSelectionId(int modelIndex, int selectionId)
void TimelineRenderer::selectPrevFromSelectionId(int selectionId)
{
selectFromEventIndex(modelIndex, m_profilerModelProxy->model(modelIndex)->nextItemBySelectionId(
selectionId, m_zoomer->rangeStart(), m_selectedItem));
}
void TimelineRenderer::selectPrevFromSelectionId(int modelIndex, int selectionId)
{
selectFromEventIndex(modelIndex, m_profilerModelProxy->model(modelIndex)->prevItemBySelectionId(
selectionId, m_zoomer->rangeStart(), m_selectedItem));
setSelectedItem(m_model->prevItemBySelectionId(selectionId, m_zoomer->rangeStart(),
m_selectedItem));
}

View File

@@ -34,7 +34,8 @@
#include <QQuickPaintedItem>
#include <QJSValue>
#include "timelinezoomcontrol.h"
#include "timelinemodelaggregator.h"
#include "timelinemodel.h"
#include "qmlprofilernotesmodel.h"
namespace QmlProfiler {
namespace Internal {
@@ -42,13 +43,11 @@ namespace Internal {
class TimelineRenderer : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QObject *profilerModelProxy READ profilerModelProxy WRITE setProfilerModelProxy NOTIFY profilerModelProxyChanged)
Q_PROPERTY(QObject *zoomer READ zoomer WRITE setZoomer NOTIFY zoomerChanged)
Q_PROPERTY(QmlProfiler::QmlProfilerTimelineModel *model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(QmlProfiler::TimelineZoomControl *zoomer READ zoomer WRITE setZoomer NOTIFY zoomerChanged)
Q_PROPERTY(QmlProfiler::QmlProfilerNotesModel *notes READ notes WRITE setNotes NOTIFY notesChanged)
Q_PROPERTY(bool selectionLocked READ selectionLocked WRITE setSelectionLocked NOTIFY selectionLockedChanged)
Q_PROPERTY(int selectedItem READ selectedItem NOTIFY selectedItemChanged)
Q_PROPERTY(int selectedModel READ selectedModel NOTIFY selectedModelChanged)
Q_PROPERTY(int startDragArea READ startDragArea WRITE setStartDragArea NOTIFY startDragAreaChanged)
Q_PROPERTY(int endDragArea READ endDragArea WRITE setEndDragArea NOTIFY endDragAreaChanged)
Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem NOTIFY selectedItemChanged)
public:
explicit TimelineRenderer(QQuickPaintedItem *parent = 0);
@@ -63,93 +62,32 @@ public:
return m_selectedItem;
}
int selectedModel() const
{
return m_selectedModel;
}
int startDragArea() const
{
return m_startDragArea;
}
int endDragArea() const
{
return m_endDragArea;
}
TimelineModelAggregator *profilerModelProxy() const { return m_profilerModelProxy; }
void setProfilerModelProxy(QObject *profilerModelProxy);
QmlProfilerTimelineModel *model() const { return m_model; }
void setModel(QmlProfilerTimelineModel *model);
TimelineZoomControl *zoomer() const { return m_zoomer; }
void setZoomer(QObject *zoomer);
void setZoomer(TimelineZoomControl *zoomer);
Q_INVOKABLE int getYPosition(int modelIndex, int index) const;
QmlProfilerNotesModel *notes() const { return m_notes; }
void setNotes(QmlProfilerNotesModel *notes);
Q_INVOKABLE void selectFromEventIndex(int modelIndex, int index);
Q_INVOKABLE void selectNextFromSelectionId(int modelIndex, int selectionId);
Q_INVOKABLE void selectPrevFromSelectionId(int modelIndex, int selectionId);
Q_INVOKABLE int getYPosition(int index) const;
Q_INVOKABLE void selectNextFromSelectionId(int selectionId);
Q_INVOKABLE void selectPrevFromSelectionId(int selectionId);
signals:
void profilerModelProxyChanged(TimelineModelAggregator *list);
void modelChanged(TimelineModel *model);
void zoomerChanged(TimelineZoomControl *zoomer);
void notesChanged(QmlProfilerNotesModel *notes);
void selectionLockedChanged(bool locked);
void selectedItemChanged(int itemIndex);
void selectedModelChanged(int modelIndex);
void selectionChanged(int modelIndex, int itemIndex);
void startDragAreaChanged(int startDragArea);
void endDragAreaChanged(int endDragArea);
void itemPressed(int modelIndex, int pressedItem);
void itemPressed(int pressedItem);
public slots:
void clearData();
void requestPaint();
void swapSelections(int modelIndex1, int modelIndex2);
void setSelectionLocked(bool locked)
{
if (m_selectionLocked != locked) {
m_selectionLocked = locked;
update();
emit selectionLockedChanged(locked);
}
}
void setStartDragArea(int startDragArea)
{
if (m_startDragArea != startDragArea) {
m_startDragArea = startDragArea;
emit startDragAreaChanged(startDragArea);
}
}
void setEndDragArea(int endDragArea)
{
if (m_endDragArea != endDragArea) {
m_endDragArea = endDragArea;
emit endDragAreaChanged(endDragArea);
}
}
protected:
virtual void paint(QPainter *);
virtual void componentComplete();
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void hoverMoveEvent(QHoverEvent *event);
private:
void drawItemsToPainter(QPainter *p, int modelIndex, int fromIndex, int toIndex);
void drawSelectionBoxes(QPainter *p, int modelIndex, int fromIndex, int toIndex);
void drawBindingLoopMarkers(QPainter *p, int modelIndex, int fromIndex, int toIndex);
void drawNotes(QPainter *p);
int modelFromPosition(int y);
int rowFromPosition(int y);
void manageClicked();
void manageHovered(int mouseX, int mouseY);
void setSelectedItem(int itemIndex)
{
@@ -160,43 +98,55 @@ private:
}
}
void setSelectedModel(int modelIndex)
void setSelectionLocked(bool locked)
{
if (m_selectedModel != modelIndex) {
m_selectedModel = modelIndex;
if (m_selectionLocked != locked) {
m_selectionLocked = locked;
update();
emit selectedModelChanged(modelIndex);
emit selectionLockedChanged(locked);
}
}
protected:
virtual void paint(QPainter *);
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void hoverMoveEvent(QHoverEvent *event);
private:
enum IdType { SelectionId, TypeId };
void drawItemsToPainter(QPainter *p, int fromIndex, int toIndex);
void drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex);
void drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex);
void drawNotes(QPainter *p);
int rowFromPosition(int y);
void manageClicked();
void manageHovered(int mouseX, int mouseY);
static const int OutOfScreenMargin = 3; // margin to make sure the rectangles stay invisible
static const int MinimumItemWidth = 3;
inline void getItemXExtent(int modelIndex, int i, int &currentX, int &itemWidth);
inline void getItemXExtent(int i, int &currentX, int &itemWidth);
void resetCurrentSelection();
qreal m_spacing;
qreal m_spacedDuration;
TimelineModelAggregator *m_profilerModelProxy;
QmlProfilerTimelineModel *m_model;
TimelineZoomControl *m_zoomer;
QmlProfilerNotesModel *m_notes;
struct {
qint64 startTime;
qint64 endTime;
int row;
int eventIndex;
int modelIndex;
} m_currentSelection;
int m_selectedItem;
int m_selectedModel;
bool m_selectionLocked;
int m_startDragArea;
int m_endDragArea;
};
} // namespace Internal