forked from qt-creator/qt-creator
QmlProfiler: Provide a horizontal scroll bar for the timeline
This requires the consolidation of the nested Flickable elements into one, which is probably a good idea anyway. The horizontal scroll bar is important because people might not understand that they can use the overview for scrolling. Change-Id: Ie1555265fc3edafaf6e6e4f34d77b0d034d45639 Reviewed-by: Kai Koehne <kai.koehne@digia.com>
This commit is contained in:
@@ -66,6 +66,8 @@ Rectangle {
|
|||||||
property date recordingStartDate
|
property date recordingStartDate
|
||||||
property real elapsedTime
|
property real elapsedTime
|
||||||
|
|
||||||
|
color: "#dcdcdc"
|
||||||
|
|
||||||
// ***** connections with external objects
|
// ***** connections with external objects
|
||||||
Connections {
|
Connections {
|
||||||
target: zoomControl
|
target: zoomControl
|
||||||
@@ -230,158 +232,20 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
id: vertflick
|
id: labelsflick
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
anchors.fill: parent
|
interactive: false
|
||||||
clip: true
|
anchors.top: parent.top
|
||||||
contentHeight: labels.height
|
anchors.bottom: parent.bottom
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
anchors.left: parent.left
|
||||||
|
width: labels.width
|
||||||
// ScrollView will try to deinteractivate it. We don't want that
|
contentY: flick.contentY
|
||||||
// as the horizontal flickable is interactive, too. We do occasionally
|
|
||||||
// switch to non-interactive ourselves, though.
|
|
||||||
property bool stayInteractive: true
|
|
||||||
onInteractiveChanged: interactive = stayInteractive
|
|
||||||
onStayInteractiveChanged: interactive = stayInteractive
|
|
||||||
|
|
||||||
// ***** child items
|
|
||||||
TimeMarks {
|
|
||||||
id: backgroundMarks
|
|
||||||
y: vertflick.contentY
|
|
||||||
height: vertflick.height
|
|
||||||
width: root.width - labels.width
|
|
||||||
anchors.left: labels.right
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
function setContentWidth() {
|
|
||||||
var duration = Math.abs(zoomControl.endTime() - zoomControl.startTime());
|
|
||||||
if (duration > 0)
|
|
||||||
contentWidth = qmlProfilerModelProxy.traceDuration() * width / duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
id: flick
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: labels.y
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.left: labels.right
|
|
||||||
contentWidth: 0
|
|
||||||
height: labels.height + labelsTail.height
|
|
||||||
flickableDirection: Flickable.HorizontalFlick
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
onContentXChanged: view.updateZoomControl()
|
|
||||||
onWidthChanged: setContentWidth()
|
|
||||||
|
|
||||||
clip:true
|
|
||||||
|
|
||||||
SelectionRange {
|
|
||||||
id: selectionRange
|
|
||||||
visible: root.selectionRangeMode && creationState !== 0
|
|
||||||
height: parent.height
|
|
||||||
z: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
TimelineRenderer {
|
|
||||||
id: view
|
|
||||||
|
|
||||||
profilerModelProxy: qmlProfilerModelProxy
|
|
||||||
|
|
||||||
x: flick.contentX
|
|
||||||
y: vertflick.contentY
|
|
||||||
width: flick.width
|
|
||||||
height: vertflick.height
|
|
||||||
|
|
||||||
onEndTimeChanged: requestPaint()
|
|
||||||
onYChanged: requestPaint()
|
|
||||||
onHeightChanged: requestPaint()
|
|
||||||
|
|
||||||
function updateZoomControl() {
|
|
||||||
var newStartTime = Math.round(flick.contentX * (endTime - startTime) / flick.width) +
|
|
||||||
qmlProfilerModelProxy.traceStartTime();
|
|
||||||
if (Math.abs(newStartTime - startTime) > 1) {
|
|
||||||
var newEndTime = Math.round((flick.contentX + flick.width) *
|
|
||||||
(endTime - startTime) /
|
|
||||||
flick.width) +
|
|
||||||
qmlProfilerModelProxy.traceStartTime();
|
|
||||||
zoomControl.setRange(newStartTime, newEndTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFlickRange(start, end) {
|
|
||||||
if (start !== startTime || end !== endTime) {
|
|
||||||
startTime = start;
|
|
||||||
endTime = end;
|
|
||||||
var newStartX = (startTime - qmlProfilerModelProxy.traceStartTime()) *
|
|
||||||
flick.width / (endTime-startTime);
|
|
||||||
if (isFinite(newStartX) && Math.abs(newStartX - flick.contentX) >= 1)
|
|
||||||
flick.contentX = newStartX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSelectedItemChanged: {
|
|
||||||
if (selectedItem !== -1) {
|
|
||||||
// display details
|
|
||||||
rangeDetails.showInfo(qmlProfilerModelProxy.getEventDetails(selectedModel, selectedItem));
|
|
||||||
rangeDetails.setLocation(qmlProfilerModelProxy.getEventLocation(selectedModel, selectedItem));
|
|
||||||
|
|
||||||
// center view (horizontally)
|
|
||||||
var windowLength = view.endTime - view.startTime;
|
|
||||||
var eventStartTime = qmlProfilerModelProxy.getStartTime(selectedModel, selectedItem);
|
|
||||||
var eventEndTime = eventStartTime +
|
|
||||||
qmlProfilerModelProxy.getDuration(selectedModel, selectedItem);
|
|
||||||
|
|
||||||
if (eventEndTime < view.startTime || eventStartTime > view.endTime) {
|
|
||||||
var center = (eventStartTime + eventEndTime)/2;
|
|
||||||
var from = Math.min(qmlProfilerModelProxy.traceEndTime()-windowLength,
|
|
||||||
Math.max(0, Math.floor(center - windowLength/2)));
|
|
||||||
|
|
||||||
zoomControl.setRange(from, from + windowLength);
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.hideRangeDetails();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onItemPressed: {
|
|
||||||
var location = qmlProfilerModelProxy.getEventLocation(modelIndex, pressedItem);
|
|
||||||
if (location.hasOwnProperty("file")) // not empty
|
|
||||||
root.gotoSourceLocation(location.file, location.line, location.column);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hack to pass mouse events to the other mousearea if enabled
|
|
||||||
startDragArea: selectionRange.ready ? selectionRange.getLeft() : -flick.contentX
|
|
||||||
endDragArea: selectionRange.ready ? selectionRange.getRight() : -flick.contentX-1
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: selectionRangeControl
|
|
||||||
enabled: false
|
|
||||||
width: flick.width
|
|
||||||
height: flick.height
|
|
||||||
x: flick.contentX
|
|
||||||
hoverEnabled: enabled
|
|
||||||
z: 2
|
|
||||||
|
|
||||||
onReleased: {
|
|
||||||
selectionRange.releasedOnCreation();
|
|
||||||
}
|
|
||||||
onPressed: {
|
|
||||||
selectionRange.pressedOnCreation();
|
|
||||||
}
|
|
||||||
onCanceled: {
|
|
||||||
selectionRange.releasedOnCreation();
|
|
||||||
}
|
|
||||||
onPositionChanged: {
|
|
||||||
selectionRange.movedOnCreation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: labels
|
id: labels
|
||||||
|
anchors.left: parent.left
|
||||||
width: 150
|
width: 150
|
||||||
color: "#dcdcdc"
|
color: root.color
|
||||||
height: col.height
|
height: col.height
|
||||||
|
|
||||||
property int rowCount: qmlProfilerModelProxy.categoryCount();
|
property int rowCount: qmlProfilerModelProxy.categoryCount();
|
||||||
@@ -394,28 +258,163 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
// border between labels and timeline
|
||||||
id: labelsTail
|
Rectangle {
|
||||||
anchors.top: labels.bottom
|
id: labelsborder
|
||||||
height: Math.max(0, vertflick.height - labels.height)
|
anchors.left: labelsflick.right
|
||||||
width: labels.width
|
anchors.top: parent.top
|
||||||
color: labels.color
|
anchors.bottom: parent.bottom
|
||||||
|
width: 1
|
||||||
|
color: "#858585"
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flick
|
||||||
|
contentHeight: labels.height
|
||||||
|
contentWidth: 0
|
||||||
|
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
clip:true
|
||||||
|
|
||||||
|
// ScrollView will try to deinteractivate it. We don't want that
|
||||||
|
// as the horizontal flickable is interactive, too. We do occasionally
|
||||||
|
// switch to non-interactive ourselves, though.
|
||||||
|
property bool stayInteractive: true
|
||||||
|
onInteractiveChanged: interactive = stayInteractive
|
||||||
|
onStayInteractiveChanged: interactive = stayInteractive
|
||||||
|
|
||||||
|
function setContentWidth() {
|
||||||
|
var duration = Math.abs(zoomControl.endTime() - zoomControl.startTime());
|
||||||
|
if (duration > 0)
|
||||||
|
contentWidth = qmlProfilerModelProxy.traceDuration() * width / duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// border between labels and timeline
|
onContentXChanged: view.updateZoomControl()
|
||||||
Rectangle {
|
onWidthChanged: setContentWidth()
|
||||||
anchors.left: labels.right
|
|
||||||
anchors.top: labels.top
|
// ***** child items
|
||||||
anchors.bottom: labelsTail.bottom
|
TimeMarks {
|
||||||
width: 1
|
id: backgroundMarks
|
||||||
color: "#858585"
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
onEndTimeChanged: requestPaint()
|
||||||
|
onYChanged: requestPaint()
|
||||||
|
onHeightChanged: requestPaint()
|
||||||
|
|
||||||
|
function updateZoomControl() {
|
||||||
|
var newStartTime = Math.round(flick.contentX * (endTime - startTime) / flick.width) +
|
||||||
|
qmlProfilerModelProxy.traceStartTime();
|
||||||
|
if (Math.abs(newStartTime - startTime) > 1) {
|
||||||
|
var newEndTime = Math.round((flick.contentX + flick.width) *
|
||||||
|
(endTime - startTime) /
|
||||||
|
flick.width) +
|
||||||
|
qmlProfilerModelProxy.traceStartTime();
|
||||||
|
zoomControl.setRange(newStartTime, newEndTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFlickRange(start, end) {
|
||||||
|
if (start !== startTime || end !== endTime) {
|
||||||
|
startTime = start;
|
||||||
|
endTime = end;
|
||||||
|
var newStartX = (startTime - qmlProfilerModelProxy.traceStartTime()) *
|
||||||
|
flick.width / (endTime-startTime);
|
||||||
|
if (isFinite(newStartX) && Math.abs(newStartX - flick.contentX) >= 1)
|
||||||
|
flick.contentX = newStartX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedItemChanged: {
|
||||||
|
if (selectedItem !== -1) {
|
||||||
|
// display details
|
||||||
|
rangeDetails.showInfo(qmlProfilerModelProxy.getEventDetails(selectedModel, selectedItem));
|
||||||
|
rangeDetails.setLocation(qmlProfilerModelProxy.getEventLocation(selectedModel, selectedItem));
|
||||||
|
|
||||||
|
// center view (horizontally)
|
||||||
|
var windowLength = view.endTime - view.startTime;
|
||||||
|
var eventStartTime = qmlProfilerModelProxy.getStartTime(selectedModel, selectedItem);
|
||||||
|
var eventEndTime = eventStartTime +
|
||||||
|
qmlProfilerModelProxy.getDuration(selectedModel, selectedItem);
|
||||||
|
|
||||||
|
if (eventEndTime < view.startTime || eventStartTime > view.endTime) {
|
||||||
|
var center = (eventStartTime + eventEndTime)/2;
|
||||||
|
var from = Math.min(qmlProfilerModelProxy.traceEndTime()-windowLength,
|
||||||
|
Math.max(0, Math.floor(center - windowLength/2)));
|
||||||
|
|
||||||
|
zoomControl.setRange(from, from + windowLength);
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root.hideRangeDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemPressed: {
|
||||||
|
var location = qmlProfilerModelProxy.getEventLocation(modelIndex, pressedItem);
|
||||||
|
if (location.hasOwnProperty("file")) // not empty
|
||||||
|
root.gotoSourceLocation(location.file, location.line, location.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hack to pass mouse events to the other mousearea if enabled
|
||||||
|
startDragArea: selectionRange.ready ? selectionRange.getLeft() : -flick.contentX
|
||||||
|
endDragArea: selectionRange.ready ? selectionRange.getRight() : -flick.contentX-1
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
id: selectionRangeControl
|
||||||
|
enabled: false
|
||||||
|
width: flick.width
|
||||||
|
height: flick.height
|
||||||
|
x: flick.contentX
|
||||||
|
y: flick.contentY
|
||||||
|
hoverEnabled: enabled
|
||||||
|
z: 2
|
||||||
|
|
||||||
|
onReleased: {
|
||||||
|
selectionRange.releasedOnCreation();
|
||||||
|
}
|
||||||
|
onPressed: {
|
||||||
|
selectionRange.pressedOnCreation();
|
||||||
|
}
|
||||||
|
onCanceled: {
|
||||||
|
selectionRange.releasedOnCreation();
|
||||||
|
}
|
||||||
|
onPositionChanged: {
|
||||||
|
selectionRange.movedOnCreation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
contentItem: vertflick
|
id: scroller
|
||||||
anchors.fill: parent
|
contentItem: flick
|
||||||
|
anchors.left: labelsborder.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionRangeDetails {
|
SelectionRangeDetails {
|
||||||
|
|||||||
@@ -107,8 +107,7 @@ RangeMover {
|
|||||||
// creation control
|
// creation control
|
||||||
function releasedOnCreation() {
|
function releasedOnCreation() {
|
||||||
if (selectionRange.creationState === 2) {
|
if (selectionRange.creationState === 2) {
|
||||||
flick.interactive = true;
|
flick.stayInteractive = true;
|
||||||
vertflick.stayInteractive = true;
|
|
||||||
selectionRange.creationState = 3;
|
selectionRange.creationState = 3;
|
||||||
selectionRangeControl.enabled = false;
|
selectionRangeControl.enabled = false;
|
||||||
}
|
}
|
||||||
@@ -116,8 +115,7 @@ RangeMover {
|
|||||||
|
|
||||||
function pressedOnCreation() {
|
function pressedOnCreation() {
|
||||||
if (selectionRange.creationState === 1) {
|
if (selectionRange.creationState === 1) {
|
||||||
flick.interactive = false;
|
flick.stayInteractive = false;
|
||||||
vertflick.stayInteractive = false;
|
|
||||||
selectionRange.setPos(selectionRangeControl.mouseX + flick.contentX);
|
selectionRange.setPos(selectionRangeControl.mouseX + flick.contentX);
|
||||||
selectionRange.creationState = 2;
|
selectionRange.creationState = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ Canvas {
|
|||||||
// bottom
|
// bottom
|
||||||
if (height > labels.height - y) {
|
if (height > labels.height - y) {
|
||||||
context.fillStyle = "#f5f5f5";
|
context.fillStyle = "#f5f5f5";
|
||||||
context.fillRect(0, labels.height - y, width, Math.min(height - labels.height + y, labelsTail.height));
|
context.fillRect(0, labels.height - y, width, height - labels.height + y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user