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

View File

@@ -31,13 +31,14 @@
import QtQuick 2.1 import QtQuick 2.1
import Monitor 1.0 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 alias selectionLocked : view.selectionLocked property bool selectionLocked : true
property bool lockItemSelection : false property bool lockItemSelection : false
property real mainviewTimePerPixel: zoomControl.rangeDuration / root.width property real mainviewTimePerPixel: zoomControl.rangeDuration / root.width
@@ -47,6 +48,8 @@ Rectangle {
property int lineNumber: -1 property int lineNumber: -1
property int columnNumber: 0 property int columnNumber: 0
property int typeId: -1 property int typeId: -1
property int selectedModel: -1
property int selectedItem: -1
property bool selectionRangeMode: false property bool selectionRangeMode: false
@@ -60,12 +63,11 @@ Rectangle {
Connections { Connections {
target: zoomControl target: zoomControl
onRangeChanged: { onRangeChanged: {
backgroundMarks.updateMarks(zoomControl.rangeStart, zoomControl.rangeEnd);
zoomSliderToolBar.updateZoomLevel(); zoomSliderToolBar.updateZoomLevel();
view.updateWindow(); flick.updateWindow();
} }
onWindowChanged: { onWindowChanged: {
view.updateWindow(); flick.updateWindow();
} }
} }
@@ -73,15 +75,10 @@ Rectangle {
Connections { Connections {
target: qmlProfilerModelProxy target: qmlProfilerModelProxy
onDataAvailable: { onDataAvailable: {
view.clearData(); timelineView.clearChildren();
zoomControl.setRange(zoomControl.traceStart, zoomControl.setRange(zoomControl.traceStart,
zoomControl.traceStart + zoomControl.traceDuration / 10); 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() { function clear() {
flick.contentY = 0; flick.contentY = 0;
flick.contentX = 0; flick.contentX = 0;
flick.contentWidth = 0; flick.contentWidth = 0;
view.clearData(); timelineView.clearChildren();
rangeDetails.hide(); rangeDetails.hide();
selectionRangeMode = false; selectionRangeMode = false;
zoomSlider.externalUpdate = true; zoomSlider.externalUpdate = true;
@@ -107,34 +108,35 @@ Rectangle {
timeDisplay.clear(); timeDisplay.clear();
} }
function enableButtonsBar(enable) { function propagateSelection(newModel, newItem) {
buttonsBar.enabled = enable; if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem))
}
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)
return; return;
// if item is outside of the view, jump back to its position lockItemSelection = true;
if (qmlProfilerModelProxy.endTime(modelIndex, itemIndex) < zoomControl.rangeStart || if (selectedModel !== -1 && selectedModel !== newModel)
qmlProfilerModelProxy.startTime(modelIndex, itemIndex) > zoomControl.rangeEnd) { timelineView.select(selectedModel, -1);
recenter((qmlProfilerModelProxy.startTime(modelIndex, itemIndex) +
qmlProfilerModelProxy.endTime(modelIndex, itemIndex)) / 2); 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); lockItemSelection = false;
var totalRowOffset = qmlProfilerModelProxy.modelOffset(modelIndex) + }
qmlProfilerModelProxy.rowOffset(modelIndex, row);
if (totalRowOffset > flick.contentY + flick.height || function enableButtonsBar(enable) {
totalRowOffset + qmlProfilerModelProxy.rowHeight(modelIndex, row) < flick.contentY) buttonsBar.enabled = enable;
flick.contentY = Math.min(flick.contentHeight - flick.height,
Math.max(0, totalRowOffset - flick.height / 2));
} }
function selectByTypeId(typeId) function selectByTypeId(typeId)
@@ -142,8 +144,6 @@ Rectangle {
if (lockItemSelection || typeId === -1) if (lockItemSelection || typeId === -1)
return; return;
lockItemSelection = true;
var itemIndex = -1; var itemIndex = -1;
var modelIndex = -1; var modelIndex = -1;
var notes = qmlProfilerModelProxy.notesByTypeId(typeId); var notes = qmlProfilerModelProxy.notesByTypeId(typeId);
@@ -151,15 +151,16 @@ Rectangle {
modelIndex = qmlProfilerModelProxy.noteTimelineModel(notes[0]); modelIndex = qmlProfilerModelProxy.noteTimelineModel(notes[0]);
itemIndex = qmlProfilerModelProxy.noteTimelineIndex(notes[0]); itemIndex = qmlProfilerModelProxy.noteTimelineIndex(notes[0]);
} else { } else {
for (modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); ++modelIndex) { for (modelIndex = 0; modelIndex < qmlProfilerModelProxy.models.length; ++modelIndex) {
if (modelIndex === view.selectedModel && view.selectedItem !== -1 && if (modelIndex === selectedModel && selectedItem !== -1 &&
typeId === qmlProfilerModelProxy.typeId(modelIndex, view.selectedItem)) typeId === qmlProfilerModelProxy.models[modelIndex].typeId(selectedItem))
break; break;
if (!qmlProfilerModelProxy.handlesTypeId(modelIndex, typeId)) if (!qmlProfilerModelProxy.models[modelIndex].handlesTypeId(typeId))
continue; continue;
itemIndex = view.nextItemFromTypeId(modelIndex, typeId); itemIndex = qmlProfilerModelProxy.models[modelIndex].nextItemByTypeId(typeId,
zoomControl.rangeStart, selectedItem);
if (itemIndex !== -1) if (itemIndex !== -1)
break; break;
} }
@@ -167,11 +168,9 @@ 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
view.selectFromEventIndex(modelIndex, itemIndex); // triggers recentering timelineView.select(modelIndex, itemIndex);
view.selectionLocked = true; root.selectionLocked = true;
} }
lockItemSelection = false;
} }
// ***** slots // ***** slots
@@ -210,8 +209,6 @@ Rectangle {
color: root.color color: root.color
height: col.height height: col.height
property int rowCount: qmlProfilerModelProxy.modelCount();
Column { Column {
id: col id: col
@@ -220,15 +217,47 @@ Rectangle {
// events, so we can't use the drag events to determine the cursor shape. // events, so we can't use the drag events to determine the cursor shape.
property bool dragging: false property bool dragging: false
Repeater { DelegateModel {
model: labels.rowCount id: labelsModel
model: qmlProfilerModelProxy.models
delegate: CategoryLabel { delegate: CategoryLabel {
model: modelData
mockup: qmlProfilerModelProxy.height == 0
visualIndex: DelegateModel.itemsIndex
dragging: col.dragging dragging: col.dragging
reverseSelect: root.shiftPressed reverseSelect: root.shiftPressed
onDragStarted: col.dragging = true onDragStarted: col.dragging = true
onDragStopped: col.dragging = false onDragStopped: col.dragging = false
draggerParent: labels 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 onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.visible
onFilterMenuChanged: filterMenu.visible = !filterMenu.visible onFilterMenuChanged: filterMenu.visible = !filterMenu.visible
onJumpToNext: { onJumpToNext: {
var next = qmlProfilerModelProxy.nextItem(view.selectedModel, view.selectedItem, var next = qmlProfilerModelProxy.nextItem(root.selectedModel, root.selectedItem,
zoomControl.rangeStart); zoomControl.rangeStart);
view.selectFromEventIndex(next.model, next.item); timelineView.select(next.model, next.item);
} }
onJumpToPrev: { onJumpToPrev: {
var prev = qmlProfilerModelProxy.prevItem(view.selectedModel, view.selectedItem, var prev = qmlProfilerModelProxy.prevItem(root.selectedModel, root.selectedItem,
zoomControl.rangeEnd); zoomControl.rangeEnd);
view.selectFromEventIndex(prev.model, prev.item); timelineView.select(prev.model, prev.item);
} }
onRangeSelectChanged: selectionRangeMode = rangeButtonChecked(); onRangeSelectChanged: selectionRangeMode = rangeButtonChecked();
onLockChanged: selectionLocked = !lockButtonChecked(); onLockChanged: selectionLocked = !lockButtonChecked();
} }
@@ -290,40 +320,11 @@ Rectangle {
onInteractiveChanged: interactive = stayInteractive onInteractiveChanged: interactive = stayInteractive
onStayInteractiveChanged: 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 bool recursionGuard: false
property int intX: contentX
property int intWidth: scroller.width
property int intX: x // Update the zoom control on srolling.
property int intWidth: width
onIntXChanged: { onIntXChanged: {
if (recursionGuard) if (recursionGuard)
return; return;
@@ -340,6 +341,7 @@ Rectangle {
recursionGuard = false; recursionGuard = false;
} }
// Scroll when the zoom control is updated
function updateWindow() { function updateWindow() {
if (recursionGuard || zoomControl.rangeDuration <= 0) if (recursionGuard || zoomControl.rangeDuration <= 0)
return; return;
@@ -360,41 +362,158 @@ Rectangle {
recursionGuard = false; recursionGuard = false;
} }
onSelectionChanged: { // ***** child items
if (selectedItem !== -1) { SelectionRange {
// display details id: selectionRange
rangeDetails.showInfo(selectedModel, selectedItem); visible: root.selectionRangeMode && creationState !== 0
z: 2
}
// center view (horizontally) Column {
recenterOnItem(selectedModel, selectedItem); id: timelineView
if (!lockItemSelection) {
lockItemSelection = true; signal clearChildren
// update in other views signal select(int modelIndex, int eventIndex)
var eventLocation = qmlProfilerModelProxy.location(view.selectedModel,
view.selectedItem); // As we cannot retrieve items by visible index we keep an array of row counts here,
gotoSourceLocation(eventLocation.file, eventLocation.line, // for the time marks to draw the row backgrounds in the right colors.
eventLocation.column); property var rowCounts: new Array(qmlProfilerModelProxy.models.length)
root.typeId = qmlProfilerModelProxy.typeId(view.selectedModel,
view.selectedItem); DelegateModel {
root.updateCursorPosition(); id: timelineModel
lockItemSelection = false; 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 { } 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: { 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 if (location.hasOwnProperty("file")) // not empty
root.gotoSourceLocation(location.file, location.line, location.column); root.gotoSourceLocation(location.file, location.line, location.column);
root.typeId = qmlProfilerModelProxy.typeId(modelIndex, pressedItem); root.typeId = model.typeId(pressedItem);
root.updateCursorPosition(); root.updateCursorPosition();
} }
}
}
}
}
// hack to pass mouse events to the other mousearea if enabled Repeater {
startDragArea: selectionRange.ready ? selectionRange.rangeLeft : -x id: repeater
endDragArea: selectionRange.ready ? selectionRange.rangeRight : -x - 1 model: timelineModel
}
} }
MouseArea { MouseArea {
id: selectionRangeControl id: selectionRangeControl
@@ -441,6 +560,9 @@ Rectangle {
RangeDetails { RangeDetails {
id: rangeDetails id: rangeDetails
property alias locked: root.selectionLocked
models: qmlProfilerModelProxy.models
notes: qmlProfilerModelProxy.notes
} }
Rectangle { Rectangle {
@@ -451,7 +573,7 @@ Rectangle {
width: labels.width width: labels.width
anchors.left: parent.left anchors.left: parent.left
anchors.top: buttonsBar.bottom anchors.top: buttonsBar.bottom
height: qmlProfilerModelProxy.modelCount() * buttonsBar.height height: qmlProfilerModelProxy.models.length * buttonsBar.height
Repeater { Repeater {
id: filterMenuInner id: filterMenuInner
@@ -461,10 +583,10 @@ Rectangle {
anchors.right: filterMenu.right anchors.right: filterMenu.right
height: buttonsBar.height height: buttonsBar.height
y: index * height y: index * height
text: qmlProfilerModelProxy.models[index].displayName text: modelData.displayName
enabled: !qmlProfilerModelProxy.models[index].empty enabled: !modelData.empty
checked: enabled && !qmlProfilerModelProxy.models[index].hidden checked: enabled && !modelData.hidden
onCheckedChanged: qmlProfilerModelProxy.models[index].hidden = !checked onCheckedChanged: modelData.hidden = !checked
} }
} }
@@ -514,9 +636,11 @@ Rectangle {
minWindowLength); minWindowLength);
var fixedPoint = (zoomControl.rangeStart + zoomControl.rangeEnd) / 2; 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 // 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 && if (newFixedPoint >= zoomControl.rangeStart &&
newFixedPoint < zoomControl.rangeEnd) newFixedPoint < zoomControl.rangeEnd)
fixedPoint = newFixedPoint; fixedPoint = newFixedPoint;

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,6 +36,9 @@ Canvas {
objectName: "TimeMarks" objectName: "TimeMarks"
contextType: "2d" contextType: "2d"
property QtObject model
property bool startOdd
readonly property int scaleMinHeight: 60 readonly property int scaleMinHeight: 60
readonly property int scaleStepping: 30 readonly property int scaleStepping: 30
readonly property string units: " kMGT" readonly property string units: " kMGT"
@@ -45,11 +48,14 @@ Canvas {
property real timePerPixel property real timePerPixel
Connections { Connections {
target: labels target: model
onHeightChanged: requestPaint() onHeightChanged: requestPaint()
} }
onStartTimeChanged: requestPaint()
onEndTimeChanged: requestPaint()
onYChanged: requestPaint() onYChanged: requestPaint()
onHeightChanged: requestPaint()
onPaint: { onPaint: {
var context = (timeMarks.context === null) ? getContext("2d") : timeMarks.context; var context = (timeMarks.context === null) ? getContext("2d") : timeMarks.context;
@@ -70,36 +76,26 @@ Canvas {
timePerPixel = timePerBlock/pixelsPerBlock; 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++) { for (var ii = 0; ii < blockCount+1; ii++) {
var x = Math.floor(ii*pixelsPerBlock - realStartPos); var x = Math.floor(ii*pixelsPerBlock - realStartPos);
context.strokeStyle = "#B0B0B0"; context.strokeStyle = "#B0B0B0";
context.beginPath(); context.beginPath();
context.moveTo(x, lineStart); context.moveTo(x, 0);
context.lineTo(x, lineEnd); context.lineTo(x, height);
context.stroke(); context.stroke();
context.strokeStyle = "#CCCCCC"; context.strokeStyle = "#CCCCCC";
for (var jj=1; jj < 5; jj++) { for (var jj=1; jj < 5; jj++) {
var xx = Math.floor(ii*pixelsPerBlock + jj*pixelsPerSection - realStartPos); var xx = Math.floor(ii*pixelsPerBlock + jj*pixelsPerSection - realStartPos);
context.beginPath(); context.beginPath();
context.moveTo(xx, lineStart); context.moveTo(xx, 0);
context.lineTo(xx, lineEnd); context.lineTo(xx, height);
context.stroke(); context.stroke();
} }
} }
} }
function updateMarks(start, end) {
if (startTime !== start || endTime !== end) {
startTime = start;
endTime = end;
requestPaint();
}
}
function prettyPrintScale(amount) { function prettyPrintScale(amount) {
var unitOffset = 0; var unitOffset = 0;
for (unitOffset = 0; amount > (1 << ((unitOffset + 1) * 10)); ++unitOffset) {} for (unitOffset = 0; amount > (1 << ((unitOffset + 1) * 10)); ++unitOffset) {}
@@ -117,26 +113,16 @@ Canvas {
} }
function drawBackgroundBars( context, region ) { function drawBackgroundBars( context, region ) {
var colorIndex = true; var colorIndex = startOdd;
context.font = "8px sans-serif"; context.font = "8px sans-serif";
// separators // separators
var cumulatedHeight = y < 0 ? -y : 0; var cumulatedHeight = 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;
}
for (var row = 0; row < qmlProfilerModelProxy.rowCount(modelIndex); ++row) { for (var row = 0; row < model.rowCount; ++row) {
// row background // row background
var rowHeight = qmlProfilerModelProxy.rowHeight(modelIndex, row) var rowHeight = model.rowHeight(row)
cumulatedHeight += rowHeight; cumulatedHeight += rowHeight;
colorIndex = !colorIndex; colorIndex = !colorIndex;
if (cumulatedHeight < y - rowHeight) if (cumulatedHeight < y - rowHeight)
@@ -145,8 +131,8 @@ Canvas {
context.fillRect(0, cumulatedHeight - rowHeight - y, width, rowHeight); context.fillRect(0, cumulatedHeight - rowHeight - y, width, rowHeight);
if (rowHeight >= scaleMinHeight) { if (rowHeight >= scaleMinHeight) {
var minVal = qmlProfilerModelProxy.rowMinValue(modelIndex, row); var minVal = model.rowMinValue(row);
var maxVal = qmlProfilerModelProxy.rowMaxValue(modelIndex, row); var maxVal = model.rowMaxValue(row);
if (minVal !== maxVal) { if (minVal !== maxVal) {
context.strokeStyle = context.fillStyle = "#B0B0B0"; context.strokeStyle = context.fillStyle = "#B0B0B0";
@@ -188,11 +174,4 @@ Canvas {
context.lineTo(width, cumulatedHeight - y); context.lineTo(width, cumulatedHeight - y);
context.stroke(); 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->setContentsMargins(0, 0, 0, 0);
groupLayout->setSpacing(0); groupLayout->setSpacing(0);
qmlRegisterType<TimelineZoomControl>();
qmlRegisterType<QmlProfilerTimelineModel>();
qmlRegisterType<QmlProfilerNotesModel>(); qmlRegisterType<QmlProfilerNotesModel>();
d->m_mainView = new QmlProfilerQuickView(this); d->m_mainView = new QmlProfilerQuickView(this);
@@ -195,7 +197,8 @@ void QmlProfilerTraceView::selectBySourceLocation(const QString &filename, int l
return; return;
for (int modelIndex = 0; modelIndex < d->m_modelProxy->modelCount(); ++modelIndex) { 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) { if (typeId != -1) {
QMetaObject::invokeMethod(rootObject, "selectBySelectionId", QMetaObject::invokeMethod(rootObject, "selectBySelectionId",
Q_ARG(QVariant,QVariant(modelIndex)), Q_ARG(QVariant,QVariant(modelIndex)),
@@ -264,7 +267,7 @@ void QmlProfilerTraceView::showContextMenu(QPoint position)
if (d->m_viewContainer->hasGlobalStats()) if (d->m_viewContainer->hasGlobalStats())
getGlobalStatsAction->setEnabled(false); getGlobalStatsAction->setEnabled(false);
if (!d->m_modelProxy->isEmpty()) { if (d->m_zoomControl->traceDuration() > 0) {
menu.addSeparator(); menu.addSeparator();
viewAllAction = menu.addAction(tr("Reset Zoom")); 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(int rowCount READ rowCount NOTIFY rowCountChanged)
Q_PROPERTY(QVariantList labels READ labels NOTIFY labelsChanged) Q_PROPERTY(QVariantList labels READ labels NOTIFY labelsChanged)
Q_PROPERTY(int count READ count NOTIFY emptyChanged) Q_PROPERTY(int count READ count NOTIFY emptyChanged)
Q_PROPERTY(int defaultRowHeight READ defaultRowHeight CONSTANT)
public: public:
class TimelineModelPrivate; class TimelineModelPrivate;

View File

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

View File

@@ -34,7 +34,8 @@
#include <QQuickPaintedItem> #include <QQuickPaintedItem>
#include <QJSValue> #include <QJSValue>
#include "timelinezoomcontrol.h" #include "timelinezoomcontrol.h"
#include "timelinemodelaggregator.h" #include "timelinemodel.h"
#include "qmlprofilernotesmodel.h"
namespace QmlProfiler { namespace QmlProfiler {
namespace Internal { namespace Internal {
@@ -42,13 +43,11 @@ namespace Internal {
class TimelineRenderer : public QQuickPaintedItem class TimelineRenderer : public QQuickPaintedItem
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject *profilerModelProxy READ profilerModelProxy WRITE setProfilerModelProxy NOTIFY profilerModelProxyChanged) Q_PROPERTY(QmlProfiler::QmlProfilerTimelineModel *model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(QObject *zoomer READ zoomer WRITE setZoomer NOTIFY zoomerChanged) 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(bool selectionLocked READ selectionLocked WRITE setSelectionLocked NOTIFY selectionLockedChanged)
Q_PROPERTY(int selectedItem READ selectedItem NOTIFY selectedItemChanged) Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem 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)
public: public:
explicit TimelineRenderer(QQuickPaintedItem *parent = 0); explicit TimelineRenderer(QQuickPaintedItem *parent = 0);
@@ -63,93 +62,32 @@ public:
return m_selectedItem; return m_selectedItem;
} }
int selectedModel() const QmlProfilerTimelineModel *model() const { return m_model; }
{ void setModel(QmlProfilerTimelineModel *model);
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);
TimelineZoomControl *zoomer() const { return m_zoomer; } 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 int getYPosition(int index) const;
Q_INVOKABLE void selectNextFromSelectionId(int modelIndex, int selectionId);
Q_INVOKABLE void selectPrevFromSelectionId(int modelIndex, int selectionId); Q_INVOKABLE void selectNextFromSelectionId(int selectionId);
Q_INVOKABLE void selectPrevFromSelectionId(int selectionId);
signals: signals:
void profilerModelProxyChanged(TimelineModelAggregator *list); void modelChanged(TimelineModel *model);
void zoomerChanged(TimelineZoomControl *zoomer); void zoomerChanged(TimelineZoomControl *zoomer);
void notesChanged(QmlProfilerNotesModel *notes);
void selectionLockedChanged(bool locked); void selectionLockedChanged(bool locked);
void selectedItemChanged(int itemIndex); void selectedItemChanged(int itemIndex);
void selectedModelChanged(int modelIndex); void itemPressed(int pressedItem);
void selectionChanged(int modelIndex, int itemIndex);
void startDragAreaChanged(int startDragArea);
void endDragAreaChanged(int endDragArea);
void itemPressed(int modelIndex, int pressedItem);
public slots: public slots:
void clearData(); void clearData();
void requestPaint(); 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) void setSelectedItem(int itemIndex)
{ {
@@ -160,43 +98,55 @@ private:
} }
} }
void setSelectedModel(int modelIndex) void setSelectionLocked(bool locked)
{ {
if (m_selectedModel != modelIndex) { if (m_selectionLocked != locked) {
m_selectedModel = modelIndex; m_selectionLocked = locked;
update(); 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: 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 OutOfScreenMargin = 3; // margin to make sure the rectangles stay invisible
static const int MinimumItemWidth = 3; 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(); void resetCurrentSelection();
qreal m_spacing; qreal m_spacing;
qreal m_spacedDuration; qreal m_spacedDuration;
TimelineModelAggregator *m_profilerModelProxy; QmlProfilerTimelineModel *m_model;
TimelineZoomControl *m_zoomer; TimelineZoomControl *m_zoomer;
QmlProfilerNotesModel *m_notes;
struct { struct {
qint64 startTime; qint64 startTime;
qint64 endTime; qint64 endTime;
int row; int row;
int eventIndex; int eventIndex;
int modelIndex;
} m_currentSelection; } m_currentSelection;
int m_selectedItem; int m_selectedItem;
int m_selectedModel;
bool m_selectionLocked; bool m_selectionLocked;
int m_startDragArea;
int m_endDragArea;
}; };
} // namespace Internal } // namespace Internal