From 3cc743f1bf54da047cefc575dac03aa71a0dc88b Mon Sep 17 00:00:00 2001
From: Ulf Hermann <ulf.hermann@digia.com>
Date: Thu, 27 Feb 2014 12:14:45 +0100
Subject: [PATCH] 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>
---
 src/plugins/qmlprofiler/qml/MainView.qml      | 297 +++++++++---------
 .../qmlprofiler/qml/SelectionRange.qml        |   6 +-
 src/plugins/qmlprofiler/qml/TimeMarks.qml     |   2 +-
 3 files changed, 151 insertions(+), 154 deletions(-)

diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml
index 0f5c805604d..8659e5e8ba8 100644
--- a/src/plugins/qmlprofiler/qml/MainView.qml
+++ b/src/plugins/qmlprofiler/qml/MainView.qml
@@ -66,6 +66,8 @@ Rectangle {
     property date recordingStartDate
     property real elapsedTime
 
+    color: "#dcdcdc"
+
     // ***** connections with external objects
     Connections {
         target: zoomControl
@@ -230,12 +232,51 @@ Rectangle {
     }
 
     Flickable {
-        id: vertflick
+        id: labelsflick
         flickableDirection: Flickable.VerticalFlick
-        anchors.fill: parent
-        clip: true
+        interactive: false
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        width: labels.width
+        contentY: flick.contentY
+
+        Rectangle {
+            id: labels
+            anchors.left: parent.left
+            width: 150
+            color: root.color
+            height: col.height
+
+            property int rowCount: qmlProfilerModelProxy.categoryCount();
+
+            Column {
+                id: col
+                Repeater {
+                    model: labels.rowCount
+                    delegate: CategoryLabel { }
+                }
+            }
+        }
+    }
+
+    // border between labels and timeline
+    Rectangle {
+        id: labelsborder
+        anchors.left: labelsflick.right
+        anchors.top: parent.top
+        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
@@ -244,178 +285,136 @@ Rectangle {
         onInteractiveChanged: interactive = stayInteractive
         onStayInteractiveChanged: interactive = stayInteractive
 
+        function setContentWidth() {
+            var duration = Math.abs(zoomControl.endTime() - zoomControl.startTime());
+            if (duration > 0)
+                contentWidth = qmlProfilerModelProxy.traceDuration() * width / duration;
+        }
+
+        onContentXChanged: view.updateZoomControl()
+        onWidthChanged: setContentWidth()
+
         // ***** child items
         TimeMarks {
             id: backgroundMarks
-            y: vertflick.contentY
-            height: vertflick.height
-            width: root.width - labels.width
-            anchors.left: labels.right
+            y: flick.contentY
+            x: flick.contentX
+            height: flick.height
+            width: scroller.width
         }
 
-        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;
-                    }
-                }
+        SelectionRange {
+            id: selectionRange
+            visible: root.selectionRangeMode && creationState !== 0
+            z: 2
+        }
 
-                onSelectedItemChanged: {
-                    if (selectedItem !== -1) {
-                        // display details
-                        rangeDetails.showInfo(qmlProfilerModelProxy.getEventDetails(selectedModel, selectedItem));
-                        rangeDetails.setLocation(qmlProfilerModelProxy.getEventLocation(selectedModel, selectedItem));
+        TimelineRenderer {
+            id: view
 
-                        // center view (horizontally)
-                        var windowLength = view.endTime - view.startTime;
-                        var eventStartTime = qmlProfilerModelProxy.getStartTime(selectedModel, selectedItem);
-                        var eventEndTime = eventStartTime +
-                                qmlProfilerModelProxy.getDuration(selectedModel, selectedItem);
+            profilerModelProxy: qmlProfilerModelProxy
 
-                        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)));
+            x: flick.contentX
+            y: flick.contentY
 
-                            zoomControl.setRange(from, from + windowLength);
+            // paint "under" the vertical scrollbar, so that it always matches with the timemarks
+            width: scroller.width
+            height: flick.height
 
-                        }
-                    } else {
-                        root.hideRangeDetails();
-                    }
-                }
+            onEndTimeChanged: requestPaint()
+            onYChanged: requestPaint()
+            onHeightChanged: requestPaint()
 
-                onItemPressed: {
-                    var location = qmlProfilerModelProxy.getEventLocation(modelIndex, pressedItem);
-                    if (location.hasOwnProperty("file")) // not empty
-                        root.gotoSourceLocation(location.file, location.line, location.column);
+            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);
                 }
-
-             // 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();
+
+            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;
                 }
             }
-        }
 
-        Rectangle {
-            id: labels
-            width: 150
-            color: "#dcdcdc"
-            height: col.height
+            onSelectedItemChanged: {
+                if (selectedItem !== -1) {
+                    // display details
+                    rangeDetails.showInfo(qmlProfilerModelProxy.getEventDetails(selectedModel, selectedItem));
+                    rangeDetails.setLocation(qmlProfilerModelProxy.getEventLocation(selectedModel, selectedItem));
 
-            property int rowCount: qmlProfilerModelProxy.categoryCount();
+                    // center view (horizontally)
+                    var windowLength = view.endTime - view.startTime;
+                    var eventStartTime = qmlProfilerModelProxy.getStartTime(selectedModel, selectedItem);
+                    var eventEndTime = eventStartTime +
+                            qmlProfilerModelProxy.getDuration(selectedModel, selectedItem);
 
-            Column {
-                id: col
-                Repeater {
-                    model: labels.rowCount
-                    delegate: CategoryLabel { }
+                    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();
                 }
             }
-        }
 
-        Rectangle {
-            id: labelsTail
-            anchors.top: labels.bottom
-            height: Math.max(0, vertflick.height - labels.height)
-            width: labels.width
-            color: labels.color
-        }
+            onItemPressed: {
+                var location = qmlProfilerModelProxy.getEventLocation(modelIndex, pressedItem);
+                if (location.hasOwnProperty("file")) // not empty
+                    root.gotoSourceLocation(location.file, location.line, location.column);
+            }
 
-        // border between labels and timeline
-        Rectangle {
-            anchors.left: labels.right
-            anchors.top: labels.top
-            anchors.bottom: labelsTail.bottom
-            width: 1
-            color: "#858585"
+         // 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 {
-        contentItem: vertflick
-        anchors.fill: parent
+        id: scroller
+        contentItem: flick
+        anchors.left: labelsborder.right
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+        anchors.right: parent.right
     }
 
     SelectionRangeDetails {
diff --git a/src/plugins/qmlprofiler/qml/SelectionRange.qml b/src/plugins/qmlprofiler/qml/SelectionRange.qml
index f565571294e..47a1bd94b4e 100644
--- a/src/plugins/qmlprofiler/qml/SelectionRange.qml
+++ b/src/plugins/qmlprofiler/qml/SelectionRange.qml
@@ -107,8 +107,7 @@ RangeMover {
     // creation control
     function releasedOnCreation() {
         if (selectionRange.creationState === 2) {
-            flick.interactive = true;
-            vertflick.stayInteractive = true;
+            flick.stayInteractive = true;
             selectionRange.creationState = 3;
             selectionRangeControl.enabled = false;
         }
@@ -116,8 +115,7 @@ RangeMover {
 
     function pressedOnCreation() {
         if (selectionRange.creationState === 1) {
-            flick.interactive = false;
-            vertflick.stayInteractive = false;
+            flick.stayInteractive = false;
             selectionRange.setPos(selectionRangeControl.mouseX + flick.contentX);
             selectionRange.creationState = 2;
         }
diff --git a/src/plugins/qmlprofiler/qml/TimeMarks.qml b/src/plugins/qmlprofiler/qml/TimeMarks.qml
index f9ac8e3590e..94e8fd058b0 100644
--- a/src/plugins/qmlprofiler/qml/TimeMarks.qml
+++ b/src/plugins/qmlprofiler/qml/TimeMarks.qml
@@ -128,7 +128,7 @@ Canvas {
         // bottom
         if (height > labels.height - y) {
             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);
         }
     }
 }
-- 
GitLab