summaryrefslogtreecommitdiff
path: root/src/libs/timeline/qml/MainView.qml
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/timeline/qml/MainView.qml')
-rw-r--r--src/libs/timeline/qml/MainView.qml472
1 files changed, 472 insertions, 0 deletions
diff --git a/src/libs/timeline/qml/MainView.qml b/src/libs/timeline/qml/MainView.qml
new file mode 100644
index 0000000000..6b3e171e35
--- /dev/null
+++ b/src/libs/timeline/qml/MainView.qml
@@ -0,0 +1,472 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://www.qt.io/licensing. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import QtQuick 2.1
+import QtQuick.Controls 1.0
+
+Rectangle {
+ id: root
+
+ // ***** properties
+
+ property bool lockItemSelection : false
+
+ signal updateCursorPosition
+ property string fileName: ""
+ property int lineNumber: -1
+ property int columnNumber: 0
+ property int selectedModel: -1
+ property int selectedItem: -1
+
+ property bool selectionRangeMode: false
+ property bool selectionRangeReady: selectionRange.ready
+ property real selectionRangeStart: selectionRange.startTime
+ property real selectionRangeEnd: selectionRange.startTime + selectionRange.duration
+ property int typeId: content.typeId
+
+ onTypeIdChanged: updateCursorPosition()
+
+ color: "#dcdcdc"
+
+ // ***** connections with external objects
+ Connections {
+ target: zoomControl
+ onRangeChanged: {
+ zoomSliderToolBar.updateZoomLevel();
+ content.scroll();
+
+ // If you select something in the main view and then resize the current range by some
+ // other means, the selection should stay where it was.
+ var oldTimePerPixel = selectionRange.viewTimePerPixel;
+ selectionRange.viewTimePerPixel = zoomControl.rangeDuration / content.width;
+ if (selectionRange.creationState === selectionRange.creationFinished &&
+ oldTimePerPixel != selectionRange.viewTimePerPixel) {
+ var newWidth = selectionRange.rangeWidth * oldTimePerPixel /
+ selectionRange.viewTimePerPixel;
+ selectionRange.rangeLeft = selectionRange.rangeLeft * oldTimePerPixel /
+ selectionRange.viewTimePerPixel;
+ selectionRange.rangeRight = selectionRange.rangeLeft + newWidth;
+ }
+ }
+ onWindowChanged: {
+ content.scroll();
+ }
+ }
+
+
+ Connections {
+ target: timelineModelAggregator
+ onDataAvailable: {
+ content.clearChildren();
+ zoomControl.setRange(zoomControl.traceStart,
+ zoomControl.traceStart + zoomControl.traceDuration / 10);
+ }
+ }
+
+ onSelectionRangeModeChanged: {
+ selectionRange.reset();
+ buttonsBar.updateRangeButton(selectionRangeMode);
+ }
+
+ // ***** functions
+ function clear() {
+ content.clearChildren();
+ rangeDetails.hide();
+ selectionRangeMode = false;
+ zoomSlider.externalUpdate = true;
+ zoomSlider.value = zoomSlider.minimumValue;
+ }
+
+ function enableButtonsBar(enable) {
+ buttonsBar.enabled = enable;
+ }
+
+ function selectByTypeId(typeId)
+ {
+ if (lockItemSelection || typeId === -1)
+ return;
+
+ var itemIndex = -1;
+ var modelIndex = -1;
+
+ var notesModel = timelineModelAggregator.notes;
+ var notes = notesModel ? notesModel.byTypeId(typeId) : [];
+ if (notes.length !== 0) {
+ itemIndex = notesModel.timelineIndex(notes[0]);
+ var modelId = notesModel.timelineModel(notes[0]);
+ for (modelIndex = 0; modelIndex < timelineModelAggregator.models.length;
+ ++modelIndex) {
+ if (timelineModelAggregator.models[modelIndex].modelId === modelId)
+ break;
+ }
+ } else {
+ for (modelIndex = 0; modelIndex < timelineModelAggregator.models.length; ++modelIndex) {
+ if (modelIndex === selectedModel && selectedItem !== -1 &&
+ typeId === timelineModelAggregator.models[modelIndex].typeId(selectedItem))
+ break;
+
+ if (!timelineModelAggregator.models[modelIndex].handlesTypeId(typeId))
+ continue;
+
+ itemIndex = timelineModelAggregator.models[modelIndex].nextItemByTypeId(typeId,
+ zoomControl.rangeStart, selectedItem);
+ if (itemIndex !== -1)
+ break;
+ }
+ }
+
+ if (modelIndex !== -1 && modelIndex < timelineModelAggregator.models.length &&
+ itemIndex !== -1) {
+ // select an item, lock to it, and recenter if necessary
+ content.select(modelIndex, itemIndex);
+ content.selectionLocked = true;
+ }
+ }
+
+ focus: true
+ property bool shiftPressed: false;
+ Keys.onPressed: shiftPressed = (event.key === Qt.Key_Shift);
+ Keys.onReleased: shiftPressed = false;
+
+ TimelineLabels {
+ id: categories
+ anchors.top: buttonsBar.bottom
+ anchors.bottom: overview.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ contentY: content.contentY
+ selectedModel: root.selectedModel
+ selectedItem: root.selectedItem
+ color: root.color
+ modelProxy: timelineModelAggregator
+ zoomer: zoomControl
+ reverseSelect: shiftPressed
+
+ onMoveCategories: content.moveCategories(sourceIndex, targetIndex)
+ onSelectItem: content.select(modelIndex, eventIndex)
+ }
+
+ TimeDisplay {
+ id: timeDisplay
+ anchors.top: parent.top
+ anchors.left: buttonsBar.right
+ anchors.right: parent.right
+ anchors.bottom: overview.top
+ windowStart: zoomControl.windowStart
+ rangeDuration: Math.max(1, Math.round(zoomControl.rangeDuration))
+ contentX: content.contentX
+ clip: true
+ }
+
+ ButtonsBar {
+ id: buttonsBar
+ enabled: timelineModelAggregator.height > 0
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: 150
+ height: 24
+ onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.visible
+ onFilterMenuChanged: filterMenu.visible = !filterMenu.visible
+ onJumpToNext: {
+ var next = timelineModelAggregator.nextItem(root.selectedModel, root.selectedItem,
+ zoomControl.rangeStart);
+ content.select(next.model, next.item);
+ }
+ onJumpToPrev: {
+ var prev = timelineModelAggregator.prevItem(root.selectedModel, root.selectedItem,
+ zoomControl.rangeEnd);
+ content.select(prev.model, prev.item);
+ }
+
+ onRangeSelectChanged: selectionRangeMode = rangeButtonChecked();
+ onLockChanged: content.selectionLocked = !lockButtonChecked();
+ }
+
+ TimelineContent {
+ id: content
+ anchors.left: buttonsBar.right
+ anchors.top: buttonsBar.bottom
+ anchors.bottom: overview.top
+ anchors.right: parent.right
+ selectionLocked: true
+ zoomer: zoomControl
+ modelProxy: timelineModelAggregator
+
+ onSelectionLockedChanged: {
+ buttonsBar.updateLockButton(selectionLocked);
+ }
+
+ onPropagateSelection: {
+ if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem))
+ return;
+
+ lockItemSelection = true;
+ if (selectedModel !== -1 && selectedModel !== newModel)
+ select(selectedModel, -1);
+
+ selectedItem = newItem
+ selectedModel = newModel
+ if (selectedItem !== -1) {
+ // display details
+ rangeDetails.showInfo(selectedModel, selectedItem);
+
+ // update in other views
+ var model = timelineModelAggregator.models[selectedModel];
+ var eventLocation = model.location(selectedItem);
+ gotoSourceLocation(eventLocation.file, eventLocation.line,
+ eventLocation.column);
+ typeId = model.typeId(selectedItem);
+ } else {
+ rangeDetails.hide();
+ }
+ lockItemSelection = false;
+ }
+
+ onGotoSourceLocation: {
+ if (file !== undefined) {
+ root.fileName = file;
+ root.lineNumber = line;
+ root.columnNumber = column;
+ }
+ }
+ }
+
+ MouseArea {
+ id: selectionRangeControl
+ enabled: selectionRangeMode &&
+ selectionRange.creationState !== selectionRange.creationFinished
+ anchors.right: content.right
+ anchors.left: buttonsBar.right
+ anchors.top: content.top
+ anchors.bottom: content.bottom
+ hoverEnabled: enabled
+ z: 2
+
+ onReleased: {
+ if (selectionRange.creationState === selectionRange.creationSecondLimit) {
+ content.stayInteractive = true;
+ selectionRange.creationState = selectionRange.creationFinished;
+ }
+ }
+ onPressed: {
+ if (selectionRange.creationState === selectionRange.creationFirstLimit) {
+ content.stayInteractive = false;
+ selectionRange.setPos(selectionRangeControl.mouseX + content.contentX);
+ selectionRange.creationState = selectionRange.creationSecondLimit;
+ }
+ }
+ onPositionChanged: {
+ if (selectionRange.creationState === selectionRange.creationInactive)
+ selectionRange.creationState = selectionRange.creationFirstLimit;
+
+ if (selectionRangeControl.pressed ||
+ selectionRange.creationState !== selectionRange.creationFinished)
+ selectionRange.setPos(selectionRangeControl.mouseX + content.contentX);
+ }
+ onCanceled: pressed()
+ }
+
+ Flickable {
+ flickableDirection: Flickable.HorizontalFlick
+ clip: true
+ visible: selectionRangeMode &&
+ selectionRange.creationState !== selectionRange.creationInactive
+ interactive: false
+ x: content.x + content.flickableItem.x
+ y: content.y + content.flickableItem.y
+ height: content.flickableItem.height
+ width: content.flickableItem.width
+ contentX: content.contentX
+ contentWidth: content.contentWidth
+
+ SelectionRange {
+ id: selectionRange
+ zoomer: zoomControl
+ visible: parent.visible
+
+ onRangeDoubleClicked: {
+ zoomControl.setRange(startTime, endTime);
+ root.selectionRangeMode = false;
+ }
+
+ }
+ }
+
+ SelectionRangeDetails {
+ z: 1
+ x: 200
+ y: 125
+
+ id: selectionRangeDetails
+ visible: selectionRange.visible
+ startTime: selectionRange.startTime
+ duration: selectionRange.duration
+ endTime: selectionRange.endTime
+ showDuration: selectionRange.rangeWidth > 1
+
+ onRecenter: {
+ if ((selectionRange.startTime < zoomControl.rangeStart) ^
+ (selectionRange.endTime > zoomControl.rangeEnd)) {
+ var center = selectionRange.startTime + selectionRange.duration / 2;
+ var halfDuration = Math.max(selectionRange.duration, zoomControl.rangeDuration / 2);
+ zoomControl.setRange(center - halfDuration, center + halfDuration);
+ }
+ }
+
+ onClose: selectionRangeMode = false;
+ }
+
+ RangeDetails {
+ id: rangeDetails
+
+ z: 1
+ visible: false
+ x: 200
+ y: 25
+
+ locked: content.selectionLocked
+ models: timelineModelAggregator.models
+ notes: timelineModelAggregator.notes
+ onRecenterOnItem: {
+ content.gotoSourceLocation(file, line, column);
+ content.select(selectedModel, selectedItem)
+ }
+ onToggleSelectionLocked: {
+ content.selectionLocked = !content.selectionLocked;
+ }
+ onClearSelection: {
+ content.propagateSelection(-1, -1);
+ }
+ }
+
+ Rectangle {
+ anchors.left: buttonsBar.right
+ anchors.bottom: overview.top
+ anchors.top: parent.top
+ width: 1
+ color: "#B0B0B0"
+ }
+
+ Rectangle {
+ id: filterMenu
+ color: "#9b9b9b"
+ enabled: buttonsBar.enabled
+ visible: false
+ width: buttonsBar.width
+ anchors.left: parent.left
+ anchors.top: buttonsBar.bottom
+ height: timelineModelAggregator.models.length * buttonsBar.height
+
+ Repeater {
+ id: filterMenuInner
+ model: timelineModelAggregator.models
+ CheckBox {
+ anchors.left: filterMenu.left
+ anchors.right: filterMenu.right
+ height: buttonsBar.height
+ y: index * height
+ text: modelData.displayName
+ enabled: !modelData.empty
+ checked: enabled && !modelData.hidden
+ onCheckedChanged: modelData.hidden = !checked
+ }
+ }
+
+ }
+
+ Rectangle {
+ id: zoomSliderToolBar
+ objectName: "zoomSliderToolBar"
+ color: "#9b9b9b"
+ enabled: buttonsBar.enabled
+ visible: false
+ width: buttonsBar.width
+ height: buttonsBar.height
+ anchors.left: parent.left
+ anchors.top: buttonsBar.bottom
+
+ function updateZoomLevel() {
+ zoomSlider.externalUpdate = true;
+ zoomSlider.value = Math.pow(zoomControl.rangeDuration /
+ Math.max(1, zoomControl.windowDuration),
+ 1 / zoomSlider.exponent) * zoomSlider.maximumValue;
+ }
+
+ Slider {
+ id: zoomSlider
+ anchors.fill: parent
+ minimumValue: 1
+ maximumValue: 10000
+ stepSize: 100
+
+ property int exponent: 3
+ property bool externalUpdate: false
+ property int minWindowLength: 1e5 // 0.1 ms
+
+ onValueChanged: {
+ if (externalUpdate || zoomControl.windowEnd <= zoomControl.windowStart) {
+ // Zoom range is independently updated. We shouldn't mess
+ // with it here as otherwise we might introduce rounding
+ // or arithmetic errors.
+ externalUpdate = false;
+ return;
+ }
+
+ var windowLength = Math.max(
+ Math.pow(value / maximumValue, exponent) * zoomControl.windowDuration,
+ minWindowLength);
+
+ var fixedPoint = (zoomControl.rangeStart + zoomControl.rangeEnd) / 2;
+ if (root.selectedItem !== -1) {
+ // center on selected item if it's inside the current screen
+ var model = timelineModelAggregator.models[root.selectedModel]
+ var newFixedPoint = (model.startTime(root.selectedItem) +
+ model.endTime(root.selectedItem)) / 2;
+ if (newFixedPoint >= zoomControl.rangeStart &&
+ newFixedPoint < zoomControl.rangeEnd)
+ fixedPoint = newFixedPoint;
+ }
+
+ var startTime = Math.max(zoomControl.windowStart, fixedPoint - windowLength / 2)
+ zoomControl.setRange(startTime, startTime + windowLength);
+ }
+ }
+ }
+
+ Overview {
+ id: overview
+ height: 50
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.left: parent.left
+ modelProxy: timelineModelAggregator
+ zoomer: zoomControl
+ }
+}