diff options
author | Ulf Hermann <[email protected]> | 2018-05-03 09:50:11 +0200 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2018-05-04 14:08:47 +0000 |
commit | 734611131dc20283b118353130bdd5e16ce0aeaf (patch) | |
tree | 91adf7b62f98765a46bc5c544a1808cde1b1bf82 /src/libs/tracing | |
parent | b9b2f2c1875030b8259d22cba69161304ada7011 (diff) |
Move Timeline and FlameGraph into a common "Tracing" library
This allows us to share code between the two, in particular the QML code
for the Details window, and the theme code. This way we can potentially
deduplicate some code.
Change-Id: I3a0d26b18488bd2a46b5b077b5b5d79ac2dfc5ce
Reviewed-by: Christian Kandeler <[email protected]>
Reviewed-by: Eike Ziller <[email protected]>
Diffstat (limited to 'src/libs/tracing')
86 files changed, 10308 insertions, 0 deletions
diff --git a/src/libs/tracing/README b/src/libs/tracing/README new file mode 100644 index 00000000000..13b4c6b11a1 --- /dev/null +++ b/src/libs/tracing/README @@ -0,0 +1,50 @@ + +The timeline library can be used to display timeline data, primarily for performance analysis. It +provides a comprehensive set of visual components as well as a set of models that can be extended to +hold custom timeline data. You can use all the provided QML to get a complete GUI similar to the QML +profiler or you can mix and match the QML components with your own. + +Models +------ + +At the core of the timeline library is the TimelineModel. You can create multiple TimelineModels to +represent different categories of data. The TimelineModelAggregator class is then used to manage +those models. TimelineModels are expected to load their contents all at once and then only change if +you clear() and possibly reload them. To complement that, you can use the TimelineNotesModel. The +TimelineNotesModel is designed to hold less, but mutable, data that spans all TimelineModels in a +TimelineModelAggregator. + +Views +----- + +Expose the TimelineRenderer class to QML and add a TimelineModel to it to get a visualization of the +data in the model. TimelineRenderer will generally use the TimelineRenderPasses the TimelineModel +suggests. It contains a caching system for keeping different versions of the visualization, +depending on position and zoom level, so that the picture always stays sharp and numerical overflows +are avoided. To do that it needs a TimelineZoomControl which manages zooming and scrolling. + +A simplified display for the contents of a TimelineModel, based on the same render passes as the +TimelineRenderer, can be found in TimelineOverviewRenderer. TimelineOverviewRenderer will squeeze +the data into a fixed height and only allow all the data to be displayed at once. It won't react to +zooming or scrolling and can easily be used for a more concise overview. + +Various utilities are provided in the QML code. CategoryLabels can be used to provide a legend for +data rendered using a TimelineRenderer. TimeMarks provides colored bars that can be layered behind +a TimelineRenderer to make the rows easier to distinguish. TimeDisplay provides a legend for the +time spanned by a timeline. + +Render Passes +------------- + +Render passes for TimelineRenderer and TimelineOverviewRenderer are used to create the scene graph +nodes to display data from TimelineModels. You can add your own RenderPasses. For an example, see +QmlProfilerBindingLoopsRenderPass in the qmlprofiler plugin. In general, a render pass is stateless +and will update only the render pass state specific to itself and the model being rendered. A render +pass state can contain scene graph subtrees for each row in both the expanded and collapsed states +of the view, as well as scene graph subtrees to be overlayed on top of the whole category. The +renderer will layer the subtrees according to the order in which the render passes are given. + +Each render pass has to provide an update() method which is called to update data in the given +render state or create a new render state. The rows in expanded state can be vertically resized. To +deal with this, keep the nodes in rows with a QSGTransformNode as parent for each of them. The +overlay mode should only be used for things that span multiple rows. diff --git a/src/libs/tracing/flamegraph.cpp b/src/libs/tracing/flamegraph.cpp new file mode 100644 index 00000000000..2fa38313edc --- /dev/null +++ b/src/libs/tracing/flamegraph.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "flamegraph.h" + +namespace FlameGraph { + +FlameGraph::FlameGraph(QQuickItem *parent) : + QQuickItem(parent) +{ + // Queue the rebuild in this case so that a delegate can set the root without getting deleted + // during the call. + connect(this, &FlameGraph::rootChanged, this, &FlameGraph::rebuild, Qt::QueuedConnection); +} + +QQmlComponent *FlameGraph::delegate() const +{ + return m_delegate; +} + +void FlameGraph::setDelegate(QQmlComponent *delegate) +{ + if (delegate != m_delegate) { + m_delegate = delegate; + emit delegateChanged(delegate); + } +} + +QAbstractItemModel *FlameGraph::model() const +{ + return m_model; +} + +void FlameGraph::setModel(QAbstractItemModel *model) +{ + if (model != m_model) { + if (m_model) + disconnect(m_model, &QAbstractItemModel::modelReset, this, &FlameGraph::rebuild); + + m_model = model; + if (m_model) + connect(m_model, &QAbstractItemModel::modelReset, this, &FlameGraph::rebuild); + emit modelChanged(model); + rebuild(); + } +} + +int FlameGraph::sizeRole() const +{ + return m_sizeRole; +} + +void FlameGraph::setSizeRole(int sizeRole) +{ + if (sizeRole != m_sizeRole) { + m_sizeRole = sizeRole; + emit sizeRoleChanged(sizeRole); + rebuild(); + } +} + +qreal FlameGraph::sizeThreshold() const +{ + return m_sizeThreshold; +} + +void FlameGraph::setSizeThreshold(qreal sizeThreshold) +{ + if (sizeThreshold != m_sizeThreshold) { + m_sizeThreshold = sizeThreshold; + emit sizeThresholdChanged(sizeThreshold); + rebuild(); + } +} + +int FlameGraph::depth() const +{ + return m_depth; +} + +FlameGraphAttached *FlameGraph::qmlAttachedProperties(QObject *object) +{ + FlameGraphAttached *attached = + object->findChild<FlameGraphAttached *>(QString(), Qt::FindDirectChildrenOnly); + if (!attached) + attached = new FlameGraphAttached(object); + return attached; +} + +QObject *FlameGraph::appendChild(QObject *parentObject, QQuickItem *parentItem, + QQmlContext *context, const QModelIndex &childIndex, + qreal position, qreal size) +{ + QObject *childObject = m_delegate->beginCreate(context); + if (parentItem) { + QQuickItem *childItem = qobject_cast<QQuickItem *>(childObject); + if (childItem) + childItem->setParentItem(parentItem); + } + childObject->setParent(parentObject); + FlameGraphAttached *attached = FlameGraph::qmlAttachedProperties(childObject); + attached->setRelativePosition(position); + attached->setRelativeSize(size); + attached->setModelIndex(childIndex); + connect(m_model, &QAbstractItemModel::dataChanged, + attached, &FlameGraphAttached::modelIndexChanged); + m_delegate->completeCreate(); + return childObject; +} + + +int FlameGraph::buildNode(const QModelIndex &parentIndex, QObject *parentObject, int depth, + int maximumDepth) +{ + qreal position = 0; + qreal skipped = 0; + qreal parentSize = m_model->data(parentIndex, m_sizeRole).toReal(); + QQuickItem *parentItem = qobject_cast<QQuickItem *>(parentObject); + QQmlContext *context = qmlContext(this); + int rowCount = m_model->rowCount(parentIndex); + int childrenDepth = depth; + if (depth == maximumDepth - 1) { + skipped = parentSize; + } else { + for (int row = 0; row < rowCount; ++row) { + QModelIndex childIndex = m_model->index(row, 0, parentIndex); + qreal size = m_model->data(childIndex, m_sizeRole).toReal(); + if (size / m_model->data(m_root, m_sizeRole).toReal() < m_sizeThreshold) { + skipped += size; + continue; + } + + QObject *childObject = appendChild(parentObject, parentItem, context, childIndex, + position / parentSize, size / parentSize); + position += size; + childrenDepth = qMax(childrenDepth, buildNode(childIndex, childObject, depth + 1, + maximumDepth)); + } + } + + // Invisible Root object: attribute all remaining width to "others" + if (!parentIndex.isValid()) + skipped = parentSize - position; + + if (skipped > 0) { + appendChild(parentObject, parentItem, context, QModelIndex(), position / parentSize, + skipped / parentSize); + childrenDepth = qMax(childrenDepth, depth + 1); + } + + return childrenDepth; +} + +void FlameGraph::rebuild() +{ + qDeleteAll(childItems()); + childItems().clear(); + m_depth = 0; + + if (!m_model) { + emit depthChanged(m_depth); + return; + } + + if (m_root.isValid()) { + QObject *parentObject = appendChild(this, this, qmlContext(this), m_root, 0, 1); + m_depth = buildNode(m_root, parentObject, 1, m_maximumDepth); + } else { + m_depth = buildNode(m_root, this, 0, m_maximumDepth); + } + + emit depthChanged(m_depth); +} + +} // namespace FlameGraph diff --git a/src/libs/tracing/flamegraph.h b/src/libs/tracing/flamegraph.h new file mode 100644 index 00000000000..a01c44ea39e --- /dev/null +++ b/src/libs/tracing/flamegraph.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include "flamegraphattached.h" + +#include <QQuickItem> +#include <QAbstractItemModel> + +namespace FlameGraph { + +class TRACING_EXPORT FlameGraph : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(int sizeRole READ sizeRole WRITE setSizeRole NOTIFY sizeRoleChanged) + Q_PROPERTY(qreal sizeThreshold READ sizeThreshold WRITE setSizeThreshold + NOTIFY sizeThresholdChanged) + Q_PROPERTY(int maximumDepth READ maximumDepth WRITE setMaximumDepth + NOTIFY maximumDepthChanged) + Q_PROPERTY(int depth READ depth NOTIFY depthChanged) + Q_PROPERTY(QPersistentModelIndex root READ root WRITE setRoot NOTIFY rootChanged) + +public: + FlameGraph(QQuickItem *parent = 0); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + QAbstractItemModel *model() const; + void setModel(QAbstractItemModel *model); + + int sizeRole() const; + void setSizeRole(int sizeRole); + + qreal sizeThreshold() const; + void setSizeThreshold(qreal sizeThreshold); + + int depth() const; + + int maximumDepth() const + { + return m_maximumDepth; + } + + void setMaximumDepth(int maximumDepth) + { + if (maximumDepth != m_maximumDepth) { + m_maximumDepth = maximumDepth; + emit maximumDepthChanged(); + } + } + + QPersistentModelIndex root() const + { + return m_root; + } + + void setRoot(const QPersistentModelIndex &root) + { + if (root != m_root) { + m_root = root; + emit rootChanged(root); + } + } + + Q_INVOKABLE void resetRoot() + { + setRoot(QModelIndex()); + } + + static FlameGraphAttached *qmlAttachedProperties(QObject *object); + +signals: + void delegateChanged(QQmlComponent *delegate); + void modelChanged(QAbstractItemModel *model); + void sizeRoleChanged(int role); + void sizeThresholdChanged(qreal threshold); + void depthChanged(int depth); + void maximumDepthChanged(); + void rootChanged(const QPersistentModelIndex &root); + +private: + void rebuild(); + + QQmlComponent *m_delegate = nullptr; + QAbstractItemModel *m_model = nullptr; + QPersistentModelIndex m_root; + int m_sizeRole = 0; + int m_depth = 0; + qreal m_sizeThreshold = 0; + int m_maximumDepth = std::numeric_limits<int>::max(); + + int buildNode(const QModelIndex &parentIndex, QObject *parentObject, int depth, + int maximumDepth); + QObject *appendChild(QObject *parentObject, QQuickItem *parentItem, QQmlContext *context, + const QModelIndex &childIndex, qreal position, qreal size); +}; + +} // namespace FlameGraph + +QML_DECLARE_TYPEINFO(FlameGraph::FlameGraph, QML_HAS_ATTACHED_PROPERTIES) diff --git a/src/libs/tracing/flamegraphattached.h b/src/libs/tracing/flamegraphattached.h new file mode 100644 index 00000000000..05cc09627be --- /dev/null +++ b/src/libs/tracing/flamegraphattached.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include <QObject> +#include <QModelIndex> +#include <QVariant> + +namespace FlameGraph { + +class TRACING_EXPORT FlameGraphAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal relativeSize READ relativeSize WRITE setRelativeSize + NOTIFY relativeSizeChanged) + Q_PROPERTY(qreal relativePosition READ relativePosition WRITE setRelativePosition + NOTIFY relativePositionChanged) + Q_PROPERTY(bool dataValid READ isDataValid NOTIFY dataValidChanged) + Q_PROPERTY(QModelIndex modelIndex READ modelIndex WRITE setModelIndex NOTIFY modelIndexChanged) + +public: + FlameGraphAttached(QObject *parent = 0) : + QObject(parent), m_relativeSize(0), m_relativePosition(0) {} + + QModelIndex modelIndex() const + { + return m_data; + } + + Q_INVOKABLE QVariant data(int role) const + { + return m_data.isValid() ? m_data.data(role) : QVariant(); + } + + bool isDataValid() const + { + return m_data.isValid(); + } + + qreal relativeSize() const + { + return m_relativeSize; + } + + void setRelativeSize(qreal relativeSize) + { + if (relativeSize != m_relativeSize) { + m_relativeSize = relativeSize; + emit relativeSizeChanged(); + } + } + + qreal relativePosition() const + { + return m_relativePosition; + } + + void setRelativePosition(qreal relativePosition) + { + if (relativePosition != m_relativePosition) { + m_relativePosition = relativePosition; + emit relativePositionChanged(); + } + } + + void setModelIndex(const QModelIndex &data) + { + if (data != m_data) { + bool validChanged = (data.isValid() != m_data.isValid()); + m_data = data; + if (validChanged) + emit dataValidChanged(); + emit modelIndexChanged(); + } + } + +signals: + void dataValidChanged(); + void modelIndexChanged(); + void relativeSizeChanged(); + void relativePositionChanged(); + +private: + QPersistentModelIndex m_data; + qreal m_relativeSize; + qreal m_relativePosition; +}; + +} // namespace FlameGraph diff --git a/src/libs/tracing/qml/ButtonsBar.qml b/src/libs/tracing/qml/ButtonsBar.qml new file mode 100644 index 00000000000..a151eb9f91d --- /dev/null +++ b/src/libs/tracing/qml/ButtonsBar.qml @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls.Styles 1.2 + +import TimelineTheme 1.0 + +ToolBar { + id: buttons + + signal jumpToPrev() + signal jumpToNext() + signal zoomControlChanged() + signal rangeSelectChanged() + signal lockChanged() + + function updateLockButton(locked) { + lockButton.checked = !locked; + } + + function lockButtonChecked() { + return lockButton.checked; + } + + function updateRangeButton(rangeMode) { + rangeButton.checked = rangeMode; + } + + function rangeButtonChecked() { + return rangeButton.checked + } + + style: ToolBarStyle { + padding { + left: 0 + right: 0 + top: 0 + bottom: 0 + } + background: Rectangle { + anchors.fill: parent + color: Theme.color(Theme.PanelStatusBarBackgroundColor) + } + } + + RowLayout { + spacing: 0 + anchors.fill: parent + + ImageToolButton { + id: jumpToPrevButton + Layout.fillHeight: true + + imageSource: "image://icons/prev" + tooltip: qsTr("Jump to previous event.") + onClicked: buttons.jumpToPrev() + } + + ImageToolButton { + id: jumpToNextButton + Layout.fillHeight: true + + imageSource: "image://icons/next" + tooltip: qsTr("Jump to next event.") + onClicked: buttons.jumpToNext() + } + + ImageToolButton { + id: zoomControlButton + Layout.fillHeight: true + + imageSource: "image://icons/zoom" + tooltip: qsTr("Show zoom slider.") + checkable: true + checked: false + onCheckedChanged: buttons.zoomControlChanged() + } + + ImageToolButton { + id: rangeButton + Layout.fillHeight: true + + imageSource: "image://icons/" + (checked ? "rangeselected" : "rangeselection"); + tooltip: qsTr("Select range.") + checkable: true + checked: false + onCheckedChanged: buttons.rangeSelectChanged() + } + + ImageToolButton { + id: lockButton + Layout.fillHeight: true + + imageSource: "image://icons/selectionmode" + tooltip: qsTr("View event information on mouseover.") + checkable: true + checked: false + onCheckedChanged: buttons.lockChanged() + } + } +} diff --git a/src/libs/tracing/qml/CategoryLabel.qml b/src/libs/tracing/qml/CategoryLabel.qml new file mode 100644 index 00000000000..e6dd396547a --- /dev/null +++ b/src/libs/tracing/qml/CategoryLabel.qml @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 + +import TimelineTheme 1.0 + +Item { + id: labelContainer + + property QtObject model + property QtObject notesModel + property string text: model ? model.displayName : "" + property bool expanded: model && model.expanded + property var labels: (expanded && model) ? model.labels : [] + + property bool dragging + property int visualIndex + property int dragOffset + property Item draggerParent + property int contentBottom: draggerParent.contentY + draggerParent.height - dragOffset + + signal dragStarted; + signal dragStopped; + signal dropped(int sourceIndex, int targetIndex) + + signal selectById(int eventId) + signal selectNextBySelectionId(int selectionId) + signal selectPrevBySelectionId(int selectionId) + + property bool reverseSelect: false + + MouseArea { + id: dragArea + anchors.fill: txt + drag.target: dragger + cursorShape: dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + drag.minimumY: dragging ? 0 : -dragOffset // Account for parent change below + drag.maximumY: draggerParent.height - (dragging ? 0 : dragOffset) + drag.axis: Drag.YAxis + } + + DropArea { + id: dropArea + + onPositionChanged: { + var sourceIndex = drag.source.visualIndex; + if (drag.source.y === 0) { + // special case for first position: Always swap, no matter if upper border touched. + if (sourceIndex > visualIndex) + labelContainer.dropped(sourceIndex, visualIndex); + } else if (sourceIndex !== visualIndex && sourceIndex !== visualIndex + 1) { + labelContainer.dropped(sourceIndex, sourceIndex > visualIndex ? visualIndex + 1 : + visualIndex); + } + } + + anchors.fill: parent + } + + TimelineText { + id: txt + anchors.left: parent.left + anchors.leftMargin: 5 + anchors.right: notesButton.visible ? notesButton.left : notesButton.right + + text: labelContainer.text + color: Theme.color(Theme.PanelTextColorLight) + height: model ? model.defaultRowHeight : 0 + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Column { + id: column + property QtObject parentModel: model + anchors.top: txt.bottom + visible: expanded + Repeater { + model: expanded ? labels.length : 0 + Loader { + id: loader + + // Initially y == 0 for all the items. Don't enable them until they have been moved + // into place. + property int offset: (index === 0 || y > 0) ? (y + txt.height) + : draggerParent.contentHeight + active: contentBottom > offset + width: labelContainer.width + height: column.parentModel ? column.parentModel.rowHeight(index + 1) : 0 + + sourceComponent: RowLabel { + label: labels[index]; + onSelectBySelectionId: { + if (labelContainer.reverseSelect) { + labelContainer.selectPrevBySelectionId(label.id); + } else { + labelContainer.selectNextBySelectionId(label.id); + } + } + onSetRowHeight: { + column.parentModel.setExpandedRowHeight(index + 1, newHeight); + loader.height = column.parentModel.rowHeight(index + 1); + } + } + } + } + } + + ImageToolButton { + id: notesButton + anchors.verticalCenter: txt.verticalCenter + anchors.right: expandButton.left + implicitHeight: txt.height - 1 + property var eventIds: [] + property var texts: [] + property int currentNote: -1 + Connections { + target: notesModel + onChanged: { + // This will only be called if notesModel != null. + if (modelId === -1 || modelId === model.modelId) { + var notes = notesModel.byTimelineModel(model.modelId); + var newTexts = []; + var newEventIds = []; + for (var i in notes) { + newTexts.push(notesModel.text(notes[i])) + newEventIds.push(notesModel.timelineIndex(notes[i])); + } + + // Bindings are only triggered when assigning the whole array. + notesButton.eventIds = newEventIds; + notesButton.texts = newTexts; + } + } + } + + visible: eventIds.length > 0 + imageSource: "image://icons/note" + tooltip: texts.join("\n"); + onClicked: { + if (++currentNote >= eventIds.length) + currentNote = 0; + labelContainer.selectById(eventIds[currentNote]); + } + } + + ImageToolButton { + id: expandButton + anchors.verticalCenter: txt.verticalCenter + anchors.right: parent.right + implicitHeight: txt.height - 1 + enabled: expanded || (model && !model.empty) + imageSource: expanded ? "image://icons/close_split" : "image://icons/split" + tooltip: expanded ? qsTr("Collapse category") : qsTr("Expand category") + onClicked: model.expanded = !expanded + } + + Rectangle { + id: dragger + property int visualIndex: labelContainer.visualIndex + width: labelContainer.width + height: 0 + color: Theme.color(Theme.PanelStatusBarBackgroundColor) + opacity: 0.5 + anchors.left: parent.left + + // anchor to top so that it reliably snaps back after dragging + anchors.top: parent.top + + Drag.active: dragArea.drag.active + Drag.onActiveChanged: { + // We don't want height or text to be changed when reordering occurs, so we don't make + // them properties. + draggerText.text = txt.text; + if (Drag.active) { + height = labelContainer.height; + labelContainer.dragStarted(); + } else { + height = 0; + labelContainer.dragStopped(); + } + } + + states: [ + State { + when: dragger.Drag.active + ParentChange { + target: dragger + parent: draggerParent + } + PropertyChanges { + target: dragger + anchors.top: undefined + } + } + ] + + TimelineText { + id: draggerText + visible: parent.Drag.active + x: txt.x + color: Theme.color(Theme.PanelTextColorLight) + width: txt.width + height: txt.height + verticalAlignment: txt.verticalAlignment + } + } + + MouseArea { + anchors.top: dragArea.bottom + anchors.bottom: labelContainer.dragging ? labelContainer.bottom : dragArea.bottom + anchors.left: labelContainer.left + anchors.right: labelContainer.right + cursorShape: dragArea.cursorShape + } + +} diff --git a/src/libs/tracing/qml/Detail.qml b/src/libs/tracing/qml/Detail.qml new file mode 100644 index 00000000000..a1cbe86f10b --- /dev/null +++ b/src/libs/tracing/qml/Detail.qml @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 + +TimelineText { + property bool isLabel: false + property int valueWidth: 170 + property int labelWidth: implicitWidth + font.bold: isLabel + elide: Text.ElideRight + width: isLabel ? labelWidth : valueWidth +} diff --git a/src/libs/tracing/qml/FlameGraphDelegate.qml b/src/libs/tracing/qml/FlameGraphDelegate.qml new file mode 100644 index 00000000000..8a8f90c99f6 --- /dev/null +++ b/src/libs/tracing/qml/FlameGraphDelegate.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import FlameGraph 1.0 + +Item { + id: flamegraphItem + property color borderColor + property real borderWidth + property real itemHeight + property bool isSelected + property string text; + + signal mouseEntered + signal mouseExited + signal clicked + signal doubleClicked + + property bool textVisible: width > 20 || isSelected + property int level: parent.level !== undefined ? parent.level + 1 : 0 + + height: parent === null ? + 0 : (parent.height - (parent.itemHeight !== undefined ? parent.itemHeight : 0)); + width: parent === null ? 0 : parent.width * FlameGraph.relativeSize + x: parent === null ? 0 : parent.width * FlameGraph.relativePosition + + Rectangle { + border.color: borderColor + border.width: borderWidth + color: Qt.hsla((level % 12) / 72, 0.9 + Math.random() / 10, + 0.45 + Math.random() / 10, 0.9 + Math.random() / 10); + height: itemHeight; + anchors.left: flamegraphItem.left + anchors.right: flamegraphItem.right + anchors.bottom: flamegraphItem.bottom + + FlameGraphText { + id: text + visible: textVisible + anchors.fill: parent + anchors.margins: 5 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: flamegraphItem.text + elide: Text.ElideRight + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.bold: isSelected + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: flamegraphItem.mouseEntered() + onExited: flamegraphItem.mouseExited() + onClicked: flamegraphItem.clicked() + onDoubleClicked: flamegraphItem.doubleClicked() + } + } +} diff --git a/src/libs/tracing/qml/FlameGraphDetails.qml b/src/libs/tracing/qml/FlameGraphDetails.qml new file mode 100644 index 00000000000..99ff3989b6e --- /dev/null +++ b/src/libs/tracing/qml/FlameGraphDetails.qml @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Item { + id: rangeDetails + + property color titleBarColor: "#55a3b8" + property color titleBarTextColor: "white" + property color contentColor: "white" + property color contentTextColor: "black" + property color borderColor: "#a0a0a0" + property color noteTextColor: "orange" + property color buttonSelectedColor: titleBarColor + property color buttonHoveredColor: titleBarColor + + property real titleBarHeight: 20 + property real borderWidth: 1 + property real outerMargin: 10 + property real innerMargin: 5 + property real minimumInnerWidth: 150 + property real initialWidth: 300 + + property real minimumX + property real maximumX + property real minimumY + property real maximumY + + property string dialogTitle + property var model + property string note + + signal clearSelection + + visible: dialogTitle.length > 0 || model.length > 0 + + width: dragHandle.x + dragHandle.width + height: contentArea.height + titleBar.height + + onMinimumXChanged: x = Math.max(x, minimumX) + onMaximumXChanged: x = Math.min(x, Math.max(minimumX, maximumX - width)) + onMinimumYChanged: y = Math.max(y, minimumY) + onMaximumYChanged: y = Math.min(y, Math.max(minimumY, maximumY - height)) + + MouseArea { + anchors.fill: parent + drag.target: parent + drag.minimumX: parent.minimumX + drag.maximumX: parent.maximumX - rangeDetails.width + drag.minimumY: parent.minimumY + drag.maximumY: parent.maximumY - rangeDetails.height + } + + Rectangle { + id: titleBar + width: parent.width + height: titleBarHeight + color: titleBarColor + border.width: borderWidth + border.color: borderColor + + FlameGraphText { + id: typeTitle + text: rangeDetails.dialogTitle + font.bold: true + verticalAlignment: Text.AlignVCenter + anchors.left: parent.left + anchors.right: closeIcon.left + anchors.leftMargin: outerMargin + anchors.rightMargin: innerMargin + anchors.top: parent.top + anchors.bottom: parent.bottom + color: titleBarTextColor + elide: Text.ElideRight + } + + ToolButton { + id: closeIcon + + implicitWidth: 30 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + onClicked: rangeDetails.clearSelection() + + Image { + id: image + source: "image://icons/close_window" + (parent.enabled ? "" : "/disabled") + width: 16 + height: 16 + anchors.centerIn: parent + } + + style: ButtonStyle { + background: Rectangle { + color: (control.checked || control.pressed) + ? buttonSelectedColor + : control.hovered + ? buttonHoveredColor + : "#00000000" + } + } + } + } + + Rectangle { + id: contentArea + color: contentColor + + anchors.top: titleBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: dragHandle.bottom + + border.width: borderWidth + border.color: borderColor + } + + Grid { + id: col + + anchors.left: parent.left + anchors.top: titleBar.bottom + anchors.topMargin: innerMargin + anchors.leftMargin: outerMargin + anchors.rightMargin: outerMargin + + spacing: innerMargin + columns: 2 + property int minimumWidth: { + // max(width of longest label * 2, minimumInnerWidth) + var result = minimumInnerWidth; + for (var i = 0; i < children.length; ++i) { + if (children[i].isLabel) + result = Math.max(children[i].implicitWidth * 2 + innerMargin, result); + } + + return result + 2 * outerMargin; + } + + property int labelWidth: (minimumWidth - innerMargin) / 2 - outerMargin + property int valueWidth: dragHandle.x - labelWidth - innerMargin - outerMargin + + onMinimumWidthChanged: { + if (dragHandle.x < minimumWidth - outerMargin) + dragHandle.x = minimumWidth - outerMargin; + } + + Repeater { + model: rangeDetails.model + FlameGraphText { + property bool isLabel: index % 2 === 0 + font.bold: isLabel + elide: Text.ElideRight + width: isLabel ? col.labelWidth : col.valueWidth + text: isLabel ? (modelData + ":") : modelData + color: contentTextColor + } + } + } + + + TextEdit { + id: noteEdit + + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: outerMargin + anchors.rightMargin: outerMargin + anchors.topMargin: visible ? innerMargin : 0 + anchors.top: col.bottom + height: visible ? implicitHeight : 0 + + readOnly: true + visible: text.length > 0 + text: note + wrapMode: Text.Wrap + color: noteTextColor + font.italic: true + font.pixelSize: typeTitle.font.pixelSize + font.family: typeTitle.font.family + renderType: typeTitle.renderType + selectByMouse: true + } + + Item { + id: dragHandle + width: outerMargin + height: outerMargin + x: initialWidth + anchors.top: noteEdit.bottom + clip: true + MouseArea { + anchors.fill: parent + drag.target: parent + drag.minimumX: col.minimumWidth - outerMargin + drag.axis: Drag.XAxis + cursorShape: Qt.SizeHorCursor + } + Rectangle { + color: titleBarColor + rotation: 45 + width: parent.width * Math.SQRT2 + height: parent.height * Math.SQRT2 + x: parent.width - width / 2 + y: parent.height - height / 2 + } + } +} diff --git a/src/libs/tracing/qml/FlameGraphText.qml b/src/libs/tracing/qml/FlameGraphText.qml new file mode 100644 index 00000000000..a989b393556 --- /dev/null +++ b/src/libs/tracing/qml/FlameGraphText.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 + +Text { + font.pixelSize: 12 + font.family: "sans-serif" + textFormat: Text.PlainText + renderType: Text.NativeRendering +} + diff --git a/src/libs/tracing/qml/ImageToolButton.qml b/src/libs/tracing/qml/ImageToolButton.qml new file mode 100644 index 00000000000..353e38f122b --- /dev/null +++ b/src/libs/tracing/qml/ImageToolButton.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.2 + +import TimelineTheme 1.0 + +ToolButton { + implicitWidth: 30 + + property string imageSource + + Image { + source: parent.enabled ? parent.imageSource : parent.imageSource + "/disabled" + width: 16 + height: 16 + anchors.centerIn: parent + smooth: false + } + + style: ButtonStyle { + background: Rectangle { + color: (control.checked || control.pressed) + ? Theme.color(Theme.FancyToolButtonSelectedColor) + : control.hovered + ? Theme.color(Theme.FancyToolButtonHoverColor) + : "#00000000" + } + } +} diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml new file mode 100644 index 00000000000..2c0aa1258e5 --- /dev/null +++ b/src/libs/tracing/qml/MainView.qml @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + +import TimelineTheme 1.0 + +Rectangle { + id: root + + // ***** properties + + property bool lockItemSelection : false + + 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 int typeId: content.typeId + onWidthChanged: zoomSliderToolBar.updateZoomLevel(); + + color: Theme.color(Theme.Timeline_BackgroundColor1) + + // ***** 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(); + } + } + + onSelectionRangeModeChanged: { + selectionRange.reset(); + buttonsBar.updateRangeButton(selectionRangeMode); + } + + // ***** functions + function clear() { + content.clearChildren(); + rangeDetails.hide(); + selectionRangeMode = false; + zoomSlider.externalUpdate = true; + zoomSlider.value = zoomSlider.minimumValue; + } + + // This is called from outside to synchronize the timeline to other views + function selectByTypeId(typeId) + { + if (lockItemSelection || typeId === -1 || content.typeId === typeId) + 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 + + // set this here, so that propagateSelection doesn't trigger updateCursorPosition() + content.typeId = typeId; + content.select(modelIndex, itemIndex); + content.selectionLocked = true; + } + } + + // This is called from outside to synchronize the timeline to other views + function selectByIndices(modelIndex, eventIndex) + { + if (modelIndex >= 0 && modelIndex < timelineModelAggregator.models.length && + selectedItem !== -1) { + // set this here, so that propagateSelection doesn't trigger updateCursorPosition() + content.typeId = timelineModelAggregator.models[modelIndex].typeId(eventIndex); + } + content.select(modelIndex, eventIndex); + } + + 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: Theme.color(Theme.PanelStatusBarBackgroundColor) + 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: zoomControl.rangeDuration + contentX: content.contentX + } + + ButtonsBar { + id: buttonsBar + enabled: zoomControl.traceDuration > 0 + anchors.top: parent.top + anchors.left: parent.left + width: 150 + height: 24 + onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.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); + if (eventLocation.file !== undefined) { + root.fileName = eventLocation.file; + root.lineNumber = eventLocation.line; + root.columnNumber = eventLocation.column; + } + var newTypeId = model.typeId(selectedItem); + if (newTypeId !== typeId) { + typeId = newTypeId; + timelineModelAggregator.updateCursorPosition(); + } + } else { + selectedModel = -1; + rangeDetails.hide(); + } + lockItemSelection = false; + } + } + + 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 + interactive: false + x: content.x + content.flickableItem.x + y: content.y + content.flickableItem.y + height: (selectionRangeMode && + selectionRange.creationState !== selectionRange.creationInactive) ? + content.flickableItem.height : 0 + width: content.flickableItem.width + contentX: content.contentX + contentWidth: content.contentWidth + + SelectionRange { + id: selectionRange + zoomer: zoomControl + + onRangeDoubleClicked: { + var diff = zoomer.minimumRangeLength - zoomer.selectionDuration; + if (diff > 0) + zoomControl.setRange(zoomer.selectionStart - diff / 2, + zoomer.selectionEnd + diff / 2); + else + zoomControl.setRange(zoomer.selectionStart, zoomer.selectionEnd); + root.selectionRangeMode = false; + } + + } + } + + TimelineRulers { + contentX: buttonsBar.width - content.x - content.flickableItem.x + content.contentX + anchors.left: buttonsBar.right + anchors.right: parent.right + anchors.top: parent.top + height: content.flickableItem.height + buttonsBar.height + windowStart: zoomControl.windowStart + viewTimePerPixel: selectionRange.viewTimePerPixel + scaleHeight: buttonsBar.height + } + + SelectionRangeDetails { + z: 3 + x: 200 + y: 125 + + clip: true + id: selectionRangeDetails + startTime: zoomControl.selectionStart + duration: zoomControl.selectionDuration + endTime: zoomControl.selectionEnd + referenceDuration: zoomControl.rangeDuration + showDuration: selectionRange.rangeWidth > 1 + hasContents: selectionRangeMode && + selectionRange.creationState !== selectionRange.creationInactive + + onRecenter: { + if ((zoomControl.selectionStart < zoomControl.rangeStart) ^ + (zoomControl.selectionEnd > zoomControl.rangeEnd)) { + var center = (zoomControl.selectionStart + zoomControl.selectionEnd) / 2; + var halfDuration = Math.max(zoomControl.selectionDuration, + zoomControl.rangeDuration) / 2; + zoomControl.setRange(center - halfDuration, center + halfDuration); + } + } + + onClose: selectionRangeMode = false; + } + + RangeDetails { + id: rangeDetails + + z: 3 + x: 200 + y: 25 + + clip: true + locked: content.selectionLocked + models: timelineModelAggregator.models + notes: timelineModelAggregator.notes + hasContents: false + onRecenterOnItem: { + content.select(selectedModel, selectedItem) + } + onToggleSelectionLocked: { + content.selectionLocked = !content.selectionLocked; + } + onClearSelection: { + content.propagateSelection(-1, -1); + } + } + + Rectangle { + id: zoomSliderToolBar + objectName: "zoomSliderToolBar" + color: Theme.color(Theme.Timeline_PanelBackgroundColor) + enabled: buttonsBar.enabled + visible: false + width: buttonsBar.width + height: buttonsBar.height + anchors.left: parent.left + anchors.top: buttonsBar.bottom + + function updateZoomLevel() { + var newValue = Math.round(Math.pow(zoomControl.rangeDuration / + Math.max(1, zoomControl.windowDuration), + 1 / zoomSlider.exponent) * zoomSlider.maximumValue); + if (newValue !== zoomSlider.value) { + zoomSlider.externalUpdate = true; + zoomSlider.value = newValue; + } + } + + 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 + property double fixedPoint: 0 + onPressedChanged: fixedPoint = (zoomControl.rangeStart + zoomControl.rangeEnd) / 2; + + 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 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 + } + + Rectangle { + // Opal glass pane for visualizing the "disabled" state. + anchors.fill: parent + z: 10 + color: parent.color + opacity: 0.5 + visible: !parent.enabled + } +} diff --git a/src/libs/tracing/qml/Overview.qml b/src/libs/tracing/qml/Overview.qml new file mode 100644 index 00000000000..feda483bbb4 --- /dev/null +++ b/src/libs/tracing/qml/Overview.qml @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineOverviewRenderer 1.0 +import TimelineTheme 1.0 + +Rectangle { + id: overview + objectName: "Overview" + color: Theme.color(Theme.Timeline_BackgroundColor2) + + property QtObject modelProxy + property QtObject zoomer + property bool recursionGuard: false + onWidthChanged: updateRangeMover() + + function updateZoomer() { + if (recursionGuard) + return; + recursionGuard = true; + var newStartTime = rangeMover.rangeLeft * zoomer.traceDuration / width + + zoomer.traceStart; + var newEndTime = rangeMover.rangeRight * zoomer.traceDuration / width + + zoomer.traceStart; + if (isFinite(newStartTime) && isFinite(newEndTime) && + newEndTime - newStartTime > zoomer.minimumRangeLength) + zoomer.setRange(newStartTime, newEndTime); + recursionGuard = false; + } + + function updateRangeMover() { + if (recursionGuard) + return; + recursionGuard = true; + var newRangeX = (zoomer.rangeStart - zoomer.traceStart) * width / + zoomer.traceDuration; + var newWidth = zoomer.rangeDuration * width / zoomer.traceDuration; + var widthChanged = Math.abs(newWidth - rangeMover.rangeWidth) > 1; + var leftChanged = Math.abs(newRangeX - rangeMover.rangeLeft) > 1; + if (leftChanged) + rangeMover.rangeLeft = newRangeX; + + if (leftChanged || widthChanged) + rangeMover.rangeRight = newRangeX + newWidth; + recursionGuard = false; + } + + Connections { + target: zoomer + onRangeChanged: updateRangeMover() + } + + TimeDisplay { + id: timebar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + textMargin: 2 + height: 10 + fontSize: 6 + labelsHeight: 10 + windowStart: zoomer.traceStart + alignedWindowStart: zoomer.traceStart + rangeDuration: zoomer.traceDuration + contentX: 0 + offsetX: 0 + } + + Column { + anchors.top: timebar.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + id: column + + Repeater { + model: modelProxy.models + TimelineOverviewRenderer { + model: modelData + zoomer: overview.zoomer + notes: modelProxy.notes + width: column.width + height: column.height / modelProxy.models.length + } + } + } + + Repeater { + id: noteSigns + property var modelsById: modelProxy.models.reduce(function(prev, model) { + prev[model.modelId] = model; + return prev; + }, {}); + + property int vertSpace: column.height / 7 + property color noteColor: Theme.color(Theme.Timeline_HighlightColor) + readonly property double spacing: parent.width / zoomer.traceDuration + + model: modelProxy.notes ? modelProxy.notes.count : 0 + Item { + property int timelineIndex: modelProxy.notes.timelineIndex(index) + property int timelineModel: modelProxy.notes.timelineModel(index) + property double startTime: noteSigns.modelsById[timelineModel].startTime(timelineIndex) + property double endTime: noteSigns.modelsById[timelineModel].endTime(timelineIndex) + x: ((startTime + endTime) / 2 - zoomer.traceStart) * noteSigns.spacing + y: timebar.height + noteSigns.vertSpace + height: noteSigns.vertSpace * 5 + width: 2 + Rectangle { + color: noteSigns.noteColor + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: noteSigns.vertSpace * 3 + } + + Rectangle { + color: noteSigns.noteColor + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: noteSigns.vertSpace + } + } + } + + // ***** child items + MouseArea { + anchors.fill: parent + function jumpTo(posX) { + var newX = posX - rangeMover.rangeWidth / 2; + if (newX < 0) + newX = 0; + if (newX + rangeMover.rangeWidth > overview.width) + newX = overview.width - rangeMover.rangeWidth; + + if (newX < rangeMover.rangeLeft) { + // Changing left border will change width, so precompute right border here. + var right = newX + rangeMover.rangeWidth; + rangeMover.rangeLeft = newX; + rangeMover.rangeRight = right; + } else if (newX > rangeMover.rangeLeft) { + rangeMover.rangeRight = newX + rangeMover.rangeWidth; + rangeMover.rangeLeft = newX; + } + } + + onPressed: { + jumpTo(mouse.x); + } + onPositionChanged: { + jumpTo(mouse.x); + } + } + + RangeMover { + id: rangeMover + visible: modelProxy.height > 0 + onRangeLeftChanged: overview.updateZoomer() + onRangeRightChanged: overview.updateZoomer() + } +} diff --git a/src/libs/tracing/qml/RangeDetails.qml b/src/libs/tracing/qml/RangeDetails.qml new file mode 100644 index 00000000000..c6b057cc6c5 --- /dev/null +++ b/src/libs/tracing/qml/RangeDetails.qml @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineTheme 1.0 + +Item { + id: rangeDetails + + property string duration + property string label + property string dialogTitle + property string file + property int line + property int column + property bool isBindingLoop + property bool hasContents + + property int selectedModel: -1 + property int selectedItem: -1 + + property bool locked + + property var models + property var notes + + signal recenterOnItem + signal toggleSelectionLocked + signal clearSelection + + width: col.width + 20 + height: hasContents ? contentArea.height + titleBar.height : 0 + + function hide() { + noteEdit.focus = false; + hasContents = false; + selectedModel = selectedItem = -1; + noteEdit.text = ""; + duration = ""; + label = ""; + file = ""; + line = -1; + column = 0; + isBindingLoop = false; + } + + Connections { + target: rangeDetails.parent + // keep inside view + onWidthChanged: fitInView(); + onHeightChanged: fitInView(); + } + + QtObject { + id: eventInfo + property bool ready: false + property var content: [] + } + + function showInfo(model, item) { + eventInfo.ready = false; + // make sure we don't accidentally save the old text for the new event + noteEdit.focus = false; + + selectedModel = model; + selectedItem = item; + var timelineModel = models[selectedModel]; + var eventData = timelineModel.details(selectedItem) + eventInfo.content = []; + for (var k in eventData) { + if (k === "displayName") { + dialogTitle = eventData[k]; + } else { + eventInfo.content.push(k); + eventInfo.content.push(eventData[k]); + } + } + eventInfo.ready = true; + hasContents = eventInfo.content.length > 0; + + var location = timelineModel.location(selectedItem) + if (location.hasOwnProperty("file")) { // not empty + file = location.file; + line = location.line; + column = location.column; + } else { + // reset to default values + file = ""; + line = 0; + column = -1; + } + + noteEdit.focus = false; + var noteId = notes ? notes.get(timelineModel.modelId, selectedItem) : -1; + noteEdit.text = (noteId !== -1) ? notes.text(noteId) : ""; + } + + function fitInView() { + // don't reposition if it does not fit + if (parent.width < width || parent.height < height) + return; + + if (x + width > parent.width) + x = parent.width - width; + if (x < 0) + x = 0; + if (y + height > parent.height) + y = parent.height - height; + if (y < 0) + y = 0; + } + + Rectangle { + id: titleBar + width: parent.width + height: 20 + color: Theme.color(Theme.Timeline_PanelHeaderColor) + } + Item { + width: parent.width+1 + height: 11 + y: 10 + clip: true + Rectangle { + width: parent.width-1 + height: 15 + y: -5 + color: Theme.color(Theme.Timeline_PanelHeaderColor) + } + } + + //title + TimelineText { + id: typeTitle + text: " "+rangeDetails.dialogTitle + font.bold: true + height: 20 + verticalAlignment: Text.AlignVCenter + anchors.left: parent.left + anchors.right: editIcon.left + elide: Text.ElideRight + color: Theme.color(Theme.PanelTextColorLight) + } + + // Details area + Rectangle { + id: contentArea + color: Theme.color(Theme.Timeline_PanelBackgroundColor) + width: parent.width + height: 10 + col.height + (noteEdit.visible ? (noteEdit.height + 5) : 0) + y: 20 + + //details + Grid { + property int outerMargin: 10 + property int minimumWidth: 150 + property int labelWidth: (minimumWidth - spacing) / 2 - outerMargin + property int valueWidth: dragHandle.x - labelWidth - spacing - outerMargin + + id: col + x: outerMargin + y: 5 + spacing: 5 + columns: 2 + + onChildrenChanged: { + // max(width of longest label * 2, 150) + var result = 150; + for (var i = 0; i < children.length; ++i) { + if (children[i].isLabel) + result = Math.max(children[i].implicitWidth * 2 + spacing, result); + } + + minimumWidth = result + 2 * outerMargin; + if (dragHandle.x < minimumWidth - outerMargin) + dragHandle.x = minimumWidth - outerMargin; + } + + Repeater { + model: eventInfo.ready ? eventInfo.content : 0 + Detail { + labelWidth: col.labelWidth + valueWidth: col.valueWidth + isLabel: index % 2 === 0 + text: isLabel ? (modelData + ":") : modelData + } + } + } + + + TextEdit { + id: noteEdit + x: 10 + anchors.topMargin: 5 + anchors.bottomMargin: 5 + anchors.top: col.bottom + + visible: notes && (text.length > 0 || focus) + width: col.width + wrapMode: Text.Wrap + color: Theme.color(Theme.Timeline_HighlightColor) + font.italic: true + font.pixelSize: typeTitle.font.pixelSize + font.family: typeTitle.font.family + renderType: typeTitle.renderType + selectByMouse: true + onTextChanged: saveTimer.restart() + onFocusChanged: { + if (!focus && selectedModel != -1 && selectedItem != -1) { + saveTimer.stop(); + if (notes) + notes.setText(models[selectedModel].modelId, selectedItem, text); + } + } + + Timer { + id: saveTimer + onTriggered: { + if (notes && selectedModel != -1 && selectedItem != -1) + notes.setText(models[selectedModel].modelId, selectedItem, noteEdit.text); + } + interval: 1000 + } + } + } + + MouseArea { + anchors.fill: parent + drag.target: parent + drag.minimumX: 0 + drag.maximumX: rangeDetails.parent.width - rangeDetails.width + drag.minimumY: 0 + drag.maximumY: rangeDetails.parent.height - rangeDetails.height + onClicked: rangeDetails.recenterOnItem() + } + + ImageToolButton { + id: editIcon + imageSource: "image://icons/edit" + anchors.top: closeIcon.top + anchors.right: lockIcon.left + implicitHeight: typeTitle.height + visible: notes + onClicked: noteEdit.focus = true + } + + ImageToolButton { + id: lockIcon + imageSource: "image://icons/lock_" + (locked ? "closed" : "open") + anchors.top: closeIcon.top + anchors.right: closeIcon.left + implicitHeight: typeTitle.height + onClicked: rangeDetails.toggleSelectionLocked() + } + + ImageToolButton { + id: closeIcon + anchors.right: parent.right + anchors.top: parent.top + implicitHeight: typeTitle.height + imageSource: "image://icons/close_window" + onClicked: rangeDetails.clearSelection() + } + + Item { + id: dragHandle + width: 10 + height: 10 + x: 300 + anchors.bottom: parent.bottom + clip: true + MouseArea { + anchors.fill: parent + drag.target: parent + drag.minimumX: col.minimumWidth - col.outerMargin + drag.axis: Drag.XAxis + cursorShape: Qt.SizeHorCursor + } + Rectangle { + color: Theme.color(Theme.Timeline_PanelHeaderColor) + rotation: 45 + width: parent.width * Math.SQRT2 + height: parent.height * Math.SQRT2 + x: parent.width - width / 2 + y: parent.height - height / 2 + } + } +} diff --git a/src/libs/tracing/qml/RangeMover.qml b/src/libs/tracing/qml/RangeMover.qml new file mode 100644 index 00000000000..f11505e3571 --- /dev/null +++ b/src/libs/tracing/qml/RangeMover.qml @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineTheme 1.0 + +Item { + id: rangeMover + anchors.fill: parent + signal rangeDoubleClicked() + + property color handleColor: Theme.color(Theme.Timeline_HandleColor) + property color rangeColor: Theme.color(Theme.Timeline_RangeColor) + property color dragColor: Theme.color(Theme.Timeline_RangeColor) + property color borderColor: Theme.color(Theme.Timeline_RangeColor) + property color singleLineColor: Theme.color(Theme.Timeline_RangeColor) + + property alias rangeLeft: leftRange.x + property alias rangeRight: rightRange.x + readonly property alias rangeWidth: selectedRange.width + + Rectangle { + id: selectedRange + + x: leftRange.x + width: rightRange.x - leftRange.x + height: parent.height + + color: width > 1 ? (dragArea.pressed ? dragColor : rangeColor) : singleLineColor + } + + Item { + id: leftRange + + onXChanged: { + if (dragArea.drag.active) + rightRange.x = x + dragArea.origWidth; + } + + x: 0 + height: parent.height + + Rectangle { + id: leftBorderHandle + height: parent.height + anchors.right: parent.left + width: 7 + color: handleColor + visible: false + Image { + source: "image://icons/range_handle" + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 1 + y: (parent.height - height) / 2 + } + } + + states: State { + name: "highlighted" + PropertyChanges { + target: leftBorderHandle + visible: true + } + } + + MouseArea { + anchors.fill: leftBorderHandle + + drag.target: leftRange + drag.axis: "XAxis" + drag.minimumX: 0 + drag.maximumX: rangeMover.width + drag.onActiveChanged: drag.maximumX = rightRange.x + + hoverEnabled: true + + onEntered: { + parent.state = "highlighted"; + } + onExited: { + if (!pressed) parent.state = ""; + } + onReleased: { + if (!containsMouse) parent.state = ""; + } + } + } + + Item { + id: rightRange + + x: 1 + height: parent.height + + Rectangle { + id: rightBorderHandle + height: parent.height + anchors.left: parent.right + width: 7 + color: handleColor + visible: false + Image { + source: "image://icons/range_handle" + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 1 + y: (parent.height - height) / 2 + } + } + + states: State { + name: "highlighted" + PropertyChanges { + target: rightBorderHandle + visible: true + } + } + + MouseArea { + anchors.fill: rightBorderHandle + + drag.target: rightRange + drag.axis: "XAxis" + drag.minimumX: 0 + drag.maximumX: rangeMover.width + drag.onActiveChanged: drag.minimumX = leftRange.x + + hoverEnabled: true + + onEntered: { + parent.state = "highlighted"; + } + onExited: { + if (!pressed) parent.state = ""; + } + onReleased: { + if (!containsMouse) parent.state = ""; + } + } + } + + MouseArea { + id: dragArea + property double origWidth: 0 + + anchors.fill: selectedRange + drag.target: leftRange + drag.axis: "XAxis" + drag.minimumX: 0 + drag.maximumX: rangeMover.width - origWidth + drag.onActiveChanged: origWidth = selectedRange.width + onDoubleClicked: parent.rangeDoubleClicked() + } +} diff --git a/src/libs/tracing/qml/RowLabel.qml b/src/libs/tracing/qml/RowLabel.qml new file mode 100644 index 00000000000..be394b5e7e4 --- /dev/null +++ b/src/libs/tracing/qml/RowLabel.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import TimelineTheme 1.0 + +Button { + id: button + property var label + + readonly property int dragHeight: 5 + + signal selectBySelectionId() + signal setRowHeight(int newHeight) + + property string labelText: label.description ? label.description : qsTr("[unknown]") + action: Action { + onTriggered: button.selectBySelectionId(); + tooltip: button.labelText + (label.displayName ? (" (" + label.displayName + ")") : "") + } + + style: ButtonStyle { + background: Rectangle { + border.width: 1 + border.color: Theme.color(Theme.Timeline_DividerColor) + color: Theme.color(Theme.PanelStatusBarBackgroundColor) + } + label: TimelineText { + text: button.labelText + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + color: Theme.color(Theme.PanelTextColorLight) + } + } + MouseArea { + hoverEnabled: true + property bool resizing: false + onPressed: resizing = true + onReleased: resizing = false + + height: dragHeight + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + cursorShape: Qt.SizeVerCursor + + onMouseYChanged: { + if (resizing) + button.setRowHeight(y + mouseY) + } + } +} + diff --git a/src/libs/tracing/qml/SelectionRange.qml b/src/libs/tracing/qml/SelectionRange.qml new file mode 100644 index 00000000000..9f111b75c4c --- /dev/null +++ b/src/libs/tracing/qml/SelectionRange.qml @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 + +RangeMover { + id: selectionRange + property QtObject zoomer; + + readonly property int creationInactive: 0 + readonly property int creationFirstLimit: 1 + readonly property int creationSecondLimit: 2 + readonly property int creationFinished: 3 + + property bool ready: visible && creationState === creationFinished + + property double viewTimePerPixel: 1 + property double creationReference : 0 + property int creationState : creationInactive + + function reset() { + rangeRight = rangeLeft + 1; + creationState = creationInactive; + creationReference = 0; + } + + function updateZoomer() { + zoomer.setSelection(rangeLeft * viewTimePerPixel + zoomer.windowStart, + rangeRight * viewTimePerPixel + zoomer.windowStart) + } + + function updateRange() { + var left = (zoomer.selectionStart - zoomer.windowStart) / viewTimePerPixel; + var right = (zoomer.selectionEnd - zoomer.windowStart) / viewTimePerPixel; + if (left < rangeLeft) { + rangeLeft = left; + rangeRight = right; + } else { + rangeRight = right; + rangeLeft = left; + } + } + + onRangeWidthChanged: updateZoomer() + onRangeLeftChanged: updateZoomer() + + Connections { + target: zoomer + onWindowChanged: updateRange() + } + + function setPos(pos) { + if (pos < 0) + pos = 0; + else if (pos > width) + pos = width; + + switch (creationState) { + case creationFirstLimit: + creationReference = pos; + rangeLeft = pos; + rangeRight = pos + 1; + break; + case creationSecondLimit: + if (pos > creationReference) { + rangeLeft = creationReference; + rangeRight = pos; + } else if (pos < creationReference) { + rangeLeft = pos; + rangeRight = creationReference; + } + break; + } + } +} diff --git a/src/libs/tracing/qml/SelectionRangeDetails.qml b/src/libs/tracing/qml/SelectionRangeDetails.qml new file mode 100644 index 00000000000..458e5cf7ad4 --- /dev/null +++ b/src/libs/tracing/qml/SelectionRangeDetails.qml @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineTheme 1.0 +import TimelineTimeFormatter 1.0 + +Item { + id: selectionRangeDetails + + signal recenter + signal close + + property double startTime + property double endTime + property double duration + property double referenceDuration + property bool showDuration + property bool hasContents + + width: Math.max(150, col.width + 25) + height: hasContents ? col.height + 30 : 0 + + // keep inside view + Connections { + target: selectionRangeDetails.parent + onWidthChanged: fitInView(); + onHeightChanged: fitInView(); + } + + function fitInView() { + // don't reposition if it does not fit + if (parent.width < width || parent.height < height) + return; + + if (x + width > parent.width) + x = parent.width - width; + if (x < 0) + x = 0; + if (y + height > parent.height) + y = parent.height - height; + if (y < 0) + y = 0; + } + + // title bar + Rectangle { + width: parent.width + height: 20 + color: Theme.color(Theme.Timeline_PanelHeaderColor) + } + + //title + TimelineText { + id: typeTitle + text: " "+qsTr("Selection") + font.bold: true + height: 20 + verticalAlignment: Text.AlignVCenter + width: parent.width + color: Theme.color(Theme.PanelTextColorLight) + } + + // Details area + Rectangle { + color: Theme.color(Theme.Timeline_PanelBackgroundColor) + width: parent.width + height: col.height + 10 + y: 20 + Grid { + id: col + x: 10 + y: 5 + spacing: 5 + columns: 2 + + Repeater { + id: details + property var contents: [ + qsTr("Start") + ":", + TimeFormatter.format(startTime, referenceDuration), + (qsTr("End") + ":"), + TimeFormatter.format(endTime, referenceDuration), + (qsTr("Duration") + ":"), + TimeFormatter.format(duration, referenceDuration) + ] + + model: showDuration ? 6 : 2 + Detail { + isLabel: index % 2 === 0 + text: details.contents[index] + } + } + } + } + + MouseArea { + anchors.fill: parent + drag.target: parent + drag.minimumX: 0 + drag.maximumX: selectionRangeDetails.parent.width - width + drag.minimumY: 0 + drag.maximumY: selectionRangeDetails.parent.height - height + onClicked: selectionRangeDetails.recenter() + } + + ImageToolButton { + id: closeIcon + imageSource: "image://icons/close_window" + anchors.right: selectionRangeDetails.right + anchors.top: selectionRangeDetails.top + implicitHeight: typeTitle.height + onClicked: selectionRangeDetails.close() + } +} diff --git a/src/libs/tracing/qml/TimeDisplay.qml b/src/libs/tracing/qml/TimeDisplay.qml new file mode 100644 index 00000000000..4351c8260e5 --- /dev/null +++ b/src/libs/tracing/qml/TimeDisplay.qml @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineTheme 1.0 +import TimelineTimeFormatter 1.0 + +Item { + id: timeDisplay + + property double windowStart + property double rangeDuration + + property int textMargin: 5 + property int labelsHeight: 24 + property int fontSize: 8 + property int initialBlockLength: 120 + property double spacing: width / rangeDuration + + property double timePerBlock: Math.pow(2, Math.floor(Math.log(initialBlockLength / spacing) / + Math.LN2)) + + property double alignedWindowStart: Math.round(windowStart - (windowStart % timePerBlock)) + property double pixelsPerBlock: timeDisplay.timePerBlock * timeDisplay.spacing + property double pixelsPerSection: pixelsPerBlock / 5 + + property int contentX + property int offsetX: contentX + Math.round((windowStart % timePerBlock) * spacing) + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: timeDisplay.labelsHeight + color: Theme.color(Theme.PanelStatusBarBackgroundColor) + } + + Item { + x: -(timeDisplay.offsetX % timeDisplay.pixelsPerBlock) + y: 0 + id: row + + property int firstBlock: timeDisplay.offsetX / timeDisplay.pixelsPerBlock + property int offset: repeater.model > 0 ? repeater.model - (firstBlock % repeater.model) : 0; + + Repeater { + id: repeater + model: Math.floor(timeDisplay.width / timeDisplay.initialBlockLength * 2 + 2) + + Item { + id: column + + // Changing the text in text nodes is expensive. We minimize the number of changes + // by rotating the nodes during scrolling. + property int stableIndex: (index + row.offset) % repeater.model + + height: timeDisplay.height + y: 0 + x: width * stableIndex + width: timeDisplay.pixelsPerBlock + + property double blockStartTime: (row.firstBlock + stableIndex) + * timeDisplay.timePerBlock + + timeDisplay.alignedWindowStart + + TimelineText { + id: timeLabel + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: timeDisplay.labelsHeight + + font.pixelSize: timeDisplay.fontSize + anchors.rightMargin: timeDisplay.textMargin + verticalAlignment: Text.AlignVCenter + text: TimeFormatter.format(column.blockStartTime, timeDisplay.rangeDuration) + visible: width > 0 + color: Theme.color(Theme.PanelTextColorLight) + elide: Text.ElideLeft + } + + Row { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: timeLabel.bottom + anchors.bottom: parent.bottom + + Repeater { + model: 4 + Item { + anchors.top: parent.top + anchors.bottom: parent.bottom + width: timeDisplay.pixelsPerSection + + Rectangle { + visible: column.stableIndex !== 0 || (-row.x < parent.x + x) + color: Theme.color(Theme.Timeline_DividerColor) + width: 1 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + } + } + } + } + + Rectangle { + color: Theme.color(Theme.Timeline_DividerColor) + width: 1 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + } + } + } + } +} diff --git a/src/libs/tracing/qml/TimeMarks.qml b/src/libs/tracing/qml/TimeMarks.qml new file mode 100644 index 00000000000..2fc1ef41c8a --- /dev/null +++ b/src/libs/tracing/qml/TimeMarks.qml @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import TimelineTheme 1.0 + +Item { + id: timeMarks + visible: model && (mockup || (!model.hidden && !model.empty)) + property QtObject model + property bool startOdd + property bool mockup + + readonly property int scaleMinHeight: 60 + readonly property int scaleStepping: 30 + readonly property string units: " kMGT" + + + property int rowCount: model ? model.rowCount : 0 + + function roundTo3Digits(number) { + var factor; + + if (number < 10) + factor = 100; + else if (number < 100) + factor = 10; + else + factor = 1; + + return Math.round(number * factor) / factor; + } + + function prettyPrintScale(amount) { + var unitOffset = 0; + var unitAmount = 1; + for (unitOffset = 0; amount > unitAmount * 1024; ++unitOffset, unitAmount *= 1024) {} + var result = amount / unitAmount; + return roundTo3Digits(result) + units[unitOffset]; + } + + Connections { + target: model + onExpandedRowHeightChanged: { + if (model && model.expanded && row >= 0) + rowRepeater.itemAt(row).height = height; + } + } + + + Column { + id: rows + anchors.left: parent.left + anchors.right: parent.right + Repeater { + id: rowRepeater + model: timeMarks.rowCount + Rectangle { + id: row + color: ((index + (startOdd ? 1 : 0)) % 2) + ? Theme.color(Theme.Timeline_BackgroundColor1) + : Theme.color(Theme.Timeline_BackgroundColor2) + anchors.left: rows.left + anchors.right: rows.right + height: timeMarks.model ? timeMarks.model.rowHeight(index) : 0 + + property double minVal: timeMarks.model ? timeMarks.model.rowMinValue(index) : 0 + property double maxVal: timeMarks.model ? timeMarks.model.rowMaxValue(index) : 0 + property double valDiff: maxVal - minVal + property bool scaleVisible: timeMarks.model && timeMarks.model.expanded && + height > scaleMinHeight && valDiff > 0 + + property double stepVal: { + var ret = 1; + var ugly = Math.ceil(valDiff / Math.floor(height / scaleStepping)); + while (isFinite(ugly) && ugly > 1) { + ugly /= 2; + ret *= 2; + } + return ret; + } + + Loader { + anchors.fill: parent + active: parent.scaleVisible + sourceComponent: Item { + id: scaleParent + anchors.fill: parent + + TimelineText { + id: scaleTopLabel + font.pixelSize: 8 + anchors.top: parent.top + anchors.leftMargin: 2 + anchors.topMargin: 2 + anchors.left: parent.left + text: prettyPrintScale(row.maxVal) + } + + Repeater { + model: row.valDiff / row.stepVal + + Item { + anchors.left: scaleParent.left + anchors.right: scaleParent.right + height: row.stepVal * row.height / row.valDiff + y: row.height - (index + 1) * height + visible: y > scaleTopLabel.height + TimelineText { + font.pixelSize: 8 + anchors.bottom: parent.bottom + anchors.bottomMargin: 2 + anchors.leftMargin: 2 + anchors.left: parent.left + text: prettyPrintScale(index * row.stepVal) + } + + Rectangle { + height: 1 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + color: Theme.color(Theme.Timeline_DividerColor) + } + } + } + } + } + } + } + } +} diff --git a/src/libs/tracing/qml/TimelineContent.qml b/src/libs/tracing/qml/TimelineContent.qml new file mode 100644 index 00000000000..636d7073567 --- /dev/null +++ b/src/libs/tracing/qml/TimelineContent.qml @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 1.2 +import TimelineRenderer 1.0 +import QtQml.Models 2.1 + +ScrollView { + id: scroller + + property QtObject zoomer + property QtObject modelProxy + + property int contentX: flick.contentX + property int contentY: flick.contentY + property int contentWidth: flick.contentWidth + property int contentHeight: flick.contentHeight + property bool selectionLocked + property int typeId + + signal propagateSelection(int newModel, int newItem) + + function clearChildren() + { + timelineView.clearChildren(); + } + + function select(modelIndex, eventIndex) + { + timelineView.select(modelIndex, eventIndex); + } + + function moveCategories(sourceIndex, targetIndex) + { + timelineModel.items.move(sourceIndex, targetIndex) + } + + function scroll() + { + flick.scroll(); + } + + // 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 + onStayInteractiveChanged: flick.interactive = stayInteractive + + Flickable { + id: flick + contentHeight: timelineView.height + height + flickableDirection: Flickable.HorizontalAndVerticalFlick + boundsBehavior: Flickable.StopAtBounds + pixelAligned: true + + onInteractiveChanged: interactive = stayInteractive + + property bool recursionGuard: false + + function guarded(operation) { + if (recursionGuard) + return; + recursionGuard = true; + operation(); + recursionGuard = false; + } + + // Logically we should bind to scroller.width above as we use scroller.width in scroll(). + // However, this width changes before scroller.width when the window is resized and if we + // don't explicitly set contentX here, for some reason an automatic change in contentX is + // triggered after this width has changed, but before scroller.width changes. This would be + // indistinguishabe from a manual flick by the user and thus changes the range position. We + // don't want to change the range position on resizing the window. Therefore we bind to this + // width. + onWidthChanged: scroll() + + // Update the zoom control on scrolling. + onContentXChanged: guarded(function() { + var newStartTime = contentX * zoomer.rangeDuration / scroller.width + + zoomer.windowStart; + if (isFinite(newStartTime) && Math.abs(newStartTime - zoomer.rangeStart) >= 1) { + var newEndTime = (contentX + scroller.width) * zoomer.rangeDuration / scroller.width + + zoomer.windowStart; + if (isFinite(newEndTime)) + zoomer.setRange(newStartTime, newEndTime); + } + }); + + // Scroll when the zoom control is updated + function scroll() { + guarded(function() { + if (zoomer.rangeDuration <= 0) { + contentWidth = 0; + contentX = 0; + } else { + var newWidth = zoomer.windowDuration * scroller.width / zoomer.rangeDuration; + if (isFinite(newWidth) && Math.abs(newWidth - contentWidth) >= 1) + contentWidth = newWidth; + var newStartX = (zoomer.rangeStart - zoomer.windowStart) * scroller.width / + zoomer.rangeDuration; + if (isFinite(newStartX) && Math.abs(newStartX - contentX) >= 1) + contentX = newStartX; + } + }); + } + + Column { + id: timelineView + + signal clearChildren + signal select(int modelIndex, int eventIndex) + + DelegateModel { + id: timelineModel + model: modelProxy.models + delegate: TimelineRenderer { + id: renderer + model: modelData + notes: modelProxy.notes + zoomer: scroller.zoomer + selectionLocked: scroller.selectionLocked + x: 0 + height: modelData.height + property int visualIndex: DelegateModel.itemsIndex + + // paint "under" the vertical scrollbar, so that it always matches with the + // timemarks + width: flick.contentWidth + + Connections { + target: timelineView + onClearChildren: renderer.clearData() + onSelect: { + if (modelIndex === index || modelIndex === -1) { + renderer.selectedItem = eventIndex; + if (eventIndex !== -1) + renderer.recenter(); + } + } + } + + function recenter() { + if (modelData.endTime(selectedItem) < zoomer.rangeStart || + modelData.startTime(selectedItem) > zoomer.rangeEnd) { + + var newStart = Math.max((modelData.startTime(selectedItem) + + modelData.endTime(selectedItem) - + zoomer.rangeDuration) / 2, zoomer.traceStart); + zoomer.setRange(newStart, + Math.min(newStart + zoomer.rangeDuration, zoomer.traceEnd)); + } + + var row = modelData.row(selectedItem); + var rowStart = modelData.rowOffset(row) + y; + var rowEnd = rowStart + modelData.rowHeight(row); + if (rowStart < flick.contentY || rowEnd - scroller.height > flick.contentY) + flick.contentY = (rowStart + rowEnd - scroller.height) / 2; + } + + onSelectedItemChanged: scroller.propagateSelection(index, selectedItem); + + Connections { + target: model + onDetailsChanged: { + if (selectedItem != -1) { + scroller.propagateSelection(-1, -1); + scroller.propagateSelection(index, selectedItem); + } + } + } + } + } + + Repeater { + id: repeater + model: timelineModel + } + } + } +} diff --git a/src/libs/tracing/qml/TimelineLabels.qml b/src/libs/tracing/qml/TimelineLabels.qml new file mode 100644 index 00000000000..71434822f4b --- /dev/null +++ b/src/libs/tracing/qml/TimelineLabels.qml @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQml.Models 2.1 +import TimelineTheme 1.0 + +Flickable { + id: categories + flickableDirection: Flickable.VerticalFlick + interactive: false + property color color + + property int selectedModel + property int selectedItem + property bool reverseSelect + property QtObject modelProxy + property QtObject zoomer + + signal selectItem(int modelIndex, int eventIndex) + signal moveCategories(int sourceIndex, int targetIndex) + + // reserve some more space than needed to prevent weird effects when resizing + contentHeight: categoryContent.height + height + + // Dispatch the cursor shape to all labels. When dragging the DropArea receiving + // the drag events is not necessarily related to the MouseArea receiving the mouse + // events, so we can't use the drag events to determine the cursor shape. + property bool dragging: false + + Column { + id: categoryContent + anchors.left: parent.left + anchors.right: parent.right + + DelegateModel { + id: labelsModel + + // As we cannot retrieve items by visible index we keep an array of row counts here, + // for the time marks to draw the row backgrounds in the right colors. + property var rowCounts: new Array(modelProxy.models.length) + + function updateRowCount(visualIndex, rowCount) { + if (rowCounts[visualIndex] !== rowCount) { + rowCounts[visualIndex] = rowCount; + // Array don't "change" if entries change. We have to signal manually. + rowCountsChanged(); + } + } + + model: modelProxy.models + delegate: Loader { + id: loader + asynchronous: y < categories.contentY + categories.height && + y + height > categories.contentY + active: modelData !== null && zoomer !== null && + (zoomer.traceDuration <= 0 || (!modelData.hidden && !modelData.empty)) + height: active ? Math.max(modelData.height, modelData.defaultRowHeight) : 0 + width: categories.width + property int visualIndex: DelegateModel.itemsIndex + + sourceComponent: Rectangle { + color: categories.color + height: loader.height + width: loader.width + + CategoryLabel { + id: label + model: modelData + notesModel: modelProxy.notes + visualIndex: loader.visualIndex + dragging: categories.dragging + reverseSelect: categories.reverseSelect + onDragStarted: categories.dragging = true + onDragStopped: categories.dragging = false + draggerParent: categories + width: 150 + height: parent.height + dragOffset: loader.y + + onDropped: { + categories.moveCategories(sourceIndex, targetIndex); + labelsModel.items.move(sourceIndex, targetIndex); + } + + onSelectById: { + categories.selectItem(index, eventId) + } + + onSelectNextBySelectionId: { + categories.selectItem(index, modelData.nextItemBySelectionId( + selectionId, zoomer.rangeStart, + categories.selectedModel === index ? categories.selectedItem : + -1)); + } + + onSelectPrevBySelectionId: { + categories.selectItem(index, modelData.prevItemBySelectionId( + selectionId, zoomer.rangeStart, + categories.selectedModel === index ? categories.selectedItem : + -1)); + } + } + + TimeMarks { + id: timeMarks + model: modelData + mockup: modelProxy.height === 0 + anchors.right: parent.right + anchors.left: label.right + anchors.top: parent.top + anchors.bottom: parent.bottom + property int visualIndex: loader.visualIndex + + // 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: (labelsModel.rowCounts.slice(0, visualIndex).reduce( + function(prev, rows) {return prev + rows}, 0) % 2) === 0 + + onRowCountChanged: labelsModel.updateRowCount(visualIndex, rowCount) + onVisualIndexChanged: labelsModel.updateRowCount(visualIndex, rowCount) + } + + Rectangle { + opacity: loader.y === 0 ? 0 : 1 + color: Theme.color(Theme.Timeline_DividerColor) + height: 1 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + } + } + } + } + + Repeater { + model: labelsModel + } + } +} diff --git a/src/libs/tracing/qml/TimelineRulers.qml b/src/libs/tracing/qml/TimelineRulers.qml new file mode 100644 index 00000000000..c095bd280e2 --- /dev/null +++ b/src/libs/tracing/qml/TimelineRulers.qml @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import TimelineTheme 1.0 + +Item { + id: rulersParent + property int scaleHeight + property double viewTimePerPixel: 1 + property double contentX + property double windowStart + clip: true + + ListModel { + id: rulersModel + } + + MouseArea { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: scaleHeight + + onClicked: { + rulersModel.append({ + timestamp: (mouse.x + contentX) * viewTimePerPixel + windowStart + }); + } + } + + Item { + id: dragDummy + property int index: -1 + onXChanged: { + if (index >= 0) { + rulersModel.setProperty( + index, "timestamp", + (x + contentX) * viewTimePerPixel + windowStart); + } + } + } + + Repeater { + model: rulersModel + + Item { + id: ruler + x: (timestamp - windowStart) / viewTimePerPixel - 1 - contentX + y: 0 + width: 2 + height: rulersParent.height + Rectangle { + id: arrow + height: scaleHeight + width: scaleHeight + rotation: 45 + anchors.verticalCenter: parent.top + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.color(Theme.Timeline_HandleColor) + MouseArea { + cursorShape: pressed ? Qt.DragMoveCursor : Qt.OpenHandCursor + anchors.fill: parent + drag.target: dragDummy + drag.axis: Drag.XAxis + drag.smoothed: false + onPressedChanged: { + if (!pressed) { + dragDummy.index = -1 + } else { + dragDummy.x = ruler.x + 1 + dragDummy.index = index + } + } + } + } + + Rectangle { + anchors.left: parent.left + anchors.top: arrow.bottom + anchors.bottom: parent.bottom + width: 2 + color: Theme.color(Theme.Timeline_HandleColor) + } + + Rectangle { + anchors.top: arrow.bottom + anchors.horizontalCenter: ruler.horizontalCenter + width: scaleHeight / 4 + height: width + color: Theme.color(Theme.Timeline_PanelBackgroundColor) + + Rectangle { + anchors.centerIn: parent + width: parent.width - 2 + height: 1 + color: Theme.color(Theme.Timeline_TextColor) + } + + MouseArea { + anchors.fill: parent + onClicked: rulersModel.remove(index, 1) + } + } + } + } +} diff --git a/src/libs/tracing/qml/TimelineText.qml b/src/libs/tracing/qml/TimelineText.qml new file mode 100644 index 00000000000..dc354875d55 --- /dev/null +++ b/src/libs/tracing/qml/TimelineText.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import TimelineTheme 1.0 + +Text { + font.pixelSize: 12 + font.family: "sans-serif" + textFormat: Text.PlainText + renderType: Text.NativeRendering + color: Theme.color(Theme.Timeline_TextColor) +} + diff --git a/src/libs/tracing/qml/ico_edit.png b/src/libs/tracing/qml/ico_edit.png Binary files differnew file mode 100644 index 00000000000..276e49fac58 --- /dev/null +++ b/src/libs/tracing/qml/ico_edit.png diff --git a/src/libs/tracing/qml/[email protected] b/src/libs/tracing/qml/[email protected] Binary files differnew file mode 100644 index 00000000000..5073df7eddc --- /dev/null +++ b/src/libs/tracing/qml/[email protected] diff --git a/src/libs/tracing/qml/ico_rangeselected.png b/src/libs/tracing/qml/ico_rangeselected.png Binary files differnew file mode 100644 index 00000000000..1d4e0284f9a --- /dev/null +++ b/src/libs/tracing/qml/ico_rangeselected.png diff --git a/src/libs/tracing/qml/[email protected] b/src/libs/tracing/qml/[email protected] Binary files differnew file mode 100644 index 00000000000..d108be37e0a --- /dev/null +++ b/src/libs/tracing/qml/[email protected] diff --git a/src/libs/tracing/qml/ico_rangeselection.png b/src/libs/tracing/qml/ico_rangeselection.png Binary files differnew file mode 100644 index 00000000000..546bf8beccd --- /dev/null +++ b/src/libs/tracing/qml/ico_rangeselection.png diff --git a/src/libs/tracing/qml/[email protected] b/src/libs/tracing/qml/[email protected] Binary files differnew file mode 100644 index 00000000000..9f200fe43a8 --- /dev/null +++ b/src/libs/tracing/qml/[email protected] diff --git a/src/libs/tracing/qml/ico_selectionmode.png b/src/libs/tracing/qml/ico_selectionmode.png Binary files differnew file mode 100644 index 00000000000..fcf28531d0c --- /dev/null +++ b/src/libs/tracing/qml/ico_selectionmode.png diff --git a/src/libs/tracing/qml/[email protected] b/src/libs/tracing/qml/[email protected] Binary files differnew file mode 100644 index 00000000000..b34991e0790 --- /dev/null +++ b/src/libs/tracing/qml/[email protected] diff --git a/src/libs/tracing/qml/notes.frag b/src/libs/tracing/qml/notes.frag new file mode 100644 index 00000000000..0f174ca28de --- /dev/null +++ b/src/libs/tracing/qml/notes.frag @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +varying lowp vec4 color; +varying lowp float d; + +void main() +{ + gl_FragColor = color * float(d < (2.0 / 3.0) || d > (5.0 / 6.0)); +} diff --git a/src/libs/tracing/qml/notes.vert b/src/libs/tracing/qml/notes.vert new file mode 100644 index 00000000000..be3c8700b8c --- /dev/null +++ b/src/libs/tracing/qml/notes.vert @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +attribute vec4 vertexCoord; +attribute float distanceFromTop; + +uniform mat4 matrix; +uniform vec4 notesColor; + +varying vec4 color; +varying float d; + +void main() +{ + gl_Position = matrix * vertexCoord; + gl_Position.z -= 0.1; + gl_Position.w = 1.0; + color = notesColor; + d = distanceFromTop; +} diff --git a/src/libs/tracing/qml/range_handle.png b/src/libs/tracing/qml/range_handle.png Binary files differnew file mode 100644 index 00000000000..94303303422 --- /dev/null +++ b/src/libs/tracing/qml/range_handle.png diff --git a/src/libs/tracing/qml/[email protected] b/src/libs/tracing/qml/[email protected] Binary files differnew file mode 100644 index 00000000000..3c954051c61 --- /dev/null +++ b/src/libs/tracing/qml/[email protected] diff --git a/src/libs/tracing/qml/timelineitems.frag b/src/libs/tracing/qml/timelineitems.frag new file mode 100644 index 00000000000..97d79770f64 --- /dev/null +++ b/src/libs/tracing/qml/timelineitems.frag @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifdef GL_OES_standard_derivatives +#extension GL_OES_standard_derivatives : enable +// else we probably have fwidth() in core +#endif + +varying lowp vec3 edgeColor; +varying lowp vec3 color; +varying lowp vec2 barycentric; + +void main() +{ + lowp vec2 d = fwidth(barycentric) * 4.0; + lowp vec4 edge_closeness = step(vec4(d.x, d.y, d.x, d.y), + vec4(barycentric.x, barycentric.y, 1.0 - barycentric.x, 1.0 - barycentric.y)); + lowp float total = min(min(edge_closeness[0], edge_closeness[1]), + min(edge_closeness[2], edge_closeness[3])); + gl_FragColor.rgb = mix(edgeColor, color, total); + gl_FragColor.a = 1.0; +} diff --git a/src/libs/tracing/qml/timelineitems.vert b/src/libs/tracing/qml/timelineitems.vert new file mode 100644 index 00000000000..153bf693db2 --- /dev/null +++ b/src/libs/tracing/qml/timelineitems.vert @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +attribute vec4 vertexCoord; +attribute vec2 rectSize; +attribute float selectionId; +attribute vec4 vertexColor; + +uniform vec2 scale; +uniform mat4 matrix; +uniform vec4 selectionColor; +uniform float selectedItem; + +varying vec3 color; +varying vec3 edgeColor; +varying vec2 barycentric; + +void main() +{ + gl_Position = matrix * vertexCoord; + + // Make very narrow events somewhat wider so that they don't collapse into 0 pixels + float scaledWidth = scale.x * rectSize.x; + float shift = sign(rectSize.x) * max(0.0, 3.0 - abs(scaledWidth)) * 0.0005; + gl_Position.x += shift; + + // Ditto for events with very small height + float scaledHeight = scale.y * rectSize.y; + gl_Position.y += float(rectSize.y > 0.0) * max(0.0, 3.0 - scaledHeight) * 0.003; + + barycentric = vec2(rectSize.x > 0.0 ? 1.0 : 0.0, rectSize.y > 0.0 ? 1.0 : 0.0); + color = vertexColor.rgb; + float selected = min(1.0, abs(selectionId - selectedItem)); + edgeColor = mix(selectionColor.rgb, vertexColor.rgb, selected); + gl_Position.z += mix(0.0, (shift + 0.0015) / 10.0, selected); + gl_Position.w = 1.0; +} diff --git a/src/libs/tracing/qml/tracing.qrc b/src/libs/tracing/qml/tracing.qrc new file mode 100644 index 00000000000..c59a7ab93ae --- /dev/null +++ b/src/libs/tracing/qml/tracing.qrc @@ -0,0 +1,37 @@ +<RCC> + <qresource prefix="/tracing"> + <file>ButtonsBar.qml</file> + <file>CategoryLabel.qml</file> + <file>Detail.qml</file> + <file>FlameGraphDelegate.qml</file> + <file>FlameGraphDetails.qml</file> + <file>FlameGraphText.qml</file> + <file>ico_edit.png</file> + <file>[email protected]</file> + <file>ico_rangeselected.png</file> + <file>[email protected]</file> + <file>ico_rangeselection.png</file> + <file>[email protected]</file> + <file>ico_selectionmode.png</file> + <file>[email protected]</file> + <file>ImageToolButton.qml</file> + <file>MainView.qml</file> + <file>notes.frag</file> + <file>notes.vert</file> + <file>Overview.qml</file> + <file>range_handle.png</file> + <file>RangeDetails.qml</file> + <file>RangeMover.qml</file> + <file>RowLabel.qml</file> + <file>SelectionRange.qml</file> + <file>SelectionRangeDetails.qml</file> + <file>TimeDisplay.qml</file> + <file>TimelineContent.qml</file> + <file>timelineitems.frag</file> + <file>timelineitems.vert</file> + <file>TimelineLabels.qml</file> + <file>TimelineRulers.qml</file> + <file>TimelineText.qml</file> + <file>TimeMarks.qml</file> + </qresource> +</RCC> diff --git a/src/libs/tracing/runscenegraphtest.cpp b/src/libs/tracing/runscenegraphtest.cpp new file mode 100644 index 00000000000..c1210aa5e0c --- /dev/null +++ b/src/libs/tracing/runscenegraphtest.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "runscenegraphtest.h" + +#include <QTest> +#include <QString> +#include <QOpenGLContext> +#include <QOffscreenSurface> +#include <QSGEngine> +#include <QSGAbstractRenderer> + +namespace Timeline { + +static void renderMessageHandler(QtMsgType type, const QMessageLogContext &context, + const QString &message) +{ + if (type > QtDebugMsg) + QTest::qFail(message.toLatin1().constData(), context.file, context.line); + else + QTest::qWarn(message.toLatin1().constData(), context.file, context.line); +} + +void runSceneGraphTest(QSGNode *node) +{ + QSurfaceFormat format; + format.setStencilBufferSize(8); + format.setDepthBufferSize(24); + + QOpenGLContext context; + context.setFormat(format); + QVERIFY(context.create()); + + QOffscreenSurface surface; + surface.setFormat(format); + surface.create(); + + QVERIFY(context.makeCurrent(&surface)); + + QSGEngine engine; + QSGRootNode root; + root.appendChildNode(node); + engine.initialize(&context); + + QSGAbstractRenderer *renderer = engine.createRenderer(); + QVERIFY(renderer != 0); + renderer->setRootNode(&root); + QtMessageHandler originalHandler = qInstallMessageHandler(renderMessageHandler); + renderer->renderScene(); + qInstallMessageHandler(originalHandler); + delete renderer; + + // Unfortunately we cannot check the results of the rendering. But at least we know the shaders + // have not crashed here. + + context.doneCurrent(); +} + +} // namespace Timeline diff --git a/src/libs/tracing/runscenegraphtest.h b/src/libs/tracing/runscenegraphtest.h new file mode 100644 index 00000000000..f387e8e956d --- /dev/null +++ b/src/libs/tracing/runscenegraphtest.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include <QSGNode> + +namespace Timeline { + +void TRACING_EXPORT runSceneGraphTest(QSGNode *node); + +} // namespace Timeline diff --git a/src/libs/tracing/timelineabstractrenderer.cpp b/src/libs/tracing/timelineabstractrenderer.cpp new file mode 100644 index 00000000000..4b9f775c3b5 --- /dev/null +++ b/src/libs/tracing/timelineabstractrenderer.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelineabstractrenderer_p.h" + +namespace Timeline { + +TimelineAbstractRenderer::TimelineAbstractRendererPrivate::TimelineAbstractRendererPrivate() : + selectedItem(-1), selectionLocked(true), model(0), notes(0), zoomer(0), modelDirty(false), + rowHeightsDirty(false), notesDirty(false) +{ +} + +TimelineAbstractRenderer::TimelineAbstractRendererPrivate::~TimelineAbstractRendererPrivate() +{ + // Nothing to delete here as all the pointer members are owned by other classes. +} + +TimelineAbstractRenderer::TimelineAbstractRenderer(TimelineAbstractRendererPrivate &dd, + QQuickItem *parent) : + QQuickItem(parent), d_ptr(&dd) +{ + setFlag(ItemHasContents); +} + +int TimelineAbstractRenderer::selectedItem() const +{ + Q_D(const TimelineAbstractRenderer); + return d->selectedItem; +} + +void TimelineAbstractRenderer::setSelectedItem(int itemIndex) +{ + Q_D(TimelineAbstractRenderer); + if (d->selectedItem != itemIndex) { + d->selectedItem = itemIndex; + update(); + emit selectedItemChanged(itemIndex); + } +} + +TimelineAbstractRenderer::TimelineAbstractRenderer(QQuickItem *parent) : QQuickItem(parent), + d_ptr(new TimelineAbstractRendererPrivate) +{ + setFlag(ItemHasContents); +} + +TimelineAbstractRenderer::~TimelineAbstractRenderer() +{ + Q_D(TimelineAbstractRenderer); + delete d; +} + +bool TimelineAbstractRenderer::selectionLocked() const +{ + Q_D(const TimelineAbstractRenderer); + return d->selectionLocked; +} + +void TimelineAbstractRenderer::setSelectionLocked(bool locked) +{ + Q_D(TimelineAbstractRenderer); + if (d->selectionLocked != locked) { + d->selectionLocked = locked; + update(); + emit selectionLockedChanged(locked); + } +} + +TimelineModel *TimelineAbstractRenderer::model() const +{ + Q_D(const TimelineAbstractRenderer); + return d->model; +} + +void TimelineAbstractRenderer::setModel(TimelineModel *model) +{ + Q_D(TimelineAbstractRenderer); + if (d->model == model) + return; + + if (d->model) { + disconnect(d->model, &TimelineModel::expandedChanged, this, &QQuickItem::update); + disconnect(d->model, &TimelineModel::hiddenChanged, this, &QQuickItem::update); + disconnect(d->model, &TimelineModel::expandedRowHeightChanged, + this, &TimelineAbstractRenderer::setRowHeightsDirty); + disconnect(d->model, &TimelineModel::contentChanged, + this, &TimelineAbstractRenderer::setModelDirty); + } + + d->model = model; + if (d->model) { + connect(d->model, &TimelineModel::expandedChanged, this, &QQuickItem::update); + connect(d->model, &TimelineModel::hiddenChanged, this, &QQuickItem::update); + connect(d->model, &TimelineModel::expandedRowHeightChanged, + this, &TimelineAbstractRenderer::setRowHeightsDirty); + connect(d->model, &TimelineModel::contentChanged, + this, &TimelineAbstractRenderer::setModelDirty); + d->renderPasses = d->model->supportedRenderPasses(); + } + + setModelDirty(); + emit modelChanged(d->model); +} + +TimelineNotesModel *TimelineAbstractRenderer::notes() const +{ + Q_D(const TimelineAbstractRenderer); + return d->notes; +} + +void TimelineAbstractRenderer::setNotes(TimelineNotesModel *notes) +{ + Q_D(TimelineAbstractRenderer); + if (d->notes == notes) + return; + + if (d->notes) + disconnect(d->notes, &TimelineNotesModel::changed, + this, &TimelineAbstractRenderer::setNotesDirty); + + d->notes = notes; + if (d->notes) + connect(d->notes, &TimelineNotesModel::changed, + this, &TimelineAbstractRenderer::setNotesDirty); + + setNotesDirty(); + emit notesChanged(d->notes); +} + +TimelineZoomControl *TimelineAbstractRenderer::zoomer() const +{ + Q_D(const TimelineAbstractRenderer); + return d->zoomer; +} + +void TimelineAbstractRenderer::setZoomer(TimelineZoomControl *zoomer) +{ + Q_D(TimelineAbstractRenderer); + if (zoomer != d->zoomer) { + if (d->zoomer != 0) + disconnect(d->zoomer, &TimelineZoomControl::windowChanged, this, &QQuickItem::update); + d->zoomer = zoomer; + if (d->zoomer != 0) + connect(d->zoomer, &TimelineZoomControl::windowChanged, this, &QQuickItem::update); + emit zoomerChanged(zoomer); + update(); + } +} + +bool TimelineAbstractRenderer::modelDirty() const +{ + Q_D(const TimelineAbstractRenderer); + return d->modelDirty; +} + +bool TimelineAbstractRenderer::notesDirty() const +{ + Q_D(const TimelineAbstractRenderer); + return d->notesDirty; +} + +bool TimelineAbstractRenderer::rowHeightsDirty() const +{ + Q_D(const TimelineAbstractRenderer); + return d->rowHeightsDirty; +} + +void TimelineAbstractRenderer::setModelDirty() +{ + Q_D(TimelineAbstractRenderer); + d->modelDirty = true; + update(); +} + +void TimelineAbstractRenderer::setRowHeightsDirty() +{ + Q_D(TimelineAbstractRenderer); + d->rowHeightsDirty = true; + update(); +} + +void TimelineAbstractRenderer::setNotesDirty() +{ + Q_D(TimelineAbstractRenderer); + d->notesDirty = true; + update(); +} + +// Reset the dirty flags, delete the old node (if given), and return 0 +QSGNode *TimelineAbstractRenderer::updatePaintNode(QSGNode *oldNode, + UpdatePaintNodeData *updatePaintNodeData) +{ + Q_D(TimelineAbstractRenderer); + d->modelDirty = false; + d->rowHeightsDirty = false; + d->notesDirty = false; + return QQuickItem::updatePaintNode(oldNode, updatePaintNodeData); +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelineabstractrenderer.h b/src/libs/tracing/timelineabstractrenderer.h new file mode 100644 index 00000000000..11b2b75fc81 --- /dev/null +++ b/src/libs/tracing/timelineabstractrenderer.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinezoomcontrol.h" +#include "timelinemodel.h" +#include "timelinenotesmodel.h" +#include "timelinerenderpass.h" + +#include <QSGTransformNode> +#include <QQuickItem> + +namespace Timeline { + + +class TRACING_EXPORT TimelineAbstractRenderer : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(Timeline::TimelineModel *model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(Timeline::TimelineNotesModel *notes READ notes WRITE setNotes NOTIFY notesChanged) + Q_PROPERTY(Timeline::TimelineZoomControl *zoomer READ zoomer WRITE setZoomer NOTIFY zoomerChanged) + Q_PROPERTY(bool selectionLocked READ selectionLocked WRITE setSelectionLocked NOTIFY selectionLockedChanged) + Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem NOTIFY selectedItemChanged) + +public: + TimelineAbstractRenderer(QQuickItem *parent = 0); + ~TimelineAbstractRenderer(); + + bool selectionLocked() const; + int selectedItem() const; + + TimelineModel *model() const; + void setModel(TimelineModel *model); + + TimelineNotesModel *notes() const; + void setNotes(TimelineNotesModel *notes); + + TimelineZoomControl *zoomer() const; + void setZoomer(TimelineZoomControl *zoomer); + + bool modelDirty() const; + bool notesDirty() const; + bool rowHeightsDirty() const; + +signals: + void modelChanged(TimelineModel *model); + void notesChanged(TimelineNotesModel *notes); + void zoomerChanged(TimelineZoomControl *zoomer); + void selectionLockedChanged(bool locked); + void selectedItemChanged(int itemIndex); + +public: + void setSelectedItem(int itemIndex); + void setSelectionLocked(bool locked); + + void setModelDirty(); + void setNotesDirty(); + void setRowHeightsDirty(); + +protected: + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData); + + class TimelineAbstractRendererPrivate; + TimelineAbstractRenderer(TimelineAbstractRendererPrivate &dd, QQuickItem *parent = 0); + TimelineAbstractRendererPrivate *d_ptr; + Q_DECLARE_PRIVATE(TimelineAbstractRenderer) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelineabstractrenderer_p.h b/src/libs/tracing/timelineabstractrenderer_p.h new file mode 100644 index 00000000000..0626710cd86 --- /dev/null +++ b/src/libs/tracing/timelineabstractrenderer_p.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineabstractrenderer.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineAbstractRenderer::TimelineAbstractRendererPrivate { +public: + TimelineAbstractRendererPrivate(); + virtual ~TimelineAbstractRendererPrivate(); + + int selectedItem; + bool selectionLocked; + TimelineModel *model; + TimelineNotesModel *notes; + TimelineZoomControl *zoomer; + + bool modelDirty; + bool rowHeightsDirty; + bool notesDirty; + + QList<const TimelineRenderPass *> renderPasses; +}; + +} diff --git a/src/libs/tracing/timelineformattime.cpp b/src/libs/tracing/timelineformattime.cpp new file mode 100644 index 00000000000..54f12d90dd8 --- /dev/null +++ b/src/libs/tracing/timelineformattime.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelineformattime.h" +#include <QtQml> + +namespace Timeline { + +QString formatTime(qint64 timestamp, qint64 reference) +{ + static const char *decimalUnits[] = {"ns", "\xb5s", "ms", "s"}; + static const double second = 1e9; + static const double minute = 60; + static const double hour = 60; + + int round = 1; + qint64 barrier = 1; + + for (uint i = 0, end = sizeof(decimalUnits) / sizeof(char *); i < end; ++i) { + const double dividend = barrier; + if (reference < barrier) { + round += 3; + barrier *= 1000; + } else { + barrier *= 10; + if (reference < barrier) { + round += 2; + barrier *= 100; + } else { + barrier *= 10; + if (reference < barrier) + round += 1; + barrier *= 10; + } + } + if (timestamp < barrier) { + return QString::number(timestamp / dividend, 'g', qMax(round, 3)) + + QString::fromLatin1(decimalUnits[i]); + } + } + + double seconds = timestamp / second; + if (seconds < minute) { + return QString::number(seconds, 'g', qMax(round, 3)) + "s"; + } else { + int minutes = seconds / minute; + seconds -= minutes * minute; + if (minutes < hour) { + return QString::fromLatin1("%1m %2s").arg(QString::number(minutes), + QString::number(seconds, 'g', round)); + } else { + int hours = minutes / hour; + minutes -= hours * hour; + if (reference < barrier * minute) { + return QString::fromLatin1("%1h %2m %3s").arg(QString::number(hours), + QString::number(minutes), + QString::number(seconds, 'g', round)); + } else { + return QString::fromLatin1("%1h %2m").arg(QString::number(hours), + QString::number(minutes)); + } + + } + } +} + +static QObject *createFormatter(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine); + Q_UNUSED(scriptEngine); + return new TimeFormatter; +} + +void TimeFormatter::setupTimeFormatter() +{ + static const int typeIndex = qmlRegisterSingletonType<TimeFormatter>( + "TimelineTimeFormatter", 1, 0, "TimeFormatter", createFormatter); + Q_UNUSED(typeIndex); +} + +} diff --git a/src/libs/tracing/timelineformattime.h b/src/libs/tracing/timelineformattime.h new file mode 100644 index 00000000000..0ed8a5a3f40 --- /dev/null +++ b/src/libs/tracing/timelineformattime.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#pragma once + +#include "tracing_global.h" +#include <QString> +#include <QObject> +#include <limits> + +namespace Timeline { +QString TRACING_EXPORT formatTime(qint64 timestamp, + qint64 reference = std::numeric_limits<qint64>::max()); + +class TRACING_EXPORT TimeFormatter : public QObject { + Q_OBJECT +public: + Q_INVOKABLE QString format(qint64 timestamp, qint64 reference) + { + return formatTime(timestamp, reference); + } + + static void setupTimeFormatter(); +}; + +} diff --git a/src/libs/tracing/timelineitemsrenderpass.cpp b/src/libs/tracing/timelineitemsrenderpass.cpp new file mode 100644 index 00000000000..7dd88406e5b --- /dev/null +++ b/src/libs/tracing/timelineitemsrenderpass.cpp @@ -0,0 +1,766 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelineitemsrenderpass.h" +#include "timelinerenderstate.h" +#include <QSGSimpleRectNode> +#include <QSGVertexColorMaterial> +#include <QtAlgorithms> + +namespace Timeline { + +class TimelineItemsRenderPassState : public TimelineRenderPass::State { +public: + TimelineItemsRenderPassState(const TimelineModel *model); + ~TimelineItemsRenderPassState(); + + QSGNode *expandedRow(int row) const { return m_expandedRows[row]; } + QSGNode *collapsedRow(int row) const { return m_collapsedRows[row]; } + + const QVector<QSGNode *> &expandedRows() const { return m_expandedRows; } + const QVector<QSGNode *> &collapsedRows() const { return m_collapsedRows; } + TimelineItemsMaterial *collapsedRowMaterial() { return &m_collapsedRowMaterial; } + + int indexFrom() const { return m_indexFrom; } + int indexTo() const { return m_indexTo; } + void updateIndexes(int from, int to); + void updateCollapsedRowMaterial(float xScale, int selectedItem, QColor selectionColor); + +private: + int m_indexFrom; + int m_indexTo; + TimelineItemsMaterial m_collapsedRowMaterial; + + QVector<QSGNode *> m_expandedRows; + QVector<QSGNode *> m_collapsedRows; +}; + +struct TimelineItemsGeometry { + // Vertex indices are 16bit + static const int maxVerticesPerNode = 0xffff; + + enum VerticesPerEvent { + NoVertices = 0, + VerticesForSameHeight = 4, + VerticesForDifferentHeight = 6 + }; + + TimelineItemsGeometry(); + uint usedVertices; + + OpaqueColoredPoint2DWithSize prevNode; + OpaqueColoredPoint2DWithSize currentNode; + + QSGGeometryNode *node; + + void initNodes(); + + bool isEmpty() const; + + void allocate(QSGMaterial *material); + VerticesPerEvent addVertices(); + void addEvent(); + + void nextNode(float itemLeft, float itemTop, float itemWidth = 0, float selectionId = 0, + uchar red = 0, uchar green = 0, uchar blue = 0); + void updateCurrentNode(float itemRight, float itemTop); +}; + +class NodeUpdater { +public: + NodeUpdater(const TimelineModel *model, const TimelineRenderState *parentState, + TimelineItemsRenderPassState *state, int indexFrom, int indexTo); + void run(); + +private: + static const int s_maxNumItems = 1 << 20; + static const qint64 s_invalidTimestamp = 0xffffffffffffffffLL; + + struct ItemDescription + { + uchar red; + uchar green; + uchar blue; + + float width; + float left; + float right; + + float top; + float selectionId; + }; + + void calculateDistances(); + int updateVertices(TimelineItemsGeometry &geometry, const QVarLengthArray<qint64> &distances, + qint64 minDistance, float itemTop, int i) const; + void addEvent(TimelineItemsGeometry &geometry, const QVarLengthArray<qint64> &distances, + qint64 minDistance, const ItemDescription &item, int i) const; + int updateNodes(const int from, const int to) const; + + const TimelineModel *m_model; + const TimelineRenderState *m_parentState; + const int m_indexFrom; + const int m_indexTo; + + TimelineItemsRenderPassState *m_state; + QVarLengthArray<qint64> m_collapsedDistances; + QVarLengthArray<qint64> m_expandedDistances; + qint64 m_minCollapsedDistance; + qint64 m_minExpandedDistance; +}; + +void OpaqueColoredPoint2DWithSize::set(float nx, float ny, float nw, float nh, float nid, + uchar nr, uchar ng, uchar nb, uchar d) { + x = nx; y = ny; w = nw; h = nh; id = nid; + r = nr; g = ng, b = nb; a = d; +} + +float OpaqueColoredPoint2DWithSize::top() const +{ + return id < 0 ? (y / -id) : y; +} + +void OpaqueColoredPoint2DWithSize::update(float nr, float ny) +{ + if (a <= MaximumDirection) { + a += MaximumDirection; + id = -2; + } else { + --id; + } + + y += ny; + w = nr - x; +} + +OpaqueColoredPoint2DWithSize::Direction OpaqueColoredPoint2DWithSize::direction() const +{ + return static_cast<Direction>(a > MaximumDirection ? a - MaximumDirection : a); +} + +void OpaqueColoredPoint2DWithSize::setCommon(const OpaqueColoredPoint2DWithSize *master) +{ + a = 255; + if (master->a > MaximumDirection) { + id = std::numeric_limits<float>::lowest(); + r = g = b = 128; + } else { + id = master->id; + r = master->r; + g = master->g; + b = master->b; + } +} + +void OpaqueColoredPoint2DWithSize::setLeft(const OpaqueColoredPoint2DWithSize *master) +{ + w = -master->w; + x = master->x; +} + +void OpaqueColoredPoint2DWithSize::setRight(const OpaqueColoredPoint2DWithSize *master) +{ + w = master->w; + x = master->x + master->w; +} + +void OpaqueColoredPoint2DWithSize::setTop(const OpaqueColoredPoint2DWithSize *master) +{ + y = master->id < 0 ? master->y / -master->id : master->y; + h = TimelineModel::defaultRowHeight() - y; +} + +void OpaqueColoredPoint2DWithSize::setBottom(const OpaqueColoredPoint2DWithSize *master) +{ + y = TimelineModel::defaultRowHeight(); + h = (master->id < 0 ? master->y / -master->id : master->y) - TimelineModel::defaultRowHeight(); +} + +void OpaqueColoredPoint2DWithSize::setBottomLeft(const OpaqueColoredPoint2DWithSize *master) +{ + setCommon(master); + setLeft(master); + setBottom(master); +} + +void OpaqueColoredPoint2DWithSize::setBottomRight(const OpaqueColoredPoint2DWithSize *master) +{ + setCommon(master); + setRight(master); + setBottom(master); +} + +void OpaqueColoredPoint2DWithSize::setTopLeft(const OpaqueColoredPoint2DWithSize *master) +{ + setCommon(master); + setLeft(master); + setTop(master); +} + +void OpaqueColoredPoint2DWithSize::setTopRight(const OpaqueColoredPoint2DWithSize *master) +{ + setCommon(master); + setRight(master); + setTop(master); +} + +void TimelineItemsGeometry::addEvent() +{ + OpaqueColoredPoint2DWithSize *v = + OpaqueColoredPoint2DWithSize::fromVertexData(node->geometry()); + switch (currentNode.direction()) { + case OpaqueColoredPoint2DWithSize::BottomToTop: + v[usedVertices++].setBottomLeft(¤tNode); + v[usedVertices++].setBottomRight(¤tNode); + v[usedVertices++].setTopLeft(¤tNode); + v[usedVertices++].setTopRight(¤tNode); + break; + case OpaqueColoredPoint2DWithSize::TopToBottom: + if (prevNode.top() != currentNode.top()) { + v[usedVertices++].setTopRight(&prevNode); + v[usedVertices++].setTopLeft(¤tNode); + } + v[usedVertices++].setTopLeft((¤tNode)); + v[usedVertices++].setTopRight((¤tNode)); + v[usedVertices++].setBottomLeft((¤tNode)); + v[usedVertices++].setBottomRight((¤tNode)); + break; + default: + break; + } +} + +OpaqueColoredPoint2DWithSize *OpaqueColoredPoint2DWithSize::fromVertexData(QSGGeometry *geometry) +{ + Q_ASSERT(geometry->attributeCount() == 4); + Q_ASSERT(geometry->sizeOfVertex() == sizeof(OpaqueColoredPoint2DWithSize)); + const QSGGeometry::Attribute *attributes = geometry->attributes(); + Q_ASSERT(attributes[0].position == 0); + Q_ASSERT(attributes[0].tupleSize == 2); + Q_ASSERT(attributes[0].type == GL_FLOAT); + Q_ASSERT(attributes[1].position == 1); + Q_ASSERT(attributes[1].tupleSize == 2); + Q_ASSERT(attributes[1].type == GL_FLOAT); + Q_ASSERT(attributes[2].position == 2); + Q_ASSERT(attributes[2].tupleSize == 1); + Q_ASSERT(attributes[2].type == GL_FLOAT); + Q_ASSERT(attributes[3].position == 3); + Q_ASSERT(attributes[3].tupleSize == 4); + Q_ASSERT(attributes[3].type == GL_UNSIGNED_BYTE); + Q_UNUSED(attributes); + return static_cast<OpaqueColoredPoint2DWithSize *>(geometry->vertexData()); +} + +TimelineItemsGeometry::TimelineItemsGeometry() : usedVertices(0), node(0) +{ + initNodes(); +} + +void TimelineItemsGeometry::initNodes() +{ + currentNode.set(0, TimelineModel::defaultRowHeight(), 0, 0, 0, 0, 0, 0, + OpaqueColoredPoint2DWithSize::InvalidDirection); + prevNode.set(0, TimelineModel::defaultRowHeight(), 0, 0, 0, 0, 0, 0, + OpaqueColoredPoint2DWithSize::InvalidDirection); +} + +bool TimelineItemsGeometry::isEmpty() const +{ + return usedVertices == 0 && + currentNode.direction() == OpaqueColoredPoint2DWithSize::InvalidDirection; +} + +void TimelineItemsGeometry::allocate(QSGMaterial *material) +{ + QSGGeometry *geometry = new QSGGeometry(OpaqueColoredPoint2DWithSize::attributes(), + usedVertices); + Q_ASSERT(geometry->vertexData()); + geometry->setIndexDataPattern(QSGGeometry::StaticPattern); + geometry->setVertexDataPattern(QSGGeometry::StaticPattern); + node = new QSGGeometryNode; + node->setGeometry(geometry); + node->setFlag(QSGNode::OwnsGeometry, true); + node->setMaterial(material); + usedVertices = 0; + initNodes(); +} + +TimelineItemsGeometry::VerticesPerEvent TimelineItemsGeometry::addVertices() +{ + switch (currentNode.direction()) { + case OpaqueColoredPoint2DWithSize::BottomToTop: + usedVertices += VerticesForSameHeight; + return VerticesForSameHeight; + case OpaqueColoredPoint2DWithSize::TopToBottom: { + VerticesPerEvent vertices = (prevNode.top() != currentNode.top() ? + VerticesForDifferentHeight : VerticesForSameHeight); + usedVertices += vertices; + return vertices; + } default: + return NoVertices; + } +} + +void TimelineItemsGeometry::nextNode(float itemLeft, float itemTop, float itemWidth, + float selectionId, uchar red, uchar green, uchar blue) +{ + prevNode = currentNode; + currentNode.set(itemLeft, itemTop, itemWidth, TimelineModel::defaultRowHeight() - itemTop, + selectionId, red, green, blue, + currentNode.direction() == OpaqueColoredPoint2DWithSize::BottomToTop ? + OpaqueColoredPoint2DWithSize::TopToBottom : + OpaqueColoredPoint2DWithSize::BottomToTop); +} + +void TimelineItemsGeometry::updateCurrentNode(float itemRight, float itemTop) +{ + currentNode.update(itemRight, itemTop); +} + +class TimelineExpandedRowNode : public QSGNode { +public: + TimelineItemsMaterial material; + virtual ~TimelineExpandedRowNode() {} +}; + +static qint64 startTime(const TimelineModel *model, const TimelineRenderState *parentState, int i) +{ + return qMax(parentState->start(), model->startTime(i)); +} + +static qint64 endTime(const TimelineModel *model, const TimelineRenderState *parentState, int i) +{ + return qMin(parentState->end(), model->startTime(i) + model->duration(i)); +} + +const QSGGeometry::AttributeSet &OpaqueColoredPoint2DWithSize::attributes() +{ + static QSGGeometry::Attribute data[] = { + QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), + QSGGeometry::Attribute::create(1, 2, GL_FLOAT), + QSGGeometry::Attribute::create(2, 1, GL_FLOAT), + QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE) + }; + static QSGGeometry::AttributeSet attrs = { + 4, + sizeof(OpaqueColoredPoint2DWithSize), + data + }; + return attrs; +} + +const TimelineItemsRenderPass *TimelineItemsRenderPass::instance() +{ + static const TimelineItemsRenderPass pass; + return &pass; +} + +TimelineRenderPass::State *TimelineItemsRenderPass::update(const TimelineAbstractRenderer *renderer, + const TimelineRenderState *parentState, + State *oldState, int indexFrom, + int indexTo, bool stateChanged, + float spacing) const +{ + Q_UNUSED(stateChanged); + const TimelineModel *model = renderer->model(); + if (!model || indexFrom < 0 || indexTo > model->count() || indexFrom >= indexTo) + return oldState; + + QColor selectionColor = (renderer->selectionLocked() ? QColor(96,0,255) : + QColor(Qt::blue)).lighter(130); + + TimelineItemsRenderPassState *state; + if (oldState == 0) + state = new TimelineItemsRenderPassState(model); + else + state = static_cast<TimelineItemsRenderPassState *>(oldState); + + + int selectedItem = renderer->selectedItem() == -1 ? -1 : + model->selectionId(renderer->selectedItem()); + + state->updateCollapsedRowMaterial(spacing / parentState->scale(), selectedItem, selectionColor); + + if (state->indexFrom() < state->indexTo()) { + if (indexFrom < state->indexFrom() || indexTo > state->indexTo()) + NodeUpdater(model, parentState, state, indexFrom, indexTo).run(); + } else if (indexFrom < indexTo) { + NodeUpdater(model, parentState, state, indexFrom, indexTo).run(); + } + + if (model->expanded()) { + for (int row = 0; row < model->expandedRowCount(); ++row) { + TimelineExpandedRowNode *rowNode = static_cast<TimelineExpandedRowNode *>( + state->expandedRow(row)); + rowNode->material.setScale( + QVector2D(spacing / parentState->scale(), + static_cast<float>(model->expandedRowHeight(row))) / + static_cast<float>(TimelineModel::defaultRowHeight())); + rowNode->material.setSelectedItem(selectedItem); + rowNode->material.setSelectionColor(selectionColor); + } + } + + state->updateIndexes(indexFrom, indexTo); + return state; +} + +TimelineItemsRenderPass::TimelineItemsRenderPass() +{ +} + +class TimelineItemsMaterialShader : public QSGMaterialShader +{ +public: + TimelineItemsMaterialShader(); + + virtual void updateState(const RenderState &state, QSGMaterial *newEffect, + QSGMaterial *oldEffect); + virtual char const *const *attributeNames() const; + +private: + virtual void initialize(); + + int m_matrix_id; + int m_scale_id; + int m_selection_color_id; + int m_selected_item_id; + int m_z_range_id; +}; + +TimelineItemsMaterialShader::TimelineItemsMaterialShader() + : QSGMaterialShader() +{ + setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/tracing/timelineitems.vert")); + setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/tracing/timelineitems.frag")); +} + +void TimelineItemsMaterialShader::updateState(const RenderState &state, QSGMaterial *newMaterial, + QSGMaterial *) +{ + if (state.isMatrixDirty()) { + TimelineItemsMaterial *material = static_cast<TimelineItemsMaterial *>(newMaterial); + program()->setUniformValue(m_matrix_id, state.combinedMatrix()); + program()->setUniformValue(m_scale_id, material->scale()); + program()->setUniformValue(m_selection_color_id, material->selectionColor()); + program()->setUniformValue(m_selected_item_id, material->selectedItem()); + program()->setUniformValue(m_z_range_id, GLfloat(1.0)); + } +} + +char const *const *TimelineItemsMaterialShader::attributeNames() const +{ + static const char *const attr[] = {"vertexCoord", "rectSize", "selectionId", "vertexColor", 0}; + return attr; +} + +void TimelineItemsMaterialShader::initialize() +{ + m_matrix_id = program()->uniformLocation("matrix"); + m_scale_id = program()->uniformLocation("scale"); + m_selection_color_id = program()->uniformLocation("selectionColor"); + m_selected_item_id = program()->uniformLocation("selectedItem"); + m_z_range_id = program()->uniformLocation("_qt_zRange"); +} + + +TimelineItemsMaterial::TimelineItemsMaterial() : m_selectedItem(-1) +{ + setFlag(QSGMaterial::Blending, false); +} + +QVector2D TimelineItemsMaterial::scale() const +{ + return m_scale; +} + +void TimelineItemsMaterial::setScale(QVector2D scale) +{ + m_scale = scale; +} + +float TimelineItemsMaterial::selectedItem() const +{ + return m_selectedItem; +} + +void TimelineItemsMaterial::setSelectedItem(float selectedItem) +{ + m_selectedItem = selectedItem; +} + +QColor TimelineItemsMaterial::selectionColor() const +{ + return m_selectionColor; +} + +void TimelineItemsMaterial::setSelectionColor(QColor selectionColor) +{ + m_selectionColor = selectionColor; +} + +QSGMaterialType *TimelineItemsMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +QSGMaterialShader *TimelineItemsMaterial::createShader() const +{ + return new TimelineItemsMaterialShader; +} + +TimelineItemsRenderPassState::TimelineItemsRenderPassState(const TimelineModel *model) : + m_indexFrom(std::numeric_limits<int>::max()), m_indexTo(-1) +{ + m_expandedRows.reserve(model->expandedRowCount()); + m_collapsedRows.reserve(model->collapsedRowCount()); + for (int i = 0; i < model->expandedRowCount(); ++i) { + TimelineExpandedRowNode *node = new TimelineExpandedRowNode; + node->setFlag(QSGNode::OwnedByParent, false); + m_expandedRows << node; + } + for (int i = 0; i < model->collapsedRowCount(); ++i) { + QSGNode *node = new QSGNode; + node->setFlag(QSGNode::OwnedByParent, false); + m_collapsedRows << node; + } +} + +TimelineItemsRenderPassState::~TimelineItemsRenderPassState() +{ + qDeleteAll(m_collapsedRows); + qDeleteAll(m_expandedRows); +} + +void TimelineItemsRenderPassState::updateIndexes(int from, int to) +{ + if (from < m_indexFrom) + m_indexFrom = from; + if (to > m_indexTo) + m_indexTo = to; +} + +void TimelineItemsRenderPassState::updateCollapsedRowMaterial(float xScale, int selectedItem, + QColor selectionColor) +{ + m_collapsedRowMaterial.setScale(QVector2D(xScale, 1)); + m_collapsedRowMaterial.setSelectedItem(selectedItem); + m_collapsedRowMaterial.setSelectionColor(selectionColor); +} + +NodeUpdater::NodeUpdater(const TimelineModel *model, const TimelineRenderState *parentState, + TimelineItemsRenderPassState *state, int indexFrom, int indexTo) : + m_model(model), m_parentState(parentState), m_indexFrom(indexFrom), m_indexTo(indexTo), + m_state(state), m_minCollapsedDistance(0), m_minExpandedDistance(0) +{ +} + +void NodeUpdater::calculateDistances() +{ + int numItems = m_indexTo - m_indexFrom; + + m_collapsedDistances.resize(numItems); + m_expandedDistances.resize(numItems); + QVarLengthArray<qint64> startsPerExpandedRow(m_model->expandedRowCount()); + QVarLengthArray<qint64> startsPerCollapsedRow(m_model->collapsedRowCount()); + memset(startsPerCollapsedRow.data(), 0xff, startsPerCollapsedRow.size()); + memset(startsPerExpandedRow.data(), 0xff, startsPerExpandedRow.size()); + for (int i = m_indexFrom; i < m_indexTo; ++i) { + // Add some "random" factor. Distances below 256ns cannot be properly displayed + // anyway and if all events have the same distance from one another, then we'd merge + // them all together otherwise. + qint64 start = startTime(m_model, m_parentState, i) + (i % 256); + qint64 end = endTime(m_model, m_parentState, i) + (i % 256); + if (start > end) { + m_collapsedDistances[i - m_indexFrom] = m_expandedDistances[i - m_indexFrom] = 0; + continue; + } + + qint64 &collapsedStart = startsPerCollapsedRow[m_model->collapsedRow(i)]; + m_collapsedDistances[i - m_indexFrom] = (collapsedStart != s_invalidTimestamp) ? + end - collapsedStart : std::numeric_limits<qint64>::max(); + collapsedStart = start; + + qint64 &expandedStart = startsPerExpandedRow[m_model->expandedRow(i)]; + m_expandedDistances[i - m_indexFrom] = (expandedStart != s_invalidTimestamp) ? + end - expandedStart : std::numeric_limits<qint64>::max(); + expandedStart = start; + } + + QVarLengthArray<qint64> sorted = m_collapsedDistances; + std::sort(sorted.begin(), sorted.end()); + m_minCollapsedDistance = sorted[numItems - s_maxNumItems]; + sorted = m_expandedDistances; + std::sort(sorted.begin(), sorted.end()); + m_minExpandedDistance = sorted[numItems - s_maxNumItems]; +} + +int NodeUpdater::updateVertices(TimelineItemsGeometry &geometry, + const QVarLengthArray<qint64> &distances, qint64 minDistance, + float itemTop, int i) const +{ + int vertices = 0; + if (geometry.isEmpty()) { + // We'll run another addVertices() on each row with content after the loop. + // Reserve some space for that. + vertices = TimelineItemsGeometry::VerticesForDifferentHeight; + geometry.nextNode(0, itemTop); + } else if (distances.isEmpty() || distances[i - m_indexFrom] > minDistance) { + vertices = geometry.addVertices(); + geometry.nextNode(0, itemTop); + } else { + geometry.updateCurrentNode(0, itemTop); + } + return vertices; +} + +void NodeUpdater::addEvent(TimelineItemsGeometry &geometry, + const QVarLengthArray<qint64> &distances, qint64 minDistance, + const NodeUpdater::ItemDescription &item, int i) const +{ + if (geometry.isEmpty()) { + geometry.nextNode(item.left, item.top, item.width, item.selectionId, item.red, + item.green, item.blue); + } else if (distances.isEmpty() || distances[i - m_indexFrom] > minDistance) { + geometry.addEvent(); + geometry.nextNode(item.left, item.top, item.width, item.selectionId, item.red, + item.green, item.blue); + } else { + geometry.updateCurrentNode(item.right, item.top); + } +} + +int NodeUpdater::updateNodes(const int from, const int to) const +{ + float defaultRowHeight = TimelineModel::defaultRowHeight(); + + QVector<TimelineItemsGeometry> expandedPerRow(m_model->expandedRowCount()); + QVector<TimelineItemsGeometry> collapsedPerRow(m_model->collapsedRowCount()); + + int collapsedVertices = 0; + int expandedVertices = 0; + int lastEvent = from; + for (;lastEvent < to + && collapsedVertices < TimelineItemsGeometry::maxVerticesPerNode + && expandedVertices < TimelineItemsGeometry::maxVerticesPerNode; + ++lastEvent) { + qint64 start = startTime(m_model, m_parentState, lastEvent); + qint64 end = endTime(m_model, m_parentState, lastEvent); + if (start > end) + continue; + + float itemTop = (1.0 - m_model->relativeHeight(lastEvent)) * defaultRowHeight; + + expandedVertices += updateVertices(expandedPerRow[m_model->expandedRow(lastEvent)], + m_expandedDistances, m_minExpandedDistance, itemTop, lastEvent); + collapsedVertices += updateVertices(collapsedPerRow[m_model->collapsedRow(lastEvent)], + m_collapsedDistances, m_minCollapsedDistance, itemTop, lastEvent); + } + + for (int i = 0, end = m_model->expandedRowCount(); i < end; ++i) { + TimelineItemsGeometry &row = expandedPerRow[i]; + if (row.currentNode.direction() != OpaqueColoredPoint2DWithSize::InvalidDirection) + row.addVertices(); + if (row.usedVertices > 0) { + row.allocate(&static_cast<TimelineExpandedRowNode *>( + m_state->expandedRow(i))->material); + m_state->expandedRow(i)->appendChildNode(row.node); + } + } + + for (int i = 0; i < m_model->collapsedRowCount(); ++i) { + TimelineItemsGeometry &row = collapsedPerRow[i]; + if (row.currentNode.direction() != OpaqueColoredPoint2DWithSize::InvalidDirection) + row.addVertices(); + if (row.usedVertices > 0) { + row.allocate(m_state->collapsedRowMaterial()); + m_state->collapsedRow(i)->appendChildNode(row.node); + } + } + + ItemDescription item; + for (int i = from; i < lastEvent; ++i) { + qint64 start = startTime(m_model, m_parentState, i); + qint64 end = endTime(m_model, m_parentState, i); + if (start > end) + continue; + + QRgb color = m_model->color(i); + item.red = qRed(color); + item.green = qGreen(color); + item.blue = qBlue(color); + + item.width = end > start ? (end - start) * m_parentState->scale() : + std::numeric_limits<float>::min(); + item.left = (start - m_parentState->start()) * m_parentState->scale(); + item.right = (end - m_parentState->start()) * m_parentState->scale(); + + // This has to be the exact same expression as above, to guarantee determinism. + item.top = (1.0 - m_model->relativeHeight(i)) * defaultRowHeight; + item.selectionId = m_model->selectionId(i); + + addEvent(expandedPerRow[m_model->expandedRow(i)], m_expandedDistances, + m_minExpandedDistance, item, i); + addEvent(collapsedPerRow[m_model->collapsedRow(i)], m_collapsedDistances, + m_minCollapsedDistance, item, i); + } + + for (int i = 0, end = m_model->expandedRowCount(); i < end; ++i) { + TimelineItemsGeometry &row = expandedPerRow[i]; + if (row.currentNode.direction() != OpaqueColoredPoint2DWithSize::InvalidDirection) + row.addEvent(); + } + + for (int i = 0, end = m_model->collapsedRowCount(); i < end; ++i) { + TimelineItemsGeometry &row = collapsedPerRow[i]; + if (row.currentNode.direction() != OpaqueColoredPoint2DWithSize::InvalidDirection) + row.addEvent(); + } + + return lastEvent; +} + +void NodeUpdater::run() +{ + if (m_indexTo - m_indexFrom > s_maxNumItems) + calculateDistances(); + + if (m_state->indexFrom() < m_state->indexTo()) { + if (m_indexFrom < m_state->indexFrom()) { + for (int i = m_indexFrom; i < m_state->indexFrom();) + i = updateNodes(i, m_state->indexFrom()); + } + if (m_indexTo > m_state->indexTo()) { + for (int i = m_state->indexTo(); i < m_indexTo;) + i = updateNodes(i, m_indexTo); + } + } else { + for (int i = m_indexFrom; i < m_indexTo;) + i = updateNodes(i, m_indexTo); + } +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelineitemsrenderpass.h b/src/libs/tracing/timelineitemsrenderpass.h new file mode 100644 index 00000000000..597b1fa98d4 --- /dev/null +++ b/src/libs/tracing/timelineitemsrenderpass.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineabstractrenderer.h" +#include "timelinerenderpass.h" +#include <QSGMaterial> + +namespace Timeline { + +class TimelineItemsMaterial : public QSGMaterial +{ +public: + TimelineItemsMaterial(); + QVector2D scale() const; + void setScale(QVector2D scale); + + float selectedItem() const; + void setSelectedItem(float selectedItem); + + QColor selectionColor() const; + void setSelectionColor(QColor selectionColor); + + QSGMaterialType *type() const; + QSGMaterialShader *createShader() const; + +private: + QVector2D m_scale; + float m_selectedItem; + QColor m_selectionColor; +}; + +class OpaqueColoredPoint2DWithSize +{ +public: + enum Direction { + InvalidDirection, + TopToBottom, + BottomToTop, + MaximumDirection + }; + + void set(float nx, float ny, float nw, float nh, float nid, uchar nr, uchar ng, uchar nb, + uchar d); + + float top() const; + void update(float nr, float ny); + Direction direction() const; + + void setBottomLeft(const OpaqueColoredPoint2DWithSize *master); + void setBottomRight(const OpaqueColoredPoint2DWithSize *master); + void setTopLeft(const OpaqueColoredPoint2DWithSize *master); + void setTopRight(const OpaqueColoredPoint2DWithSize *master); + + static const QSGGeometry::AttributeSet &attributes(); + static OpaqueColoredPoint2DWithSize *fromVertexData(QSGGeometry *geometry); + +private: + float x, y, w, h, id; + unsigned char r, g, b, a; + + void setCommon(const OpaqueColoredPoint2DWithSize *master); + void setLeft(const OpaqueColoredPoint2DWithSize *master); + void setRight(const OpaqueColoredPoint2DWithSize *master); + void setTop(const OpaqueColoredPoint2DWithSize *master); + void setBottom(const OpaqueColoredPoint2DWithSize *master); +}; + +class TRACING_EXPORT TimelineItemsRenderPass : public TimelineRenderPass +{ +public: + static const TimelineItemsRenderPass *instance(); + State *update(const TimelineAbstractRenderer *renderer, const TimelineRenderState *parentState, + State *state, int firstIndex, int lastIndex, bool stateChanged, + float spacing) const; +protected: + TimelineItemsRenderPass(); +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinemodel.cpp b/src/libs/tracing/timelinemodel.cpp new file mode 100644 index 00000000000..e2f28e18497 --- /dev/null +++ b/src/libs/tracing/timelinemodel.cpp @@ -0,0 +1,718 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinemodel.h" +#include "timelinemodel_p.h" +#include "timelinemodelaggregator.h" +#include "timelineitemsrenderpass.h" +#include "timelineselectionrenderpass.h" +#include "timelinenotesrenderpass.h" + +#include <utils/qtcassert.h> +#include <QLinkedList> + +namespace Timeline { + +/*! + \class Timeline::TimelineModel + \brief The TimelineModel class provides a sorted model for timeline data. + + The TimelineModel lets you keep range data sorted by both start and end times, so that + visible ranges can easily be computed. The only precondition for that to work is that the ranges + must be perfectly nested. A "parent" range of a range R is defined as a range for which the + start time is earlier than R's start time and the end time is later than R's end time. A set + of ranges is perfectly nested if all parent ranges of any given range have a common parent + range. Mind that you can always make that happen by defining a range that spans the whole + available time span. That, however, will make any code that uses firstIndex() and lastIndex() + for selecting subsets of the model always select all of it. + + \note Indices returned from the various methods are only valid until a new range is inserted + before them. Inserting a new range before a given index moves the range pointed to by the + index by one. Incrementing the index by one will make it point to the item again. +*/ + +/*! + Compute all ranges' parents. + \sa firstIndex() +*/ +void TimelineModel::computeNesting() +{ + Q_D(TimelineModel); + QLinkedList<int> parents; + for (int range = 0; range != count(); ++range) { + TimelineModelPrivate::Range ¤t = d->ranges[range]; + for (QLinkedList<int>::iterator parentIt = parents.begin();;) { + if (parentIt == parents.end()) { + parents.append(range); + break; + } + + TimelineModelPrivate::Range &parent = d->ranges[*parentIt]; + qint64 parentEnd = parent.start + parent.duration; + if (parentEnd < current.start) { + // We've completely passed the parent. Remove it. + parentIt = parents.erase(parentIt); + } else if (parentEnd >= current.start + current.duration) { + // Current range is completely inside the parent range: no need to insert + current.parent = (parent.parent == -1 ? *parentIt : parent.parent); + break; + } else if (parent.start == current.start) { + // The parent range starts at the same time but ends before the current range. + // We could switch them but that would violate the order requirements. When + // searching for ranges between two timestamps we'd skip the ranges between the + // current range and the parent range if the start timestamp points into the parent + // range. firstIndex() would then return the current range, which has an id greater + // than the parent. The parent could not be found then. To deal with this corner + // case, we assign the parent the "wrong" way around, so that on firstIndex() we + // always end up with the smallest id of any ranges starting at the same time. + + // The other way to deal with this would be fixing up the ordering on insert. In + // fact we do that on insertStart(). + // However, in order to rely on this we would also have to move the start index if + // on insertEnd() it turns out that the range just being ended is shorter than a + // previous one starting at the same time. We don't want to do that as client code + // could not find out about the changes in the IDs for range starts then. + + current.parent = *parentIt; + parents.append(range); + break; + } else { + ++parentIt; + } + } + } +} + +int TimelineModel::collapsedRowCount() const +{ + Q_D(const TimelineModel); + return d->collapsedRowCount; +} + +void TimelineModel::setCollapsedRowCount(int rows) +{ + Q_D(TimelineModel); + if (d->collapsedRowCount != rows) { + d->collapsedRowCount = rows; + emit collapsedRowCountChanged(); + if (!d->expanded) { + emit rowCountChanged(); + emit heightChanged(); // collapsed rows have a fixed size + } + } +} + +int TimelineModel::expandedRowCount() const +{ + Q_D(const TimelineModel); + return d->expandedRowCount; +} + +void TimelineModel::setExpandedRowCount(int rows) +{ + Q_D(TimelineModel); + if (d->expandedRowCount != rows) { + int prevHeight = height(); + if (d->rowOffsets.length() > rows) + d->rowOffsets.resize(rows); + d->expandedRowCount = rows; + emit expandedRowCountChanged(); + if (d->expanded) { + emit rowCountChanged(); + if (height() != prevHeight) + emit heightChanged(); + } + } +} + +int TimelineModel::row(int index) const +{ + return expanded() ? expandedRow(index) : collapsedRow(index); +} + +TimelineModel::TimelineModelPrivate::TimelineModelPrivate(int modelId) : + modelId(modelId), expanded(false), hidden(false), + expandedRowCount(1), collapsedRowCount(1) +{ +} + +TimelineModel::TimelineModel(TimelineModelAggregator *parent) : + QObject(parent), d_ptr(new TimelineModelPrivate(parent->generateModelId())) +{ + connect(this, &TimelineModel::contentChanged, this, &TimelineModel::labelsChanged); + connect(this, &TimelineModel::contentChanged, this, &TimelineModel::detailsChanged); +} + +TimelineModel::~TimelineModel() +{ + Q_D(TimelineModel); + delete d; +} + +bool TimelineModel::isEmpty() const +{ + return count() == 0; +} + +int TimelineModel::modelId() const +{ + Q_D(const TimelineModel); + return d->modelId; +} + +int TimelineModel::collapsedRowHeight(int rowNumber) const +{ + Q_UNUSED(rowNumber); + return TimelineModelPrivate::DefaultRowHeight; +} + +int TimelineModel::collapsedRowOffset(int rowNumber) const +{ + return rowNumber * TimelineModelPrivate::DefaultRowHeight; +} + +int TimelineModel::expandedRowHeight(int rowNumber) const +{ + Q_D(const TimelineModel); + if (d->rowOffsets.size() > rowNumber) + return d->rowOffsets[rowNumber] - (rowNumber > 0 ? d->rowOffsets[rowNumber - 1] : 0); + return TimelineModelPrivate::DefaultRowHeight; +} + +int TimelineModel::expandedRowOffset(int rowNumber) const +{ + Q_D(const TimelineModel); + if (rowNumber == 0) + return 0; + + if (d->rowOffsets.size() >= rowNumber) + return d->rowOffsets[rowNumber - 1]; + if (!d->rowOffsets.empty()) + return d->rowOffsets.last() + (rowNumber - d->rowOffsets.size()) * + TimelineModelPrivate::DefaultRowHeight; + return rowNumber * TimelineModelPrivate::DefaultRowHeight; +} + +void TimelineModel::setExpandedRowHeight(int rowNumber, int height) +{ + Q_D(TimelineModel); + if (height < TimelineModelPrivate::DefaultRowHeight) + height = TimelineModelPrivate::DefaultRowHeight; + + int nextOffset = d->rowOffsets.empty() ? 0 : d->rowOffsets.last(); + while (d->rowOffsets.size() <= rowNumber) + d->rowOffsets << (nextOffset += TimelineModelPrivate::DefaultRowHeight); + int difference = height - d->rowOffsets[rowNumber] + + (rowNumber > 0 ? d->rowOffsets[rowNumber - 1] : 0); + if (difference != 0) { + for (int offsetRow = rowNumber; offsetRow < d->rowOffsets.size(); ++offsetRow) { + d->rowOffsets[offsetRow] += difference; + } + emit expandedRowHeightChanged(rowNumber, height); + if (d->expanded) + emit heightChanged(); + } +} + +int TimelineModel::rowOffset(int rowNumber) const +{ + return expanded() ? expandedRowOffset(rowNumber) : collapsedRowOffset(rowNumber); +} + +int TimelineModel::rowHeight(int rowNumber) const +{ + return expanded() ? expandedRowHeight(rowNumber) : collapsedRowHeight(rowNumber); +} + +int TimelineModel::height() const +{ + Q_D(const TimelineModel); + if (d->hidden || isEmpty()) + return 0; + + if (!d->expanded) + return collapsedRowCount() * TimelineModelPrivate::DefaultRowHeight; + if (d->rowOffsets.empty()) + return expandedRowCount() * TimelineModelPrivate::DefaultRowHeight; + + return d->rowOffsets.last() + (expandedRowCount() - d->rowOffsets.size()) * + TimelineModelPrivate::DefaultRowHeight; +} + +/*! + Returns the number of ranges in the model. +*/ +int TimelineModel::count() const +{ + Q_D(const TimelineModel); + return d->ranges.count(); +} + +qint64 TimelineModel::duration(int index) const +{ + Q_D(const TimelineModel); + return d->ranges[index].duration; +} + +qint64 TimelineModel::startTime(int index) const +{ + Q_D(const TimelineModel); + return d->ranges[index].start; +} + +qint64 TimelineModel::endTime(int index) const +{ + Q_D(const TimelineModel); + return d->ranges[index].start + d->ranges[index].duration; +} + +/*! + Returns the type ID of the event with event ID \a index. The type ID is a globally valid ID + which can be used to communicate meta information about events to other parts of the program. By + default it is -1, which means there is no global type information about the event. + */ +int TimelineModel::typeId(int index) const +{ + Q_UNUSED(index) + return -1; +} + +/*! + Looks up the first range with an end time later than the given time and + returns its parent's index. If no such range is found, it returns -1. If there + is no parent, it returns the found range's index. The parent of a range is the + range with the earliest start time that completely covers the child range. + "Completely covers" means: + parent.startTime <= child.startTime && parent.endTime >= child.endTime +*/ +int TimelineModel::firstIndex(qint64 startTime) const +{ + Q_D(const TimelineModel); + int index = d->firstIndexNoParents(startTime); + if (index == -1) + return -1; + int parent = d->ranges[index].parent; + return parent == -1 ? index : parent; +} + +/*! + Of all indexes of ranges starting at the same time as the first range with an end time later + than the specified \a startTime returns the lowest one. If no such range is found, it returns + -1. +*/ +int TimelineModel::TimelineModelPrivate::firstIndexNoParents(qint64 startTime) const +{ + // in the "endtime" list, find the first event that ends after startTime + + // lowerBound() cannot deal with empty lists, and it never finds the last element. + if (endTimes.isEmpty() || endTimes.last().end <= startTime) + return -1; + + // lowerBound() never returns "invalid", so handle this manually. + if (endTimes.first().end > startTime) + return endTimes.first().startIndex; + + return endTimes[lowerBound(endTimes, startTime) + 1].startIndex; +} + +/*! + Looks up the last range with a start time earlier than the specified \a endTime and + returns its index. If no such range is found, it returns -1. +*/ +int TimelineModel::lastIndex(qint64 endTime) const +{ + Q_D(const TimelineModel); + // in the "starttime" list, find the last event that starts before endtime + + // lowerBound() never returns "invalid", so handle this manually. + if (d->ranges.isEmpty() || d->ranges.first().start >= endTime) + return -1; + + // lowerBound() never finds the last element. + if (d->ranges.last().start < endTime) + return d->ranges.count() - 1; + + return d->lowerBound(d->ranges, endTime); +} + +/*! + Looks up a range between the last one that starts before, and the first one that ends after the + given timestamp. This might not be a range that covers the timestamp, even if one exists. + However, it's likely that the range is close to the given timestamp. + */ +int TimelineModel::bestIndex(qint64 timestamp) const +{ + Q_D(const TimelineModel); + + if (d->ranges.isEmpty()) + return -1; + + // Last range that starts before timestamp (without parents) + const int start = d->ranges.last().start < timestamp + ? d->ranges.count() - 1 : d->lowerBound(d->ranges, timestamp); + + int endTimeIndex; + if (d->endTimes.first().end >= timestamp) + endTimeIndex = 0; + else if (d->endTimes.last().end < timestamp) + endTimeIndex = d->endTimes.count() - 1; + else + endTimeIndex = d->lowerBound(d->endTimes, timestamp) + 1; + + // First range that ends after + const int end = d->endTimes[endTimeIndex].startIndex; + + // Best is probably between those + return (start + end) / 2; +} + +int TimelineModel::parentIndex(int index) const +{ + Q_D(const TimelineModel); + return d->ranges[index].parent; +} + +QVariantMap TimelineModel::location(int index) const +{ + Q_UNUSED(index); + QVariantMap map; + return map; +} + +/*! + Returns \c true if this model can contain events of global type ID \a typeIndex. Otherwise + returns \c false. The base model does not know anything about type IDs and always returns + \c false. You should override this method if you implement \l typeId(). + */ +bool TimelineModel::handlesTypeId(int typeIndex) const +{ + Q_UNUSED(typeIndex); + return false; +} + +float TimelineModel::relativeHeight(int index) const +{ + Q_UNUSED(index); + return 1.0f; +} + +qint64 TimelineModel::rowMinValue(int rowNumber) const +{ + Q_UNUSED(rowNumber); + return 0; +} + +qint64 TimelineModel::rowMaxValue(int rowNumber) const +{ + Q_UNUSED(rowNumber); + return 0; +} + +int TimelineModel::defaultRowHeight() +{ + return TimelineModelPrivate::DefaultRowHeight; +} + +QList<const TimelineRenderPass *> TimelineModel::supportedRenderPasses() const +{ + QList<const TimelineRenderPass *> passes; + passes << TimelineItemsRenderPass::instance() + << TimelineSelectionRenderPass::instance() + << TimelineNotesRenderPass::instance(); + return passes; +} + +QRgb TimelineModel::colorBySelectionId(int index) const +{ + return colorByHue(selectionId(index) * TimelineModelPrivate::SelectionIdHueMultiplier); +} + +QRgb TimelineModel::colorByFraction(double fraction) const +{ + return colorByHue(fraction * TimelineModelPrivate::FractionHueMultiplier + + TimelineModelPrivate::FractionHueMininimum); +} + +QRgb TimelineModel::colorByHue(int hue) const +{ + return TimelineModelPrivate::hueTable[hue]; +} + +/*! + Inserts the range defined by \a duration and \a selectionId at the specified \a startTime and + returns its index. The \a selectionId determines the selection group the new event belongs to. + \sa selectionId() +*/ +int TimelineModel::insert(qint64 startTime, qint64 duration, int selectionId) +{ + Q_D(TimelineModel); + /* Doing insert-sort here is preferable as most of the time the times will actually be + * presorted in the right way. So usually this will just result in appending. */ + int index = d->insertStart(TimelineModelPrivate::Range(startTime, duration, selectionId)); + if (index < d->ranges.size() - 1) + d->incrementStartIndices(index); + d->insertEnd(TimelineModelPrivate::RangeEnd(index, startTime + duration)); + return index; +} + +/*! + Inserts the specified \a selectionId as range start at the specified \a startTime and returns + its index. The range end is not set. The \a selectionId determines the selection group the new + event belongs to. + \sa selectionId() +*/ +int TimelineModel::insertStart(qint64 startTime, int selectionId) +{ + Q_D(TimelineModel); + int index = d->insertStart(TimelineModelPrivate::Range(startTime, 0, selectionId)); + if (index < d->ranges.size() - 1) + d->incrementStartIndices(index); + return index; +} + +/*! + Adds the range \a duration at the specified start \a index. +*/ +void TimelineModel::insertEnd(int index, qint64 duration) +{ + Q_D(TimelineModel); + d->ranges[index].duration = duration; + d->insertEnd(TimelineModelPrivate::RangeEnd(index, d->ranges[index].start + duration)); +} + +bool TimelineModel::expanded() const +{ + Q_D(const TimelineModel); + return d->expanded; +} + +void TimelineModel::setExpanded(bool expanded) +{ + Q_D(TimelineModel); + if (expanded != d->expanded) { + int prevHeight = height(); + d->expanded = expanded; + emit expandedChanged(); + if (prevHeight != height()) + emit heightChanged(); + if (d->collapsedRowCount != d->expandedRowCount) + emit rowCountChanged(); + } +} + +bool TimelineModel::hidden() const +{ + Q_D(const TimelineModel); + return d->hidden; +} + +void TimelineModel::setHidden(bool hidden) +{ + Q_D(TimelineModel); + if (hidden != d->hidden) { + int prevHeight = height(); + d->hidden = hidden; + emit hiddenChanged(); + if (height() != prevHeight) + emit heightChanged(); + } +} + +void TimelineModel::setDisplayName(const QString &displayName) +{ + Q_D(TimelineModel); + if (d->displayName != displayName) { + d->displayName = displayName; + emit displayNameChanged(); + } +} + +QString TimelineModel::displayName() const +{ + Q_D(const TimelineModel); + return d->displayName; +} + +int TimelineModel::rowCount() const +{ + Q_D(const TimelineModel); + return d->expanded ? d->expandedRowCount : d->collapsedRowCount; +} + +QRgb TimelineModel::color(int index) const +{ + Q_UNUSED(index); + return QRgb(); +} + +QVariantList TimelineModel::labels() const +{ + return QVariantList(); +} + +QVariantMap TimelineModel::details(int index) const +{ + Q_UNUSED(index); + return QVariantMap(); +} + +int TimelineModel::expandedRow(int index) const +{ + Q_UNUSED(index); + return 0; +} + +int TimelineModel::collapsedRow(int index) const +{ + Q_UNUSED(index); + return 0; +} + +/*! + Returns the ID of the selection group the event with event ID \a index belongs to. Selection + groups are local to the model and the model can arbitrarily assign events to selection groups + when inserting them. + If one event from a selection group is selected, all visible other events from the same + selection group are highlighted. Rows are expected to correspond to selection IDs when the view + is expanded. + */ +int TimelineModel::selectionId(int index) const +{ + Q_D(const TimelineModel); + return d->ranges[index].selectionId; +} + +void TimelineModel::clear() +{ + Q_D(TimelineModel); + bool hadRowHeights = !d->rowOffsets.empty(); + bool wasEmpty = isEmpty(); + setExpandedRowCount(1); + setCollapsedRowCount(1); + setExpanded(false); + setHidden(false); + d->rowOffsets.clear(); + d->ranges.clear(); + d->endTimes.clear(); + if (hadRowHeights) + emit expandedRowHeightChanged(-1, -1); + if (!wasEmpty) { + emit contentChanged(); + emit heightChanged(); + emit labelsChanged(); + emit detailsChanged(); + } +} + +int TimelineModel::nextItemBySelectionId(int selectionId, qint64 time, int currentItem) const +{ + Q_D(const TimelineModel); + return d->nextItemById([d, selectionId](int index) { + return d->ranges[index].selectionId == selectionId; + }, time, currentItem); +} + +int TimelineModel::nextItemByTypeId(int requestedTypeId, qint64 time, int currentItem) const +{ + Q_D(const TimelineModel); + return d->nextItemById([this, requestedTypeId](int index) { + return typeId(index) == requestedTypeId; + }, time, currentItem); +} + +int TimelineModel::prevItemBySelectionId(int selectionId, qint64 time, int currentItem) const +{ + Q_D(const TimelineModel); + return d->prevItemById([d, selectionId](int index) { + return d->ranges[index].selectionId == selectionId; + }, time, currentItem); +} + +int TimelineModel::prevItemByTypeId(int requestedTypeId, qint64 time, int currentItem) const +{ + Q_D(const TimelineModel); + return d->prevItemById([this, requestedTypeId](int index) { + return typeId(index) == requestedTypeId; + }, time, currentItem); +} + +HueLookupTable::HueLookupTable() { + for (int hue = 0; hue < 360; ++hue) { + table[hue] = QColor::fromHsl(hue, TimelineModel::TimelineModelPrivate::Saturation, + TimelineModel::TimelineModelPrivate::Lightness).rgb(); + } +} + +const HueLookupTable TimelineModel::TimelineModelPrivate::hueTable; + +int TimelineModel::TimelineModelPrivate::nextItemById(std::function<bool(int)> matchesId, + qint64 time, int currentItem) const +{ + if (ranges.empty()) + return -1; + + int ndx = -1; + if (currentItem == -1) + ndx = firstIndexNoParents(time); + else + ndx = currentItem + 1; + + if (ndx < 0 || ndx >= ranges.count()) + ndx = 0; + int startIndex = ndx; + do { + if (matchesId(ndx)) + return ndx; + ndx = (ndx + 1) % ranges.count(); + } while (ndx != startIndex); + return -1; +} + +int TimelineModel::TimelineModelPrivate::prevItemById(std::function<bool(int)> matchesId, + qint64 time, int currentItem) const +{ + if (ranges.empty()) + return -1; + + int ndx = -1; + if (currentItem == -1) + ndx = firstIndexNoParents(time); + else + ndx = currentItem - 1; + if (ndx < 0) + ndx = ranges.count() - 1; + int startIndex = ndx; + do { + if (matchesId(ndx)) + return ndx; + if (--ndx < 0) + ndx = ranges.count()-1; + } while (ndx != startIndex); + return -1; +} + +} // namespace Timeline + +#include "moc_timelinemodel.cpp" diff --git a/src/libs/tracing/timelinemodel.h b/src/libs/tracing/timelinemodel.h new file mode 100644 index 00000000000..9bf3f062695 --- /dev/null +++ b/src/libs/tracing/timelinemodel.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include "timelinerenderpass.h" +#include <QVariant> +#include <QColor> + +namespace Timeline { +class TimelineModelAggregator; + +class TRACING_EXPORT TimelineModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int modelId READ modelId CONSTANT) + Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(bool empty READ isEmpty NOTIFY contentChanged) + Q_PROPERTY(bool hidden READ hidden WRITE setHidden NOTIFY hiddenChanged) + Q_PROPERTY(bool expanded READ expanded WRITE setExpanded NOTIFY expandedChanged) + Q_PROPERTY(int height READ height NOTIFY heightChanged) + Q_PROPERTY(int expandedRowCount READ expandedRowCount NOTIFY expandedRowCountChanged) + Q_PROPERTY(int collapsedRowCount READ collapsedRowCount NOTIFY collapsedRowCountChanged) + Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged) + Q_PROPERTY(QVariantList labels READ labels NOTIFY labelsChanged) + Q_PROPERTY(int count READ count NOTIFY contentChanged) + Q_PROPERTY(int defaultRowHeight READ defaultRowHeight CONSTANT) + +public: + class TimelineModelPrivate; + + TimelineModel(TimelineModelAggregator *parent); + ~TimelineModel(); + + // Methods implemented by the abstract model itself + bool isEmpty() const; + int modelId() const; + + Q_INVOKABLE int collapsedRowHeight(int rowNumber) const; + Q_INVOKABLE int expandedRowHeight(int rowNumber) const; + Q_INVOKABLE int rowHeight(int rowNumber) const; + Q_INVOKABLE void setExpandedRowHeight(int rowNumber, int height); + + Q_INVOKABLE int collapsedRowOffset(int rowNumber) const; + Q_INVOKABLE int expandedRowOffset(int rowNumber) const; + Q_INVOKABLE int rowOffset(int rowNumber) const; + + int height() const; + int count() const; + Q_INVOKABLE qint64 duration(int index) const; + Q_INVOKABLE qint64 startTime(int index) const; + Q_INVOKABLE qint64 endTime(int index) const; + Q_INVOKABLE int selectionId(int index) const; + + int firstIndex(qint64 startTime) const; + int lastIndex(qint64 endTime) const; + int bestIndex(qint64 timestamp) const; + int parentIndex(int index) const; + + bool expanded() const; + bool hidden() const; + void setExpanded(bool expanded); + void setHidden(bool hidden); + void setDisplayName(const QString &displayName); + QString displayName() const; + int expandedRowCount() const; + int collapsedRowCount() const; + int rowCount() const; + + // Methods which can optionally be implemented by child models. + Q_INVOKABLE virtual QRgb color(int index) const; + virtual QVariantList labels() const; + Q_INVOKABLE virtual QVariantMap details(int index) const; + Q_INVOKABLE virtual int expandedRow(int index) const; + Q_INVOKABLE virtual int collapsedRow(int index) const; + Q_INVOKABLE int row(int index) const; + + // returned map should contain "file", "line", "column" properties, or be empty + Q_INVOKABLE virtual QVariantMap location(int index) const; + Q_INVOKABLE virtual int typeId(int index) const; + Q_INVOKABLE virtual bool handlesTypeId(int typeId) const; + Q_INVOKABLE virtual float relativeHeight(int index) const; + Q_INVOKABLE virtual qint64 rowMinValue(int rowNumber) const; + Q_INVOKABLE virtual qint64 rowMaxValue(int rowNumber) const; + + Q_INVOKABLE int nextItemBySelectionId(int selectionId, qint64 time, int currentItem) const; + Q_INVOKABLE int nextItemByTypeId(int typeId, qint64 time, int currentItem) const; + Q_INVOKABLE int prevItemBySelectionId(int selectionId, qint64 time, int currentItem) const; + Q_INVOKABLE int prevItemByTypeId(int typeId, qint64 time, int currentItem) const; + + static int defaultRowHeight(); + virtual QList<const TimelineRenderPass *> supportedRenderPasses() const; + +signals: + void expandedChanged(); + void hiddenChanged(); + void expandedRowHeightChanged(int row, int height); + void contentChanged(); + void heightChanged(); + void expandedRowCountChanged(); + void collapsedRowCountChanged(); + void rowCountChanged(); + void displayNameChanged(); + void labelsChanged(); + void detailsChanged(); + +protected: + QRgb colorBySelectionId(int index) const; + QRgb colorByFraction(double fraction) const; + QRgb colorByHue(int hue) const; + + int insert(qint64 startTime, qint64 duration, int selectionId); + int insertStart(qint64 startTime, int selectionId); + void insertEnd(int index, qint64 duration); + void computeNesting(); + + void setCollapsedRowCount(int rows); + void setExpandedRowCount(int rows); + + virtual void clear(); + +private: + TimelineModelPrivate *d_ptr; + Q_DECLARE_PRIVATE(TimelineModel) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinemodel_p.h b/src/libs/tracing/timelinemodel_p.h new file mode 100644 index 00000000000..53136571be2 --- /dev/null +++ b/src/libs/tracing/timelinemodel_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinemodel.h" +#include <functional> + +namespace Timeline { + +struct HueLookupTable { + QRgb table[360]; + HueLookupTable(); + + QRgb operator[](int hue) const { return table[hue % 360]; } +}; + +class TRACING_EXPORT TimelineModel::TimelineModelPrivate { +public: + + static const HueLookupTable hueTable; + static const int DefaultRowHeight = 30; + + enum BoxColorProperties { + SelectionIdHueMultiplier = 25, + FractionHueMultiplier = 96, + FractionHueMininimum = 10, + Saturation = 150, + Lightness = 166 + }; + + struct Range { + Range() : start(-1), duration(-1), selectionId(-1), parent(-1) {} + Range(qint64 start, qint64 duration, int selectionId) : + start(start), duration(duration), selectionId(selectionId), parent(-1) {} + qint64 start; + qint64 duration; + int selectionId; + int parent; + inline qint64 timestamp() const {return start;} + }; + + struct RangeEnd { + RangeEnd() : startIndex(-1), end(-1) {} + RangeEnd(int startIndex, qint64 end) : + startIndex(startIndex), end(end) {} + int startIndex; + qint64 end; + inline qint64 timestamp() const {return end;} + }; + + TimelineModelPrivate(int modelId); + + int firstIndexNoParents(qint64 startTime) const; + + void incrementStartIndices(int index) + { + for (RangeEnd &endTime : endTimes) { + if (endTime.startIndex >= index) + ++(endTime.startIndex); + } + } + + inline int insertStart(const Range &start) + { + for (int i = ranges.count();;) { + if (i == 0) { + ranges.prepend(start); + return 0; + } + const Range &range = ranges[--i]; + if (range.start < start.start || (range.start == start.start && + range.duration >= start.duration)) { + ranges.insert(++i, start); + return i; + } + } + } + + inline int insertEnd(const RangeEnd &end) + { + for (int i = endTimes.count();;) { + if (i == 0) { + endTimes.prepend(end); + return 0; + } + if (endTimes[--i].end <= end.end) { + endTimes.insert(++i, end); + return i; + } + } + } + + template<typename RangeDelimiter> + static inline int lowerBound(const QVector<RangeDelimiter> &container, qint64 time) + { + int fromIndex = 0; + int toIndex = container.count() - 1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (container[midIndex].timestamp() < time) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + return fromIndex; + } + + int prevItemById(std::function<bool(int)> matchesId, qint64 time, int currentSelected) const; + int nextItemById(std::function<bool(int)> matchesId, qint64 time, int currentSelected) const; + + QVector<Range> ranges; + QVector<RangeEnd> endTimes; + + QVector<int> rowOffsets; + const int modelId; + QString displayName; + + bool expanded; + bool hidden; + int expandedRowCount; + int collapsedRowCount; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinemodelaggregator.cpp b/src/libs/tracing/timelinemodelaggregator.cpp new file mode 100644 index 00000000000..30636356aec --- /dev/null +++ b/src/libs/tracing/timelinemodelaggregator.cpp @@ -0,0 +1,288 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinemodelaggregator.h" + +#include "timelinemodel.h" +#include "timelinenotesmodel.h" + +#include <utils/algorithm.h> + +#include <QStringList> +#include <QVariant> + +namespace Timeline { + +class TimelineModelAggregator::TimelineModelAggregatorPrivate { +public: + QList <TimelineModel *> modelList; + TimelineNotesModel *notesModel; + int currentModelId = 0; +}; + +TimelineModelAggregator::TimelineModelAggregator(TimelineNotesModel *notes, QObject *parent) + : QObject(parent), d_ptr(new TimelineModelAggregatorPrivate) +{ + Q_D(TimelineModelAggregator); + d->notesModel = notes; +} + +TimelineModelAggregator::~TimelineModelAggregator() +{ + Q_D(TimelineModelAggregator); + delete d; +} + +int TimelineModelAggregator::height() const +{ + Q_D(const TimelineModelAggregator); + return modelOffset(d->modelList.length()); +} + +int TimelineModelAggregator::generateModelId() +{ + Q_D(TimelineModelAggregator); + return d->currentModelId++; +} + +void TimelineModelAggregator::addModel(TimelineModel *m) +{ + Q_D(TimelineModelAggregator); + d->modelList << m; + connect(m, &TimelineModel::heightChanged, this, &TimelineModelAggregator::heightChanged); + if (d->notesModel) + d->notesModel->addTimelineModel(m); + emit modelsChanged(); + if (m->height() != 0) + emit heightChanged(); +} + +void TimelineModelAggregator::setModels(const QVariantList &models) +{ + Q_D(TimelineModelAggregator); + + QList<TimelineModel *> timelineModels = Utils::transform(models, [](const QVariant &model) { + return qvariant_cast<TimelineModel *>(model); + }); + + if (d->modelList == timelineModels) + return; + + int prevHeight = height(); + foreach (TimelineModel *m, d->modelList) { + disconnect(m, &TimelineModel::heightChanged, this, &TimelineModelAggregator::heightChanged); + if (d->notesModel) + d->notesModel->removeTimelineModel(m); + } + + d->modelList = timelineModels; + foreach (TimelineModel *m, timelineModels) { + connect(m, &TimelineModel::heightChanged, this, &TimelineModelAggregator::heightChanged); + if (d->notesModel) + d->notesModel->addTimelineModel(m); + } + emit modelsChanged(); + if (height() != prevHeight) + emit heightChanged(); +} + +const TimelineModel *TimelineModelAggregator::model(int modelIndex) const +{ + Q_D(const TimelineModelAggregator); + return d->modelList[modelIndex]; +} + +QVariantList TimelineModelAggregator::models() const +{ + Q_D(const TimelineModelAggregator); + QVariantList ret; + foreach (TimelineModel *model, d->modelList) + ret << QVariant::fromValue(model); + return ret; +} + +TimelineNotesModel *TimelineModelAggregator::notes() const +{ + Q_D(const TimelineModelAggregator); + return d->notesModel; +} + +void TimelineModelAggregator::clear() +{ + Q_D(TimelineModelAggregator); + int prevHeight = height(); + d->modelList.clear(); + if (d->notesModel) + d->notesModel->clear(); + emit modelsChanged(); + if (height() != prevHeight) + emit heightChanged(); +} + +int TimelineModelAggregator::modelOffset(int modelIndex) const +{ + Q_D(const TimelineModelAggregator); + int ret = 0; + for (int i = 0; i < modelIndex; ++i) + ret += d->modelList[i]->height(); + return ret; +} + +int TimelineModelAggregator::modelCount() const +{ + Q_D(const TimelineModelAggregator); + return d->modelList.count(); +} + +int TimelineModelAggregator::modelIndexById(int modelId) const +{ + Q_D(const TimelineModelAggregator); + for (int i = 0; i < d->modelList.count(); ++i) { + if (d->modelList.at(i)->modelId() == modelId) + return i; + } + return -1; +} + +QVariantMap TimelineModelAggregator::nextItem(int selectedModel, int selectedItem, + qint64 time) const +{ + if (selectedItem != -1) + time = model(selectedModel)->startTime(selectedItem); + + QVarLengthArray<int> itemIndexes(modelCount()); + for (int i = 0; i < modelCount(); i++) { + const TimelineModel *currentModel = model(i); + if (currentModel->count() > 0) { + if (selectedModel == i) { + itemIndexes[i] = (selectedItem + 1) % currentModel->count(); + } else { + if (currentModel->startTime(0) >= time) + itemIndexes[i] = 0; + else + itemIndexes[i] = (currentModel->lastIndex(time) + 1) % currentModel->count(); + + if (i < selectedModel && currentModel->startTime(itemIndexes[i]) == time) + itemIndexes[i] = (itemIndexes[i] + 1) % currentModel->count(); + } + } else { + itemIndexes[i] = -1; + } + } + + int candidateModelIndex = -1; + qint64 candidateStartTime = std::numeric_limits<qint64>::max(); + for (int i = 0; i < modelCount(); i++) { + if (itemIndexes[i] == -1) + continue; + qint64 newStartTime = model(i)->startTime(itemIndexes[i]); + if (newStartTime < candidateStartTime && + (newStartTime > time || (newStartTime == time && i > selectedModel))) { + candidateStartTime = newStartTime; + candidateModelIndex = i; + } + } + + int itemIndex; + if (candidateModelIndex != -1) { + itemIndex = itemIndexes[candidateModelIndex]; + } else { + itemIndex = -1; + candidateStartTime = std::numeric_limits<qint64>::max(); + for (int i = 0; i < modelCount(); i++) { + const TimelineModel *currentModel = model(i); + if (currentModel->count() > 0 && currentModel->startTime(0) < candidateStartTime) { + candidateModelIndex = i; + itemIndex = 0; + candidateStartTime = currentModel->startTime(0); + } + } + } + + QVariantMap ret; + ret.insert(QLatin1String("model"), candidateModelIndex); + ret.insert(QLatin1String("item"), itemIndex); + return ret; +} + +QVariantMap TimelineModelAggregator::prevItem(int selectedModel, int selectedItem, + qint64 time) const +{ + if (selectedItem != -1) + time = model(selectedModel)->startTime(selectedItem); + + QVarLengthArray<int> itemIndexes(modelCount()); + for (int i = 0; i < modelCount(); i++) { + const TimelineModel *currentModel = model(i); + if (selectedModel == i) { + itemIndexes[i] = (selectedItem <= 0 ? currentModel->count() : selectedItem) - 1; + } else { + itemIndexes[i] = currentModel->lastIndex(time); + if (itemIndexes[i] == -1) + itemIndexes[i] = currentModel->count() - 1; + else if (i < selectedModel && itemIndexes[i] + 1 < currentModel->count() && + currentModel->startTime(itemIndexes[i] + 1) == time) { + ++itemIndexes[i]; + } + } + } + + int candidateModelIndex = -1; + qint64 candidateStartTime = std::numeric_limits<qint64>::min(); + for (int i = modelCount() - 1; i >= 0 ; --i) { + const TimelineModel *currentModel = model(i); + if (itemIndexes[i] == -1 || itemIndexes[i] >= currentModel->count()) + continue; + qint64 newStartTime = currentModel->startTime(itemIndexes[i]); + if (newStartTime > candidateStartTime && + (newStartTime < time || (newStartTime == time && i < selectedModel))) { + candidateStartTime = newStartTime; + candidateModelIndex = i; + } + } + + int itemIndex = -1; + if (candidateModelIndex != -1) { + itemIndex = itemIndexes[candidateModelIndex]; + } else { + candidateStartTime = std::numeric_limits<qint64>::min(); + for (int i = 0; i < modelCount(); i++) { + const TimelineModel *currentModel = model(i); + if (currentModel->count() > 0 && + currentModel->startTime(currentModel->count() - 1) > candidateStartTime) { + candidateModelIndex = i; + itemIndex = currentModel->count() - 1; + candidateStartTime = currentModel->startTime(itemIndex); + } + } + } + + QVariantMap ret; + ret.insert(QLatin1String("model"), candidateModelIndex); + ret.insert(QLatin1String("item"), itemIndex); + return ret; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinemodelaggregator.h b/src/libs/tracing/timelinemodelaggregator.h new file mode 100644 index 00000000000..ddcf4e96a14 --- /dev/null +++ b/src/libs/tracing/timelinemodelaggregator.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinemodel.h" +#include "timelinenotesmodel.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineModelAggregator : public QObject +{ + Q_OBJECT + Q_PROPERTY(int height READ height NOTIFY heightChanged) + Q_PROPERTY(QVariantList models READ models WRITE setModels NOTIFY modelsChanged) + Q_PROPERTY(Timeline::TimelineNotesModel *notes READ notes CONSTANT) +public: + TimelineModelAggregator(TimelineNotesModel *notes = nullptr, QObject *parent = nullptr); + ~TimelineModelAggregator(); + + int height() const; + int generateModelId(); + + void addModel(TimelineModel *m); + const TimelineModel *model(int modelIndex) const; + + QVariantList models() const; + void setModels(const QVariantList &models); + + TimelineNotesModel *notes() const; + void clear(); + int modelCount() const; + int modelIndexById(int modelId) const; + + Q_INVOKABLE int modelOffset(int modelIndex) const; + + Q_INVOKABLE QVariantMap nextItem(int selectedModel, int selectedItem, qint64 time) const; + Q_INVOKABLE QVariantMap prevItem(int selectedModel, int selectedItem, qint64 time) const; + +signals: + void modelsChanged(); + void heightChanged(); + void updateCursorPosition(); + +private: + class TimelineModelAggregatorPrivate; + TimelineModelAggregatorPrivate *d_ptr; + Q_DECLARE_PRIVATE(TimelineModelAggregator) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinenotesmodel.cpp b/src/libs/tracing/timelinenotesmodel.cpp new file mode 100644 index 00000000000..13adaf72795 --- /dev/null +++ b/src/libs/tracing/timelinenotesmodel.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinenotesmodel_p.h" + +namespace Timeline { + +TimelineNotesModel::TimelineNotesModelPrivate::TimelineNotesModelPrivate(TimelineNotesModel *q) : + modified(false), q_ptr(q) +{ +} + +TimelineNotesModel::TimelineNotesModel(QObject *parent) : QObject(parent), + d_ptr(new TimelineNotesModelPrivate(this)) +{ +} + +TimelineNotesModel::~TimelineNotesModel() +{ + Q_D(TimelineNotesModel); + delete d; +} + +int TimelineNotesModel::count() const +{ + Q_D(const TimelineNotesModel); + return d->data.count(); +} + +void TimelineNotesModel::addTimelineModel(const TimelineModel *timelineModel) +{ + Q_D(TimelineNotesModel); + connect(timelineModel, &QObject::destroyed, this, [this](QObject *obj) { + removeTimelineModel(static_cast<TimelineModel *>(obj)); + }); + d->timelineModels.insert(timelineModel->modelId(), timelineModel); +} + +const TimelineModel *TimelineNotesModel::timelineModelByModelId(int modelId) const +{ + Q_D(const TimelineNotesModel); + auto it = d->timelineModels.find(modelId); + return it == d->timelineModels.end() ? 0 : it.value(); +} + +QList<const TimelineModel *> TimelineNotesModel::timelineModels() const +{ + Q_D(const TimelineNotesModel); + return d->timelineModels.values(); +} + +int TimelineNotesModel::typeId(int index) const +{ + Q_D(const TimelineNotesModel); + const TimelineNotesModelPrivate::Note ¬e = d->data[index]; + const TimelineModel *model = timelineModelByModelId(note.timelineModel); + if (!model) + return -1; // This can happen if one of the timeline models has been removed + return model->typeId(note.timelineIndex); +} + +QString TimelineNotesModel::text(int index) const +{ + Q_D(const TimelineNotesModel); + return d->data[index].text; +} + +int TimelineNotesModel::timelineModel(int index) const +{ + Q_D(const TimelineNotesModel); + return d->data[index].timelineModel; +} + +int TimelineNotesModel::timelineIndex(int index) const +{ + Q_D(const TimelineNotesModel); + return d->data[index].timelineIndex; +} + +QVariantList TimelineNotesModel::byTypeId(int selectedType) const +{ + QVariantList ret; + for (int noteId = 0; noteId < count(); ++noteId) { + if (selectedType == typeId(noteId)) + ret << noteId; + } + return ret; +} + +QVariantList TimelineNotesModel::byTimelineModel(int modelId) const +{ + Q_D(const TimelineNotesModel); + QVariantList ret; + for (int noteId = 0; noteId < count(); ++noteId) { + if (d->data[noteId].timelineModel == modelId) + ret << noteId; + } + return ret; +} + +int TimelineNotesModel::get(int modelId, int timelineIndex) const +{ + Q_D(const TimelineNotesModel); + for (int noteId = 0; noteId < count(); ++noteId) { + const TimelineNotesModelPrivate::Note ¬e = d->data[noteId]; + if (note.timelineModel == modelId && note.timelineIndex == timelineIndex) + return noteId; + } + + return -1; +} + +int TimelineNotesModel::add(int modelId, int timelineIndex, const QString &text) +{ + Q_D(TimelineNotesModel); + const TimelineModel *model = d->timelineModels.value(modelId); + int typeId = model->typeId(timelineIndex); + TimelineNotesModelPrivate::Note note = {text, modelId, timelineIndex}; + d->data << note; + d->modified = true; + emit changed(typeId, modelId, timelineIndex); + return d->data.count() - 1; +} + +void TimelineNotesModel::update(int index, const QString &text) +{ + Q_D(TimelineNotesModel); + TimelineNotesModelPrivate::Note ¬e = d->data[index]; + if (text != note.text) { + note.text = text; + d->modified = true; + emit changed(typeId(index), note.timelineModel, note.timelineIndex); + } +} + +void TimelineNotesModel::remove(int index) +{ + Q_D(TimelineNotesModel); + TimelineNotesModelPrivate::Note ¬e = d->data[index]; + int noteType = typeId(index); + int timelineModel = note.timelineModel; + int timelineIndex = note.timelineIndex; + d->data.removeAt(index); + d->modified = true; + emit changed(noteType, timelineModel, timelineIndex); +} + +bool TimelineNotesModel::isModified() const +{ + Q_D(const TimelineNotesModel); + return d->modified; +} + +void TimelineNotesModel::resetModified() +{ + Q_D(TimelineNotesModel); + d->modified = false; +} + +void TimelineNotesModel::stash() +{ +} + +void TimelineNotesModel::restore() +{ +} + +void TimelineNotesModel::removeTimelineModel(const TimelineModel *timelineModel) +{ + Q_D(TimelineNotesModel); + for (auto i = d->timelineModels.begin(); i != d->timelineModels.end();) { + if (i.value() == timelineModel) + i = d->timelineModels.erase(i); + else + ++i; + } +} + +void TimelineNotesModel::setText(int noteId, const QString &text) +{ + if (text.length() > 0) + update(noteId, text); + else + remove(noteId); +} + +void TimelineNotesModel::setText(int modelId, int index, const QString &text) +{ + int noteId = get(modelId, index); + if (noteId == -1) { + if (text.length() > 0) + add(modelId, index, text); + } else { + setText(noteId, text); + } +} + +void TimelineNotesModel::clear() +{ + Q_D(TimelineNotesModel); + d->data.clear(); + d->modified = false; + emit changed(-1, -1, -1); +} + +} // namespace Timeline + +#include "moc_timelinenotesmodel.cpp" diff --git a/src/libs/tracing/timelinenotesmodel.h b/src/libs/tracing/timelinenotesmodel.h new file mode 100644 index 00000000000..6583c5bb8d1 --- /dev/null +++ b/src/libs/tracing/timelinenotesmodel.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinemodel.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineNotesModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY changed) +public: + TimelineNotesModel(QObject *parent = 0); + ~TimelineNotesModel(); + + int count() const; + void addTimelineModel(const TimelineModel *timelineModel); + void removeTimelineModel(const TimelineModel *timelineModel); + QList<const TimelineModel *> timelineModels() const; + + Q_INVOKABLE int typeId(int index) const; + Q_INVOKABLE QString text(int index) const; + Q_INVOKABLE int timelineModel(int index) const; + Q_INVOKABLE int timelineIndex(int index) const; + + Q_INVOKABLE QVariantList byTypeId(int typeId) const; + Q_INVOKABLE QVariantList byTimelineModel(int modelId) const; + + Q_INVOKABLE int get(int modelId, int timelineIndex) const; + Q_INVOKABLE int add(int modelId, int timelineIndex, const QString &text); + Q_INVOKABLE void update(int index, const QString &text); + Q_INVOKABLE void remove(int index); + + Q_INVOKABLE void setText(int noteId, const QString &text); + Q_INVOKABLE void setText(int modelId, int index, const QString &text); + + bool isModified() const; + void resetModified(); + + virtual void stash(); + virtual void restore(); + virtual void clear(); + +protected: + const TimelineModel *timelineModelByModelId(int modelId) const; + +signals: + void changed(int typeId, int modelId, int timelineIndex); + +private: + class TimelineNotesModelPrivate; + TimelineNotesModelPrivate *d_ptr; + + Q_DECLARE_PRIVATE(TimelineNotesModel) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinenotesmodel_p.h b/src/libs/tracing/timelinenotesmodel_p.h new file mode 100644 index 00000000000..dd64fec2a99 --- /dev/null +++ b/src/libs/tracing/timelinenotesmodel_p.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinenotesmodel.h" + +namespace Timeline { + +class TimelineNotesModel::TimelineNotesModelPrivate { +public: + TimelineNotesModelPrivate(TimelineNotesModel *q); + + struct Note { + // Saved properties + QString text; + + // Cache, created on loading + int timelineModel; + int timelineIndex; + }; + + QList<Note> data; + QHash<int, const TimelineModel *> timelineModels; + bool modified; + +private: + TimelineNotesModel *q_ptr; + Q_DECLARE_PUBLIC(TimelineNotesModel) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinenotesrenderpass.cpp b/src/libs/tracing/timelinenotesrenderpass.cpp new file mode 100644 index 00000000000..932473183de --- /dev/null +++ b/src/libs/tracing/timelinenotesrenderpass.cpp @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinenotesrenderpass.h" +#include "timelinerenderstate.h" +#include "timelinenotesmodel.h" + +#include <utils/theme/theme.h> + +namespace Timeline { + +struct Point2DWithDistanceFromTop { + float x, y, d; + void set(float nx, float ny, float nd); +}; + +class NotesMaterial : public QSGMaterial +{ +public: + QSGMaterialType *type() const; + QSGMaterialShader *createShader() const; +}; + +struct NotesGeometry +{ + static const int maxNotes; + static const QSGGeometry::AttributeSet &point2DWithDistanceFromTop(); + + static QSGGeometry *createGeometry(QVector<int> &ids, const TimelineModel *model, + const TimelineRenderState *parentState, bool collapsed); +}; + +const int NotesGeometry::maxNotes = 0xffff / 2; + +class TimelineNotesRenderPassState : public TimelineRenderPass::State +{ +public: + TimelineNotesRenderPassState(int expandedRows); + ~TimelineNotesRenderPassState(); + + QSGNode *expandedRow(int row) const { return m_expandedRows[row]; } + QSGNode *collapsedOverlay() const { return m_collapsedOverlay; } + const QVector<QSGNode *> &expandedRows() const { return m_expandedRows; } + + QSGGeometry *nullGeometry() { return &m_nullGeometry; } + NotesMaterial *material() { return &m_material; } + +private: + QSGGeometryNode *createNode(); + + NotesMaterial m_material; + QSGGeometry m_nullGeometry; + QSGGeometryNode *m_collapsedOverlay; + QVector<QSGNode *> m_expandedRows; +}; + +const QSGGeometry::AttributeSet &NotesGeometry::point2DWithDistanceFromTop() +{ + static QSGGeometry::Attribute data[] = { + QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), + QSGGeometry::Attribute::create(1, 1, GL_FLOAT), + }; + static QSGGeometry::AttributeSet attrs = { + 2, + sizeof(Point2DWithDistanceFromTop), + data + }; + return attrs; +} + +const TimelineNotesRenderPass *TimelineNotesRenderPass::instance() +{ + static const TimelineNotesRenderPass pass; + return &pass; +} + +TimelineNotesRenderPass::TimelineNotesRenderPass() +{ +} + +TimelineRenderPass::State *TimelineNotesRenderPass::update(const TimelineAbstractRenderer *renderer, + const TimelineRenderState *parentState, + State *oldState, int firstIndex, + int lastIndex, bool stateChanged, + float spacing) const +{ + Q_UNUSED(firstIndex); + Q_UNUSED(lastIndex); + Q_UNUSED(spacing); + + const TimelineNotesModel *notes = renderer->notes(); + const TimelineModel *model = renderer->model(); + + if (!model || !notes) + return oldState; + + TimelineNotesRenderPassState *state; + if (oldState == 0) { + state = new TimelineNotesRenderPassState(model->expandedRowCount()); + } else { + if (!stateChanged && !renderer->notesDirty()) + return oldState; + state = static_cast<TimelineNotesRenderPassState *>(oldState); + } + + QVector<QVector<int> > expanded(model->expandedRowCount()); + QVector<int> collapsed; + + for (int i = 0; i < qMin(notes->count(), NotesGeometry::maxNotes); ++i) { + if (notes->timelineModel(i) != model->modelId()) + continue; + int timelineIndex = notes->timelineIndex(i); + if (model->startTime(timelineIndex) > parentState->end() || + model->endTime(timelineIndex) < parentState->start()) + continue; + expanded[model->expandedRow(timelineIndex)] << timelineIndex; + collapsed << timelineIndex; + } + + QSGGeometryNode *collapsedNode = static_cast<QSGGeometryNode *>(state->collapsedOverlay()); + + if (collapsed.count() > 0) { + collapsedNode->setGeometry(NotesGeometry::createGeometry(collapsed, model, parentState, + true)); + collapsedNode->setFlag(QSGGeometryNode::OwnsGeometry, true); + } else { + collapsedNode->setGeometry(state->nullGeometry()); + collapsedNode->setFlag(QSGGeometryNode::OwnsGeometry, false); + } + + for (int row = 0; row < model->expandedRowCount(); ++row) { + QSGGeometryNode *rowNode = static_cast<QSGGeometryNode *>(state->expandedRow(row)); + if (expanded[row].isEmpty()) { + rowNode->setGeometry(state->nullGeometry()); + rowNode->setFlag(QSGGeometryNode::OwnsGeometry, false); + } else { + rowNode->setGeometry(NotesGeometry::createGeometry(expanded[row], model, parentState, + false)); + collapsedNode->setFlag(QSGGeometryNode::OwnsGeometry, true); + } + } + + return state; +} + +TimelineNotesRenderPassState::TimelineNotesRenderPassState(int numExpandedRows) : + m_nullGeometry(NotesGeometry::point2DWithDistanceFromTop(), 0) +{ + m_material.setFlag(QSGMaterial::Blending, true); + m_expandedRows.reserve(numExpandedRows); + for (int i = 0; i < numExpandedRows; ++i) + m_expandedRows << createNode(); + m_collapsedOverlay = createNode(); +} + +TimelineNotesRenderPassState::~TimelineNotesRenderPassState() +{ + qDeleteAll(m_expandedRows); + delete m_collapsedOverlay; +} + +QSGGeometryNode *TimelineNotesRenderPassState::createNode() +{ + QSGGeometryNode *node = new QSGGeometryNode; + node->setGeometry(&m_nullGeometry); + node->setMaterial(&m_material); + node->setFlag(QSGNode::OwnedByParent, false); + return node; +} + +QSGGeometry *NotesGeometry::createGeometry(QVector<int> &ids, const TimelineModel *model, + const TimelineRenderState *parentState, bool collapsed) +{ + float rowHeight = TimelineModel::defaultRowHeight(); + QSGGeometry *geometry = new QSGGeometry(point2DWithDistanceFromTop(), + ids.count() * 2); + Q_ASSERT(geometry->vertexData()); + geometry->setDrawingMode(GL_LINES); + geometry->setLineWidth(3); + Point2DWithDistanceFromTop *v = + static_cast<Point2DWithDistanceFromTop *>(geometry->vertexData()); + for (int i = 0; i < ids.count(); ++i) { + int timelineIndex = ids[i]; + float horizontalCenter = ((model->startTime(timelineIndex) + + model->endTime(timelineIndex)) / (qint64)2 - + parentState->start()) * parentState->scale(); + float verticalStart = (collapsed ? (model->collapsedRow(timelineIndex) + 0.1) : 0.1) * + rowHeight; + float verticalEnd = verticalStart + 0.8 * rowHeight; + v[i * 2].set(horizontalCenter, verticalStart, 0); + v[i * 2 + 1].set(horizontalCenter, verticalEnd, 1); + } + return geometry; +} + +class NotesMaterialShader : public QSGMaterialShader +{ +public: + NotesMaterialShader(); + + virtual void updateState(const RenderState &state, QSGMaterial *newEffect, + QSGMaterial *oldEffect); + virtual char const *const *attributeNames() const; + +private: + virtual void initialize(); + + int m_matrix_id; + int m_z_range_id; + int m_color_id; +}; + +NotesMaterialShader::NotesMaterialShader() + : QSGMaterialShader() +{ + setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/tracing/notes.vert")); + setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/tracing/notes.frag")); +} + +void NotesMaterialShader::updateState(const RenderState &state, QSGMaterial *, QSGMaterial *) +{ + if (state.isMatrixDirty()) { + program()->setUniformValue(m_matrix_id, state.combinedMatrix()); + program()->setUniformValue(m_z_range_id, GLfloat(1.0)); + const QColor notesColor = Utils::creatorTheme() + ? Utils::creatorTheme()->color(Utils::Theme::Timeline_HighlightColor) + : QColor(255, 165, 0); + program()->setUniformValue(m_color_id, notesColor); + } +} + +char const *const *NotesMaterialShader::attributeNames() const +{ + static const char *const attr[] = {"vertexCoord", "distanceFromTop", 0}; + return attr; +} + +void NotesMaterialShader::initialize() +{ + m_matrix_id = program()->uniformLocation("matrix"); + m_z_range_id = program()->uniformLocation("_qt_zRange"); + m_color_id = program()->uniformLocation("notesColor"); +} + +QSGMaterialType *NotesMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +QSGMaterialShader *NotesMaterial::createShader() const +{ + return new NotesMaterialShader; +} + +void Point2DWithDistanceFromTop::set(float nx, float ny, float nd) +{ + x = nx; y = ny; d = nd; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinenotesrenderpass.h b/src/libs/tracing/timelinenotesrenderpass.h new file mode 100644 index 00000000000..57e1548771e --- /dev/null +++ b/src/libs/tracing/timelinenotesrenderpass.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineabstractrenderer.h" +#include <QSGMaterial> + +namespace Timeline { + +class TRACING_EXPORT TimelineNotesRenderPass : public TimelineRenderPass +{ +public: + static const TimelineNotesRenderPass *instance(); + + State *update(const TimelineAbstractRenderer *renderer, const TimelineRenderState *parentState, + State *oldState, int firstIndex, int lastIndex, bool stateChanged, + float spacing) const; + +private: + TimelineNotesRenderPass(); +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelineoverviewrenderer.cpp b/src/libs/tracing/timelineoverviewrenderer.cpp new file mode 100644 index 00000000000..8b78c54a1fa --- /dev/null +++ b/src/libs/tracing/timelineoverviewrenderer.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelineoverviewrenderer_p.h" +#include "timelinerenderstate.h" + +namespace Timeline { + +TimelineOverviewRenderer::TimelineOverviewRenderer(QQuickItem *parent) : + TimelineAbstractRenderer(*(new TimelineOverviewRendererPrivate), parent) +{ +} + +TimelineOverviewRenderer::TimelineOverviewRendererPrivate::TimelineOverviewRendererPrivate() : + renderState(0) +{ +} + +TimelineOverviewRenderer::TimelineOverviewRendererPrivate::~TimelineOverviewRendererPrivate() +{ + delete renderState; +} + +QSGNode *TimelineOverviewRenderer::updatePaintNode(QSGNode *oldNode, + UpdatePaintNodeData *updatePaintNodeData) +{ + Q_D(TimelineOverviewRenderer); + + if (!d->model || d->model->isEmpty() || !d->zoomer || d->zoomer->traceDuration() <= 0) { + delete oldNode; + return 0; + } + + if (d->modelDirty) { + delete d->renderState; + d->renderState = 0; + } + + if (d->renderState == 0) { + d->renderState = new TimelineRenderState(d->zoomer->traceStart(), d->zoomer->traceEnd(), + 1.0, d->renderPasses.size()); + } + + float xSpacing = static_cast<float>(width() / d->zoomer->traceDuration()); + float ySpacing = static_cast<float>( + height() / (d->model->collapsedRowCount() * TimelineModel::defaultRowHeight())); + + for (int i = 0; i < d->renderPasses.length(); ++i) { + d->renderState->setPassState(i, d->renderPasses[i]->update(this, d->renderState, + d->renderState->passState(i), + 0, d->model->count(), true, + xSpacing)); + } + + if (d->renderState->isEmpty()) + d->renderState->assembleNodeTree(d->model, d->model->height(), 0); + + TimelineAbstractRenderer::updatePaintNode(0, updatePaintNodeData); + + QMatrix4x4 matrix; + matrix.scale(xSpacing, ySpacing, 1); + return d->renderState->finalize(oldNode, false, matrix); +} + +} + diff --git a/src/libs/tracing/timelineoverviewrenderer.h b/src/libs/tracing/timelineoverviewrenderer.h new file mode 100644 index 00000000000..a1a684c5b92 --- /dev/null +++ b/src/libs/tracing/timelineoverviewrenderer.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineabstractrenderer.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineOverviewRenderer : public TimelineAbstractRenderer +{ +public: + TimelineOverviewRenderer(QQuickItem *parent = 0); + +protected: + virtual QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData); + + class TimelineOverviewRendererPrivate; + Q_DECLARE_PRIVATE(TimelineOverviewRenderer) +}; + +} // namespace Timeline + +QML_DECLARE_TYPE(Timeline::TimelineOverviewRenderer) diff --git a/src/libs/tracing/timelineoverviewrenderer_p.h b/src/libs/tracing/timelineoverviewrenderer_p.h new file mode 100644 index 00000000000..d45d4d716bd --- /dev/null +++ b/src/libs/tracing/timelineoverviewrenderer_p.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineoverviewrenderer.h" +#include "timelineabstractrenderer_p.h" + +namespace Timeline { + +class TimelineOverviewRenderer::TimelineOverviewRendererPrivate : + public TimelineAbstractRenderer::TimelineAbstractRendererPrivate +{ +public: + TimelineOverviewRendererPrivate(); + ~TimelineOverviewRendererPrivate(); + + TimelineRenderState *renderState; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderer.cpp b/src/libs/tracing/timelinerenderer.cpp new file mode 100644 index 00000000000..182a7aadd63 --- /dev/null +++ b/src/libs/tracing/timelinerenderer.cpp @@ -0,0 +1,389 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinerenderer_p.h" +#include "timelinerenderpass.h" +#include "timelinenotesmodel.h" +#include "timelineitemsrenderpass.h" +#include "timelineselectionrenderpass.h" +#include "timelinenotesrenderpass.h" + +#include <QElapsedTimer> +#include <QQmlContext> +#include <QQmlProperty> +#include <QTimer> +#include <QPixmap> +#include <QVarLengthArray> +#include <QSGTransformNode> +#include <QSGSimpleRectNode> + +#include <math.h> + +namespace Timeline { + +TimelineRenderer::TimelineRendererPrivate::TimelineRendererPrivate() : lastState(0) +{ + resetCurrentSelection(); +} + +TimelineRenderer::TimelineRendererPrivate::~TimelineRendererPrivate() +{ + clear(); +} + +void TimelineRenderer::TimelineRendererPrivate::clear() +{ + for (auto i = renderStates.begin(); i != renderStates.end(); ++i) + qDeleteAll(*i); + renderStates.clear(); + lastState = 0; +} + +TimelineRenderer::TimelineRenderer(QQuickItem *parent) : + TimelineAbstractRenderer(*(new TimelineRendererPrivate), parent) +{ + setAcceptedMouseButtons(Qt::LeftButton); + setAcceptHoverEvents(true); +} + +void TimelineRenderer::TimelineRendererPrivate::resetCurrentSelection() +{ + currentEventIndex = -1; + currentRow = -1; +} + +TimelineRenderState *TimelineRenderer::TimelineRendererPrivate::findRenderState() +{ + int newLevel = 0; + qint64 newOffset = 0; + int level; + qint64 offset; + + qint64 newStart = zoomer->traceStart(); + qint64 newEnd = zoomer->traceEnd(); + qint64 start; + qint64 end; + do { + level = newLevel; + offset = newOffset; + start = newStart; + end = newEnd; + + newLevel = level + 1; + qint64 range = zoomer->traceDuration() >> newLevel; + newOffset = (zoomer->windowStart() - zoomer->traceStart() + range / 2) / range; + newStart = zoomer->traceStart() + newOffset * range - range / 2; + newEnd = newStart + range; + } while (newStart < zoomer->windowStart() && newEnd > zoomer->windowEnd()); + + + if (renderStates.length() <= level) + renderStates.resize(level + 1); + TimelineRenderState *state = renderStates[level][offset]; + if (state == 0) { + state = new TimelineRenderState(start, end, 1.0 / static_cast<qreal>(SafeFloatMax), + renderPasses.size()); + renderStates[level][offset] = state; + } + return state; +} + +QSGNode *TimelineRenderer::updatePaintNode(QSGNode *node, UpdatePaintNodeData *updatePaintNodeData) +{ + Q_D(TimelineRenderer); + + if (!d->model || d->model->hidden() || d->model->isEmpty() || !d->zoomer || + d->zoomer->windowDuration() <= 0) { + delete node; + return 0; + } + + float spacing = static_cast<float>(width() / d->zoomer->windowDuration()); + + if (d->modelDirty) { + if (node) + node->removeAllChildNodes(); + d->clear(); + } + + TimelineRenderState *state = d->findRenderState(); + + int lastIndex = d->model->lastIndex(d->zoomer->windowEnd()); + int firstIndex = d->model->firstIndex(d->zoomer->windowStart()); + + for (int i = 0; i < d->renderPasses.length(); ++i) + state->setPassState(i, d->renderPasses[i]->update(this, state, state->passState(i), + firstIndex, lastIndex + 1, + state != d->lastState, spacing)); + + if (state->isEmpty()) { // new state + state->assembleNodeTree(d->model, TimelineModel::defaultRowHeight(), + TimelineModel::defaultRowHeight()); + } else if (d->rowHeightsDirty || state != d->lastState) { + state->updateExpandedRowHeights(d->model, TimelineModel::defaultRowHeight(), + TimelineModel::defaultRowHeight()); + } + + TimelineAbstractRenderer::updatePaintNode(0, updatePaintNodeData); + d->lastState = state; + + QMatrix4x4 matrix; + matrix.translate((state->start() - d->zoomer->windowStart()) * spacing, 0, 0); + matrix.scale(spacing / state->scale(), 1, 1); + + return state->finalize(node, d->model->expanded(), matrix); +} + +void TimelineRenderer::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); +} + +int TimelineRenderer::TimelineRendererPrivate::rowFromPosition(int y) const +{ + if (!model->expanded()) + return y / TimelineModel::defaultRowHeight(); + + int ret = 0; + for (int row = 0; row < model->expandedRowCount(); ++row) { + y -= model->expandedRowHeight(row); + if (y <= 0) return ret; + ++ret; + } + + return ret; +} + +void TimelineRenderer::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(TimelineRenderer); + d->findCurrentSelection(event->pos().x(), event->pos().y(), width()); + setSelectedItem(d->currentEventIndex); +} + +void TimelineRenderer::mouseMoveEvent(QMouseEvent *event) +{ + event->setAccepted(false); +} + +void TimelineRenderer::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(TimelineRenderer); + if (!d->selectionLocked) { + d->findCurrentSelection(event->pos().x(), event->pos().y(), width()); + if (d->currentEventIndex != -1) + setSelectedItem(d->currentEventIndex); + } + if (d->currentEventIndex == -1) + event->setAccepted(false); +} + +void TimelineRenderer::wheelEvent(QWheelEvent *event) +{ + // ctrl-wheel means zoom + if (event->modifiers() & Qt::ControlModifier) { + event->setAccepted(true); + TimelineZoomControl *zoom = zoomer(); + + int degrees = (event->angleDelta().x() + event->angleDelta().y()) / 8; + const qint64 circle = 360; + qint64 mouseTime = event->pos().x() * zoom->windowDuration() / width() + + zoom->windowStart(); + qint64 beforeMouse = (mouseTime - zoom->rangeStart()) * (circle - degrees) / circle; + qint64 afterMouse = (zoom->rangeEnd() - mouseTime) * (circle - degrees) / circle; + + qint64 newStart = qBound(zoom->traceStart(), zoom->traceEnd(), mouseTime - beforeMouse); + if (newStart + zoom->minimumRangeLength() > zoom->traceEnd()) + return; // too close to trace end + + qint64 newEnd = qBound(newStart + zoom->minimumRangeLength(), zoom->traceEnd(), + mouseTime + afterMouse); + + zoom->setRange(newStart, newEnd); + } else { + TimelineAbstractRenderer::wheelEvent(event); + } +} + +TimelineRenderer::TimelineRendererPrivate::MatchResult +TimelineRenderer::TimelineRendererPrivate::checkMatch(MatchParameters *params, int index, + qint64 itemStart, qint64 itemEnd) +{ + const qint64 offset = qAbs(itemEnd - params->exactTime) + qAbs(itemStart - params->exactTime); + if (offset >= params->bestOffset) + return NoMatch; + + // match + params->bestOffset = offset; + currentEventIndex = index; + + // Exact match. If we can get better than this, then we have multiple overlapping + // events in one row. There is no point in sorting those out as you cannot properly + // discern them anyway. + return (itemEnd >= params->exactTime && itemStart <= params->exactTime) + ? ExactMatch : ApproximateMatch; +} + +TimelineRenderer::TimelineRendererPrivate::MatchResult +TimelineRenderer::TimelineRendererPrivate::matchForward(MatchParameters *params, int index) +{ + if (index < 0) + return NoMatch; + + if (index >= model->count()) + return Cutoff; + + if (model->row(index) != currentRow) + return NoMatch; + + const qint64 itemEnd = model->endTime(index); + if (itemEnd < params->startTime) + return NoMatch; + + const qint64 itemStart = model->startTime(index); + if (itemStart > params->endTime) + return Cutoff; + + // Further iteration will only increase the startOffset. + if (itemStart - params->exactTime >= params->bestOffset) + return Cutoff; + + return checkMatch(params, index, itemStart, itemEnd); +} + +TimelineRenderer::TimelineRendererPrivate::MatchResult +TimelineRenderer::TimelineRendererPrivate::matchBackward(MatchParameters *params, int index) +{ + if (index < 0) + return Cutoff; + + if (index >= model->count()) + return NoMatch; + + if (model->row(index) != currentRow) + return NoMatch; + + const qint64 itemStart = model->startTime(index); + if (itemStart > params->endTime) + return NoMatch; + + // There can be small events that don't reach the cursor position after large events + // that do but are in a different row. In that case, the parent index will be valid and will + // point to the large event. If that is also outside the range, we are really done. + const qint64 itemEnd = model->endTime(index); + if (itemEnd < params->startTime) { + const int parentIndex = model->parentIndex(index); + const qint64 parentEnd = parentIndex == -1 ? itemEnd : model->endTime(parentIndex); + return (parentEnd < params->startTime) ? Cutoff : NoMatch; + } + + if (params->exactTime - itemStart >= params->bestOffset) { + // We cannot get better anymore as the startTimes are totally ordered. + // Thus, the startOffset will only get bigger and we're only adding a + // positive number (end offset) in checkMatch() when comparing with bestOffset. + return Cutoff; + } + + return checkMatch(params, index, itemStart, itemEnd); +} + +void TimelineRenderer::TimelineRendererPrivate::findCurrentSelection(int mouseX, int mouseY, + int width) +{ + if (!zoomer || !model || width < 1) + return; + + qint64 duration = zoomer->windowDuration(); + if (duration <= 0) + return; + + MatchParameters params; + + // Make the "selected" area 3 pixels wide by adding/subtracting 1 to catch very narrow events. + params.startTime = (mouseX - 1) * duration / width + zoomer->windowStart(); + params.endTime = (mouseX + 1) * duration / width + zoomer->windowStart(); + params.exactTime = (params.startTime + params.endTime) / 2; + const int row = rowFromPosition(mouseY); + + // already covered? Only make sure d->selectedItem is correct. + if (currentEventIndex != -1 && + params.exactTime >= model->startTime(currentEventIndex) && + params.exactTime < model->endTime(currentEventIndex) && + row == currentRow) { + return; + } + + currentRow = row; + currentEventIndex = -1; + + const int middle = model->bestIndex(params.exactTime); + if (middle == -1) + return; + + params.bestOffset = std::numeric_limits<qint64>::max(); + const qint64 itemStart = model->startTime(middle); + const qint64 itemEnd = model->endTime(middle); + if (model->row(middle) == row && itemEnd >= params.startTime && itemStart <= params.endTime) { + if (checkMatch(¶ms, middle, itemStart, itemEnd) == ExactMatch) + return; + } + + MatchResult forward = NoMatch; + MatchResult backward = NoMatch; + for (int offset = 1; forward != Cutoff || backward != Cutoff; ++offset) { + if (backward != Cutoff + && (backward = matchBackward(¶ms, middle - offset)) == ExactMatch) { + return; + } + if (forward != Cutoff + && (forward = matchForward(¶ms, middle + offset)) == ExactMatch) { + return; + } + } +} + +void TimelineRenderer::clearData() +{ + Q_D(TimelineRenderer); + d->resetCurrentSelection(); + setSelectedItem(-1); + setSelectionLocked(true); +} + +void TimelineRenderer::selectNextFromSelectionId(int selectionId) +{ + Q_D(TimelineRenderer); + setSelectedItem(d->model->nextItemBySelectionId(selectionId, d->zoomer->rangeStart(), + d->selectedItem)); +} + +void TimelineRenderer::selectPrevFromSelectionId(int selectionId) +{ + Q_D(TimelineRenderer); + setSelectedItem(d->model->prevItemBySelectionId(selectionId, d->zoomer->rangeStart(), + d->selectedItem)); +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderer.h b/src/libs/tracing/timelinerenderer.h new file mode 100644 index 00000000000..7b7f035a469 --- /dev/null +++ b/src/libs/tracing/timelinerenderer.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinezoomcontrol.h" +#include "timelinemodel.h" +#include "timelinenotesmodel.h" +#include "timelineabstractrenderer.h" + +#include <QSGTransformNode> +#include <QQuickItem> + +namespace Timeline { + + +class TRACING_EXPORT TimelineRenderer : public TimelineAbstractRenderer +{ + Q_OBJECT + +public: + explicit TimelineRenderer(QQuickItem *parent = 0); + + Q_INVOKABLE void selectNextFromSelectionId(int selectionId); + Q_INVOKABLE void selectPrevFromSelectionId(int selectionId); + Q_INVOKABLE void clearData(); + +protected: + virtual QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseReleaseEvent(QMouseEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void hoverMoveEvent(QHoverEvent *event); + virtual void wheelEvent(QWheelEvent *event); + +private: + class TimelineRendererPrivate; + Q_DECLARE_PRIVATE(TimelineRenderer) +}; + +} // namespace Timeline + +QML_DECLARE_TYPE(Timeline::TimelineRenderer) diff --git a/src/libs/tracing/timelinerenderer_p.h b/src/libs/tracing/timelinerenderer_p.h new file mode 100644 index 00000000000..714206dc5d0 --- /dev/null +++ b/src/libs/tracing/timelinerenderer_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinerenderer.h" +#include "timelineabstractrenderer_p.h" + +namespace Timeline { + +class TimelineRenderer::TimelineRendererPrivate : + public TimelineAbstractRenderer::TimelineAbstractRendererPrivate { +public: + enum MatchResult { + NoMatch, + Cutoff, + ApproximateMatch, + ExactMatch + }; + + struct MatchParameters { + qint64 startTime; + qint64 endTime; + qint64 exactTime; + qint64 bestOffset; + }; + + TimelineRendererPrivate(); + ~TimelineRendererPrivate(); + + void clear(); + + int rowFromPosition(int y) const; + + MatchResult checkMatch(MatchParameters *params, int index, qint64 itemStart, qint64 itemEnd); + MatchResult matchForward(MatchParameters *params, int index); + MatchResult matchBackward(MatchParameters *params, int index); + + void findCurrentSelection(int mouseX, int mouseY, int width); + + static const int SafeFloatMax = 1 << 12; + + void resetCurrentSelection(); + + TimelineRenderState *findRenderState(); + + int currentEventIndex; + int currentRow; + + QVector<QHash<qint64, TimelineRenderState *> > renderStates; + TimelineRenderState *lastState; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderpass.cpp b/src/libs/tracing/timelinerenderpass.cpp new file mode 100644 index 00000000000..7c87f239c6c --- /dev/null +++ b/src/libs/tracing/timelinerenderpass.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinerenderpass.h" + +namespace Timeline { + +const QVector<QSGNode *> &TimelineRenderPass::State::expandedRows() const +{ + static const QVector<QSGNode *> empty; + return empty; +} + +const QVector<QSGNode *> &TimelineRenderPass::State::collapsedRows() const +{ + static const QVector<QSGNode *> empty; + return empty; +} + +QSGNode *TimelineRenderPass::State::expandedOverlay() const +{ + return 0; +} + +QSGNode *TimelineRenderPass::State::collapsedOverlay() const +{ + return 0; +} + +TimelineRenderPass::State::~State() +{ +} + +TimelineRenderPass::~TimelineRenderPass() {} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderpass.h b/src/libs/tracing/timelinerenderpass.h new file mode 100644 index 00000000000..3ebb3b03b16 --- /dev/null +++ b/src/libs/tracing/timelinerenderpass.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include <QVector> + +QT_FORWARD_DECLARE_CLASS(QSGNode) +namespace Timeline { + +class TimelineAbstractRenderer; +class TimelineRenderState; + +class TRACING_EXPORT TimelineRenderPass { +public: + class TRACING_EXPORT State { + public: + virtual const QVector<QSGNode *> &expandedRows() const; + virtual const QVector<QSGNode *> &collapsedRows() const; + virtual QSGNode *expandedOverlay() const; + virtual QSGNode *collapsedOverlay() const; + virtual ~State(); + }; + + virtual ~TimelineRenderPass(); + virtual State *update(const TimelineAbstractRenderer *renderer, + const TimelineRenderState *parentState, + State *state, int indexFrom, int indexTo, bool stateChanged, + float spacing) const = 0; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderstate.cpp b/src/libs/tracing/timelinerenderstate.cpp new file mode 100644 index 00000000000..f897496c56c --- /dev/null +++ b/src/libs/tracing/timelinerenderstate.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinerenderstate_p.h" +#include <utils/qtcassert.h> + +namespace Timeline { + +TimelineRenderState::TimelineRenderState(qint64 start, qint64 end, float scale, int numPasses) : + d_ptr(new TimelineRenderStatePrivate) +{ + Q_D(TimelineRenderState); + d->expandedRowRoot = new QSGNode; + d->collapsedRowRoot = new QSGNode; + d->expandedOverlayRoot = new QSGNode; + d->collapsedOverlayRoot = new QSGNode; + d->start = start; + d->end = end; + d->scale = scale; + d->passes.resize(numPasses); + + d->expandedRowRoot->setFlag(QSGNode::OwnedByParent, false); + d->collapsedRowRoot->setFlag(QSGNode::OwnedByParent, false); + d->expandedOverlayRoot->setFlag(QSGNode::OwnedByParent, false); + d->collapsedOverlayRoot->setFlag(QSGNode::OwnedByParent, false); +} + +TimelineRenderState::~TimelineRenderState() +{ + Q_D(TimelineRenderState); + delete d->expandedRowRoot; + delete d->collapsedRowRoot; + delete d->expandedOverlayRoot; + delete d->collapsedOverlayRoot; + qDeleteAll(d->passes); + delete d; +} + +qint64 TimelineRenderState::start() const +{ + Q_D(const TimelineRenderState); + return d->start; +} + +qint64 TimelineRenderState::end() const +{ + Q_D(const TimelineRenderState); + return d->end; +} + +float TimelineRenderState::scale() const +{ + Q_D(const TimelineRenderState); + return d->scale; +} + +const QSGNode *TimelineRenderState::expandedRowRoot() const +{ + Q_D(const TimelineRenderState); + return d->expandedRowRoot; +} + +const QSGNode *TimelineRenderState::collapsedRowRoot() const +{ + Q_D(const TimelineRenderState); + return d->collapsedRowRoot; +} + +const QSGNode *TimelineRenderState::expandedOverlayRoot() const +{ + Q_D(const TimelineRenderState); + return d->expandedOverlayRoot; +} + +const QSGNode *TimelineRenderState::collapsedOverlayRoot() const +{ + Q_D(const TimelineRenderState); + return d->collapsedOverlayRoot; +} + +QSGNode *TimelineRenderState::expandedRowRoot() +{ + Q_D(TimelineRenderState); + return d->expandedRowRoot; +} + +QSGNode *TimelineRenderState::collapsedRowRoot() +{ + Q_D(TimelineRenderState); + return d->collapsedRowRoot; +} + +QSGNode *TimelineRenderState::expandedOverlayRoot() +{ + Q_D(TimelineRenderState); + return d->expandedOverlayRoot; +} + +QSGNode *TimelineRenderState::collapsedOverlayRoot() +{ + Q_D(TimelineRenderState); + return d->collapsedOverlayRoot; +} + +bool TimelineRenderState::isEmpty() const +{ + Q_D(const TimelineRenderState); + return d->collapsedRowRoot->childCount() == 0 && d->expandedRowRoot->childCount() == 0 && + d->collapsedOverlayRoot->childCount() == 0 && d->expandedOverlayRoot->childCount() == 0; +} + +void TimelineRenderState::assembleNodeTree(const TimelineModel *model, int defaultRowHeight, + int defaultRowOffset) +{ + Q_D(TimelineRenderState); + QTC_ASSERT(isEmpty(), return); + + for (int pass = 0; pass < d->passes.length(); ++pass) { + const TimelineRenderPass::State *passState = d->passes[pass]; + if (!passState) + continue; + if (passState->expandedOverlay()) + d->expandedOverlayRoot->appendChildNode(passState->expandedOverlay()); + if (passState->collapsedOverlay()) + d->collapsedOverlayRoot->appendChildNode(passState->collapsedOverlay()); + } + + int row = 0; + for (int i = 0; i < model->expandedRowCount(); ++i) { + QSGTransformNode *rowNode = new QSGTransformNode; + for (int pass = 0; pass < d->passes.length(); ++pass) { + const TimelineRenderPass::State *passState = d->passes[pass]; + if (!passState) + continue; + const QVector<QSGNode *> &rows = passState->expandedRows(); + if (rows.length() > row) { + QSGNode *rowChildNode = rows[row]; + if (rowChildNode) + rowNode->appendChildNode(rowChildNode); + } + } + d->expandedRowRoot->appendChildNode(rowNode); + ++row; + } + + for (int row = 0; row < model->collapsedRowCount(); ++row) { + QSGTransformNode *rowNode = new QSGTransformNode; + QMatrix4x4 matrix; + matrix.translate(0, row * defaultRowOffset, 0); + matrix.scale(1.0, static_cast<float>(defaultRowHeight) / + static_cast<float>(TimelineModel::defaultRowHeight()), 1.0); + rowNode->setMatrix(matrix); + for (int pass = 0; pass < d->passes.length(); ++pass) { + const TimelineRenderPass::State *passState = d->passes[pass]; + if (!passState) + continue; + const QVector<QSGNode *> &rows = passState->collapsedRows(); + if (rows.length() > row) { + QSGNode *rowChildNode = rows[row]; + if (rowChildNode) + rowNode->appendChildNode(rowChildNode); + } + } + d->collapsedRowRoot->appendChildNode(rowNode); + } + + updateExpandedRowHeights(model, defaultRowHeight, defaultRowOffset); +} + +void TimelineRenderState::updateExpandedRowHeights(const TimelineModel *model, int defaultRowHeight, + int defaultRowOffset) +{ + Q_D(TimelineRenderState); + int row = 0; + qreal offset = 0; + for (QSGNode *rowNode = d->expandedRowRoot->firstChild(); rowNode != 0; + rowNode = rowNode->nextSibling()) { + qreal rowHeight = model->expandedRowHeight(row++); + QMatrix4x4 matrix; + matrix.translate(0, offset, 0); + matrix.scale(1, rowHeight / defaultRowHeight, 1); + offset += defaultRowOffset * rowHeight / defaultRowHeight; + static_cast<QSGTransformNode *>(rowNode)->setMatrix(matrix); + } +} + +QSGTransformNode *TimelineRenderState::finalize(QSGNode *oldNode, bool expanded, + const QMatrix4x4 &transform) +{ + Q_D(TimelineRenderState); + QSGNode *rowNode = expanded ? d->expandedRowRoot : d->collapsedRowRoot; + QSGNode *overlayNode = expanded ?d->expandedOverlayRoot : d->collapsedOverlayRoot; + + QSGTransformNode *node = oldNode ? static_cast<QSGTransformNode *>(oldNode) : + new QSGTransformNode; + node->setMatrix(transform); + + if (node->firstChild() != rowNode || node->lastChild() != overlayNode) { + node->removeAllChildNodes(); + node->appendChildNode(rowNode); + node->appendChildNode(overlayNode); + } + return node; +} + +TimelineRenderPass::State *TimelineRenderState::passState(int i) +{ + Q_D(TimelineRenderState); + return d->passes[i]; +} + +const TimelineRenderPass::State *TimelineRenderState::passState(int i) const +{ + Q_D(const TimelineRenderState); + return d->passes[i]; +} + +void TimelineRenderState::setPassState(int i, TimelineRenderPass::State *state) +{ + Q_D(TimelineRenderState); + d->passes[i] = state; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderstate.h b/src/libs/tracing/timelinerenderstate.h new file mode 100644 index 00000000000..c756f1606c6 --- /dev/null +++ b/src/libs/tracing/timelinerenderstate.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QSGNode> +#include "timelinerenderpass.h" +#include "timelinemodel.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineRenderState { +public: + TimelineRenderState(qint64 start, qint64 end, float scale, int numPasses); + ~TimelineRenderState(); + + qint64 start() const; + qint64 end() const; + float scale() const; + + TimelineRenderPass::State *passState(int i); + const TimelineRenderPass::State *passState(int i) const; + void setPassState(int i, TimelineRenderPass::State *state); + + const QSGNode *expandedRowRoot() const; + const QSGNode *collapsedRowRoot() const; + const QSGNode *expandedOverlayRoot() const; + const QSGNode *collapsedOverlayRoot() const; + + QSGNode *expandedRowRoot(); + QSGNode *collapsedRowRoot(); + QSGNode *expandedOverlayRoot(); + QSGNode *collapsedOverlayRoot(); + + bool isEmpty() const; + void assembleNodeTree(const TimelineModel *model, int defaultRowHeight, int defaultRowOffset); + void updateExpandedRowHeights(const TimelineModel *model, int defaultRowHeight, + int defaultRowOffset); + QSGTransformNode *finalize(QSGNode *oldNode, bool expanded, const QMatrix4x4 &transform); + +private: + class TimelineRenderStatePrivate; + TimelineRenderStatePrivate *d_ptr; + Q_DECLARE_PRIVATE(TimelineRenderState) +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinerenderstate_p.h b/src/libs/tracing/timelinerenderstate_p.h new file mode 100644 index 00000000000..3399e6b9af5 --- /dev/null +++ b/src/libs/tracing/timelinerenderstate_p.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelinerenderstate.h" + +namespace Timeline { + +class TimelineRenderState::TimelineRenderStatePrivate { +public: + QSGNode *expandedRowRoot; + QSGNode *collapsedRowRoot; + QSGNode *expandedOverlayRoot; + QSGNode *collapsedOverlayRoot; + + qint64 start; + qint64 end; + + float scale; // "native" scale, this stays the same through the life time of a state + + QVector<TimelineRenderPass::State *> passes; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelineselectionrenderpass.cpp b/src/libs/tracing/timelineselectionrenderpass.cpp new file mode 100644 index 00000000000..b8d6c2991a0 --- /dev/null +++ b/src/libs/tracing/timelineselectionrenderpass.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelineselectionrenderpass.h" +#include "timelineitemsrenderpass.h" +#include <QtMath> +#include <QSGSimpleRectNode> + +namespace Timeline { + +QSGGeometryNode *createSelectionNode(QSGMaterial *material) +{ + QSGGeometryNode *selectionNode = new QSGGeometryNode; + selectionNode->setMaterial(material); + selectionNode->setFlag(QSGNode::OwnsMaterial, false); + QSGGeometry *geometry = new QSGGeometry(OpaqueColoredPoint2DWithSize::attributes(), 4); + Q_ASSERT(geometry->vertexData()); + geometry->setDrawingMode(GL_TRIANGLE_STRIP); + OpaqueColoredPoint2DWithSize *v = OpaqueColoredPoint2DWithSize::fromVertexData(geometry); + for (int i = 0; i < 4; ++i) + v[i].set(0, 0, 0, 0, 0, 0, 0, 0, 0); + selectionNode->setGeometry(geometry); + selectionNode->setFlag(QSGNode::OwnsGeometry, true); + selectionNode->setFlag(QSGNode::OwnedByParent, false); + return selectionNode; +} + +class TimelineSelectionRenderPassState : public TimelineRenderPass::State { +public: + TimelineSelectionRenderPassState(); + ~TimelineSelectionRenderPassState(); + + QSGNode *expandedOverlay() const override { return m_expandedOverlay; } + QSGNode *collapsedOverlay() const override { return m_collapsedOverlay; } + TimelineItemsMaterial *material() { return &m_material; } + +private: + QSGNode *m_expandedOverlay; + QSGNode *m_collapsedOverlay; + TimelineItemsMaterial m_material; +}; + +TimelineRenderPass::State *TimelineSelectionRenderPass::update( + const TimelineAbstractRenderer *renderer, const TimelineRenderState *parentState, + State *oldState, int firstIndex, int lastIndex, bool stateChanged, float spacing) const +{ + Q_UNUSED(stateChanged); + + const TimelineModel *model = renderer->model(); + if (!model || model->isEmpty()) + return oldState; + + TimelineSelectionRenderPassState *state; + + if (oldState == 0) + state = new TimelineSelectionRenderPassState; + else + state = static_cast<TimelineSelectionRenderPassState *>(oldState); + + int selectedItem = renderer->selectedItem(); + QSGGeometryNode *node = static_cast<QSGGeometryNode *>(model->expanded() ? + state->expandedOverlay() : + state->collapsedOverlay()); + + if (selectedItem != -1 && selectedItem >= firstIndex && selectedItem < lastIndex) { + qreal top = 0; + qreal height = 0; + if (model->expanded()) { + int row = model->expandedRow(selectedItem); + int rowHeight = model->expandedRowHeight(row); + height = rowHeight * model->relativeHeight(selectedItem); + top = (model->expandedRowOffset(row) + rowHeight) - height; + } else { + int row = model->collapsedRow(selectedItem); + height = TimelineModel::defaultRowHeight() * model->relativeHeight(selectedItem); + top = TimelineModel::defaultRowHeight() * (row + 1) - height; + } + + qint64 startTime = qBound(parentState->start(), model->startTime(selectedItem), + parentState->end()); + qint64 endTime = qBound(parentState->start(), model->endTime(selectedItem), + parentState->end()); + qint64 left = startTime - parentState->start(); + qint64 width = endTime - startTime; + + // Construct from upper left and lower right for better precision. When constructing from + // left and width the error on the left border is inherited by the right border. Like this + // they're independent. + + QRectF position(left * parentState->scale(), top, width * parentState->scale(), height); + + QColor itemColor = model->color(selectedItem); + uchar red = itemColor.red(); + uchar green = itemColor.green(); + uchar blue = itemColor.blue(); + int selectionId = model->selectionId(selectedItem); + + OpaqueColoredPoint2DWithSize *v = OpaqueColoredPoint2DWithSize::fromVertexData( + node->geometry()); + v[0].set(position.left(), position.bottom(), -position.width(), -position.height(), + selectionId, red, green, blue, 255); + v[1].set(position.right(), position.bottom(), position.width(), -position.height(), + selectionId, red, green, blue, 255); + v[2].set(position.left(), position.top(), -position.width(), position.height(), + selectionId, red, green, blue, 255); + v[3].set(position.right(), position.top(), position.width(), position.height(), + selectionId, red, green, blue, 255); + state->material()->setSelectionColor(renderer->selectionLocked() ? QColor(96,0,255) : + Qt::blue); + state->material()->setSelectedItem(selectionId); + state->material()->setScale(QVector2D(spacing / parentState->scale(), 1)); + node->markDirty(QSGNode::DirtyMaterial | QSGNode::DirtyGeometry); + } else { + OpaqueColoredPoint2DWithSize *v = OpaqueColoredPoint2DWithSize::fromVertexData( + node->geometry()); + for (int i = 0; i < 4; ++i) + v[i].set(0, 0, 0, 0, 0, 0, 0, 0, 0); + node->markDirty(QSGNode::DirtyGeometry); + } + return state; +} + +const TimelineSelectionRenderPass *TimelineSelectionRenderPass::instance() +{ + static const TimelineSelectionRenderPass pass; + return &pass; +} + +TimelineSelectionRenderPass::TimelineSelectionRenderPass() +{ +} + +TimelineSelectionRenderPassState::TimelineSelectionRenderPassState() : + m_expandedOverlay(0), m_collapsedOverlay(0) +{ + m_expandedOverlay = createSelectionNode(&m_material); + m_collapsedOverlay = createSelectionNode(&m_material); +} + +TimelineSelectionRenderPassState::~TimelineSelectionRenderPassState() +{ + delete m_collapsedOverlay; + delete m_expandedOverlay; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelineselectionrenderpass.h b/src/libs/tracing/timelineselectionrenderpass.h new file mode 100644 index 00000000000..1a65ab3e451 --- /dev/null +++ b/src/libs/tracing/timelineselectionrenderpass.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timelineabstractrenderer.h" +#include "timelinerenderpass.h" +#include "timelinerenderstate.h" + +namespace Timeline { + +class TRACING_EXPORT TimelineSelectionRenderPass : public TimelineRenderPass +{ +public: + static const TimelineSelectionRenderPass *instance(); + + State *update(const TimelineAbstractRenderer *renderer, const TimelineRenderState *parentState, + State *state, int firstIndex, int lastIndex, bool stateChanged, + float spacing) const; + +protected: + TimelineSelectionRenderPass(); +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetheme.cpp b/src/libs/tracing/timelinetheme.cpp new file mode 100644 index 00000000000..22d8697b782 --- /dev/null +++ b/src/libs/tracing/timelinetheme.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinetheme.h" + +#include <utils/icon.h> +#include <utils/qtcassert.h> +#include <utils/utilsicons.h> +#include <utils/theme/theme.h> + +#include <QIcon> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQmlPropertyMap> +#include <QQuickImageProvider> +#include <QtQml> + +namespace Timeline { + +class TimelineImageIconProvider : public QQuickImageProvider +{ +public: + TimelineImageIconProvider() + : QQuickImageProvider(Pixmap) + { + } + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override + { + Q_UNUSED(requestedSize) + + const QStringList idElements = id.split(QLatin1Char('/')); + + QTC_ASSERT(!idElements.isEmpty(), return QPixmap()); + const QString &iconName = idElements.first(); + const QIcon::Mode iconMode = (idElements.count() > 1 + && idElements.at(1) == QLatin1String("disabled")) + ? QIcon::Disabled : QIcon::Normal; + + Utils::Icon icon; + if (iconName == QLatin1String("prev")) + icon = Utils::Icons::PREV_TOOLBAR; + else if (iconName == QLatin1String("next")) + icon = Utils::Icons::NEXT_TOOLBAR; + else if (iconName == QLatin1String("zoom")) + icon = Utils::Icons::ZOOM_TOOLBAR; + else if (iconName == QLatin1String("rangeselection")) + icon = Utils::Icon({{QLatin1String(":/tracing/ico_rangeselection.png"), + Utils::Theme::IconsBaseColor}}); + else if (iconName == QLatin1String("rangeselected")) + icon = Utils::Icon({{QLatin1String(":/tracing/ico_rangeselected.png"), + Utils::Theme::IconsBaseColor}}); + else if (iconName == QLatin1String("selectionmode")) + icon = Utils::Icon({{QLatin1String(":/tracing/ico_selectionmode.png"), + Utils::Theme::IconsBaseColor}}); + else if (iconName == QLatin1String("edit")) + icon = Utils::Icon({{QLatin1String(":/tracing/ico_edit.png"), + Utils::Theme::IconsBaseColor}}); + else if (iconName == QLatin1String("lock_open")) + icon = Utils::Icons::UNLOCKED_TOOLBAR; + else if (iconName == QLatin1String("lock_closed")) + icon = Utils::Icons::LOCKED_TOOLBAR; + else if (iconName == QLatin1String("range_handle")) + icon = Utils::Icon({{QLatin1String(":/tracing/range_handle.png"), + Utils::Theme::IconsBaseColor}}); + else if (iconName == QLatin1String("note")) + icon = Utils::Icons::INFO_TOOLBAR; + else if (iconName == QLatin1String("split")) + icon = Utils::Icons::SPLIT_HORIZONTAL_TOOLBAR; + else if (iconName == QLatin1String("close_split")) + icon = Utils::Icons::CLOSE_SPLIT_TOP; + else if (iconName == QLatin1String("close_window")) + icon = Utils::Icons::CLOSE_TOOLBAR; + + const QSize iconSize(16, 16); + const QPixmap result = icon.icon().pixmap(iconSize, iconMode); + + if (size) + *size = result.size(); + return result; + } +}; + +static QObject *singletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine); + Q_UNUSED(scriptEngine); + return Utils::proxyTheme(); +} + +void TimelineTheme::setupTheme(QQmlEngine *engine) +{ + static const int typeIndex = qmlRegisterSingletonType<Utils::Theme>("TimelineTheme", 1, 0, + "Theme", singletonProvider); + Q_UNUSED(typeIndex); + engine->addImageProvider(QLatin1String("icons"), new TimelineImageIconProvider); +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetheme.h b/src/libs/tracing/timelinetheme.h new file mode 100644 index 00000000000..059bfabad70 --- /dev/null +++ b/src/libs/tracing/timelinetheme.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" + +QT_FORWARD_DECLARE_CLASS(QQmlEngine) + +namespace Timeline { + +class TRACING_EXPORT TimelineTheme { +public: + static void setupTheme(QQmlEngine* engine); +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetracefile.cpp b/src/libs/tracing/timelinetracefile.cpp new file mode 100644 index 00000000000..45fc4bbbe17 --- /dev/null +++ b/src/libs/tracing/timelinetracefile.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinetracefile.h" + +namespace Timeline { + +TimelineTraceFile::TimelineTraceFile(QObject *parent) : QObject(parent) +{ +} + +void TimelineTraceFile::setFuture(const QFutureInterface<void> &future) +{ + m_future = future; + m_future.setProgressRange(MinimumProgress, MaximumProgress); + m_future.setProgressValue(MinimumProgress); +} + +QFutureInterface<void> &TimelineTraceFile::future() +{ + return m_future; +} + +bool TimelineTraceFile::isProgressUpdateNeeded() const +{ + return m_future.isProgressUpdateNeeded() || m_future.progressValue() == 0; +} + +void TimelineTraceFile::addProgressValue(int progressValue) +{ + m_future.setProgressValue(qMax(static_cast<int>(MinimumProgress), + qMin(m_future.progressValue() + progressValue, + static_cast<int>(MaximumProgress)))); +} + +void TimelineTraceFile::setDeviceProgress(QIODevice *device) +{ + m_future.setProgressValue(MinimumProgress + device->pos() + * (MaximumProgress - MinimumProgress) / device->size()); +} + +bool TimelineTraceFile::isCanceled() const +{ + return m_future.isCanceled(); +} + +void TimelineTraceFile::setTraceTime(qint64 traceStart, qint64 traceEnd, qint64 measuredTime) +{ + m_traceStart = traceStart; + m_traceEnd = traceEnd; + m_measuredTime = measuredTime; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetracefile.h b/src/libs/tracing/timelinetracefile.h new file mode 100644 index 00000000000..799cd6bab97 --- /dev/null +++ b/src/libs/tracing/timelinetracefile.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" + +#include <QFutureInterface> +#include <QObject> + +QT_FORWARD_DECLARE_CLASS(QIODevice) + +namespace Timeline { + +class TimelineNotesModel; +class TimelineTraceManager; + +class TRACING_EXPORT TimelineTraceFile : public QObject +{ + Q_OBJECT +public: + enum ProgressValues { + MinimumProgress = 0, + MaximumProgress = 1000 + }; + + explicit TimelineTraceFile(QObject *parent = 0); + + void setTraceManager(TimelineTraceManager *traceManager) { m_traceManager = traceManager; } + TimelineTraceManager *traceManager() const { return m_traceManager; } + + void setNotes(TimelineNotesModel *notes) { m_notes = notes; } + TimelineNotesModel *notes() const { return m_notes; } + + void setTraceTime(qint64 startTime, qint64 endTime, qint64 measturedTime); + void setFuture(const QFutureInterface<void> &future); + QFutureInterface<void> &future(); + + bool isProgressUpdateNeeded() const; + void addProgressValue(int progressValue); + void setDeviceProgress(QIODevice *device); + + virtual void save(QIODevice *device) = 0; + virtual void load(QIODevice *device) = 0; + + void setTraceStart(qint64 traceStart) { m_traceStart = traceStart; } + qint64 traceStart() const { return m_traceStart; } + + void setTraceEnd(qint64 traceEnd) { m_traceEnd = traceEnd; } + qint64 traceEnd() const { return m_traceEnd; } + qint64 measuredTime() const { return m_measuredTime; } + + bool isCanceled() const; + + quint64 loadedFeatures() const { return m_loadedFeatures; } + void addFeature(quint8 feature) { m_loadedFeatures |= (1ull << feature); } + +signals: + void error(const QString &error); + void success(); + void canceled(); + +private: + qint64 m_traceStart = -1; + qint64 m_traceEnd = -1; + qint64 m_measuredTime = -1; + quint64 m_loadedFeatures = 0; + + QFutureInterface<void> m_future; + + TimelineTraceManager *m_traceManager = nullptr; + TimelineNotesModel *m_notes = nullptr; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp new file mode 100644 index 00000000000..5a81d5a1f64 --- /dev/null +++ b/src/libs/tracing/timelinetracemanager.cpp @@ -0,0 +1,433 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinetracemanager.h" +#include "timelinetracefile.h" + +#include <utils/qtcassert.h> +#include <utils/temporaryfile.h> +#include <utils/runextensions.h> + +#include <QFile> +#include <QDataStream> + +namespace Timeline { + +class TimelineTraceManager::TimelineTraceManagerPrivate +{ +public: + TimelineNotesModel *notesModel = nullptr; + + int numEvents = 0; + int numEventTypes = 0; + quint64 availableFeatures = 0; + quint64 visibleFeatures = 0; + quint64 recordedFeatures = 0; + bool aggregateTraces = false; + + QHash<quint8, QVector<TraceEventLoader>> eventLoaders; + QVector<Initializer> initializers; + QVector<Finalizer> finalizers; + QVector<Clearer> clearers; + + qint64 traceStart = -1; + qint64 traceEnd = -1; + qint64 restrictedTraceStart = -1; + qint64 restrictedTraceEnd = -1; + + void dispatch(const TraceEvent &event, const TraceEventType &type); + void reset(); + void updateTraceTime(qint64 time); + void restrictTraceTimeToRange(qint64 start, qint64 end); +}; + +TimelineTraceManager::TimelineTraceManager(QObject *parent) : + QObject(parent), d(new TimelineTraceManagerPrivate) +{ +} + +TimelineTraceManager::~TimelineTraceManager() +{ + delete d; +} + +TimelineNotesModel *TimelineTraceManager::notesModel() const +{ + return d->notesModel; +} + +bool TimelineTraceManager::isEmpty() const +{ + return d->numEvents == 0; +} + +int TimelineTraceManager::numEvents() const +{ + return d->numEvents; +} + +int TimelineTraceManager::numEventTypes() const +{ + return d->numEventTypes; +} + +void TimelineTraceManager::addEvent(const TraceEvent &event) +{ + d->dispatch(event, lookupType(event.typeIndex())); +} + +void TimelineTraceManager::addEventType(const TraceEventType &type) +{ + const quint8 feature = type.feature(); + d->recordedFeatures |= (1ull << feature); + ++(d->numEventTypes); +} + +void TimelineTraceManager::registerFeatures(quint64 features, TraceEventLoader eventLoader, + Initializer initializer, Finalizer finalizer, + Clearer clearer) +{ + if ((features & d->availableFeatures) != features) { + d->availableFeatures |= features; + emit availableFeaturesChanged(d->availableFeatures); + } + + if ((features & d->visibleFeatures) != features) { + d->visibleFeatures |= features; + emit visibleFeaturesChanged(d->visibleFeatures); + } + + for (quint8 feature = 0; feature != sizeof(features) * 8; ++feature) { + if (features & (1ULL << feature)) { + if (eventLoader) + d->eventLoaders[feature].append(eventLoader); + } + } + + if (initializer) + d->initializers.append(initializer); + + if (finalizer) + d->finalizers.append(finalizer); + + if (clearer) + d->clearers.append(clearer); +} + +quint64 TimelineTraceManager::availableFeatures() const +{ + return d->availableFeatures; +} + +quint64 TimelineTraceManager::visibleFeatures() const +{ + return d->visibleFeatures; +} + +void TimelineTraceManager::setVisibleFeatures(quint64 features) +{ + if (d->visibleFeatures != features) { + d->visibleFeatures = features; + emit visibleFeaturesChanged(d->visibleFeatures); + } +} + +quint64 TimelineTraceManager::recordedFeatures() const +{ + return d->recordedFeatures; +} + +void TimelineTraceManager::setRecordedFeatures(quint64 features) +{ + if (d->recordedFeatures != features) { + d->recordedFeatures = features; + emit recordedFeaturesChanged(d->recordedFeatures); + } +} + +bool TimelineTraceManager::aggregateTraces() const +{ + return d->aggregateTraces; +} + +void TimelineTraceManager::setAggregateTraces(bool aggregateTraces) +{ + d->aggregateTraces = aggregateTraces; +} + +void TimelineTraceManager::initialize() +{ + for (const Initializer &initializer : qAsConst(d->initializers)) + initializer(); +} + +void TimelineTraceManager::finalize() +{ + // Load notes after the timeline models have been initialized ... + // which happens on stateChanged(Done). + + for (const Finalizer &finalizer : qAsConst(d->finalizers)) + finalizer(); +} + +QFuture<void> TimelineTraceManager::save(const QString &filename) +{ + QFile *file = new QFile(filename); + if (!file->open(QIODevice::WriteOnly)) { + delete file; + return Utils::runAsync([this, filename](QFutureInterface<void> &future) { + future.setProgressRange(0, 1); + future.setProgressValue(1); + emit error(tr("Could not open %1 for writing.").arg(filename)); + emit saveFinished(); + }); + } + + TimelineTraceFile *writer = createTraceFile(); + writer->setTraceTime(traceStart(), traceEnd(), traceDuration()); + writer->setTraceManager(this); + writer->setNotes(d->notesModel); + + connect(writer, &QObject::destroyed, this, &TimelineTraceManager::saveFinished, + Qt::QueuedConnection); + + connect(writer, &TimelineTraceFile::error, this, [this, file](const QString &message) { + file->close(); + file->remove(); + delete file; + emit error(message); + }, Qt::QueuedConnection); + + connect(writer, &TimelineTraceFile::success, this, [file]() { + file->close(); + delete file; + }, Qt::QueuedConnection); + + connect(writer, &TimelineTraceFile::canceled, this, [file]() { + file->close(); + file->remove(); + delete file; + }, Qt::QueuedConnection); + + return Utils::runAsync([file, writer] (QFutureInterface<void> &future) { + writer->setFuture(future); + writer->save(file); + writer->deleteLater(); + }); +} + +QFuture<void> TimelineTraceManager::load(const QString &filename) +{ + QFile *file = new QFile(filename, this); + if (!file->open(QIODevice::ReadOnly)) { + delete file; + return Utils::runAsync([this, filename] (QFutureInterface<void> &future) { + future.setProgressRange(0, 1); + future.setProgressValue(1); + emit error(tr("Could not open %1 for reading.").arg(filename)); + emit loadFinished(); + }); + } + + clearAll(); + initialize(); + TimelineTraceFile *reader = createTraceFile(); + reader->setTraceManager(this); + reader->setNotes(d->notesModel); + + connect(reader, &QObject::destroyed, this, &TimelineTraceManager::loadFinished, + Qt::QueuedConnection); + + connect(reader, &TimelineTraceFile::success, this, [this, reader]() { + if (reader->traceStart() >= 0) + decreaseTraceStart(reader->traceStart()); + if (reader->traceEnd() >= 0) + increaseTraceEnd(reader->traceEnd()); + finalize(); + delete reader; + }, Qt::QueuedConnection); + + connect(reader, &TimelineTraceFile::error, this, [this, reader](const QString &message) { + clearAll(); + delete reader; + emit error(message); + }, Qt::QueuedConnection); + + connect(reader, &TimelineTraceFile::canceled, this, [this, reader]() { + clearAll(); + delete reader; + }, Qt::QueuedConnection); + + return Utils::runAsync([file, reader] (QFutureInterface<void> &future) { + reader->setFuture(future); + reader->load(file); + file->close(); + file->deleteLater(); + }); +} + +qint64 TimelineTraceManager::traceStart() const +{ + return d->restrictedTraceStart != -1 ? d->restrictedTraceStart : d->traceStart; +} + +qint64 TimelineTraceManager::traceEnd() const +{ + return d->restrictedTraceEnd != -1 ? d->restrictedTraceEnd : d->traceEnd; +} + +qint64 TimelineTraceManager::traceDuration() const +{ + return traceEnd() - traceStart(); +} + +void TimelineTraceManager::decreaseTraceStart(qint64 start) +{ + QTC_ASSERT(start >= 0, return); + if (d->traceStart > start || d->traceStart == -1) { + d->traceStart = start; + if (d->traceEnd == -1) + d->traceEnd = d->traceStart; + else + QTC_ASSERT(d->traceEnd >= d->traceStart, d->traceEnd = d->traceStart); + } +} + +void TimelineTraceManager::increaseTraceEnd(qint64 end) +{ + QTC_ASSERT(end >= 0, return); + if (d->traceEnd < end || d->traceEnd == -1) { + d->traceEnd = end; + if (d->traceStart == -1) + d->traceStart = d->traceEnd; + else + QTC_ASSERT(d->traceEnd >= d->traceStart, d->traceStart = d->traceEnd); + } +} + +void TimelineTraceManager::TimelineTraceManagerPrivate::updateTraceTime(qint64 time) +{ + QTC_ASSERT(time >= 0, return); + if (traceStart > time || traceStart == -1) + traceStart = time; + if (traceEnd < time || traceEnd == -1) + traceEnd = time; + QTC_ASSERT(traceEnd >= traceStart, traceStart = traceEnd); +} + +void TimelineTraceManager::TimelineTraceManagerPrivate::restrictTraceTimeToRange(qint64 start, + qint64 end) +{ + QTC_ASSERT(end == -1 || start <= end, end = start); + restrictedTraceStart = start; + restrictedTraceEnd = end; +} + +void TimelineTraceManager::setNotesModel(TimelineNotesModel *notesModel) +{ + d->notesModel = notesModel; +} + +void TimelineTraceManager::clearEventStorage() +{ + d->reset(); + if (d->notesModel) + d->notesModel->clear(); + setVisibleFeatures(0); + setRecordedFeatures(0); +} + +void TimelineTraceManager::clearTypeStorage() +{ + d->numEventTypes = 0; + d->recordedFeatures = 0; +} + +void TimelineTraceManager::clear() +{ + clearEventStorage(); +} + +void TimelineTraceManager::clearAll() +{ + clearEventStorage(); + clearTypeStorage(); +} + +void TimelineTraceManager::restrictToRange(qint64 startTime, qint64 endTime) +{ + if (d->notesModel) + d->notesModel->stash(); + + d->reset(); + setVisibleFeatures(0); + + QFutureInterface<void> future; + replayEvents(startTime, endTime, + std::bind(&TimelineTraceManagerPrivate::dispatch, d, std::placeholders::_1, + std::placeholders::_2), + [this, startTime, endTime]() { + d->restrictTraceTimeToRange(startTime, endTime); + initialize(); + }, [this]() { + if (d->notesModel) + d->notesModel->restore(); + finalize(); + }, [this](const QString &message) { + emit error(tr("Could not re-read events from temporary trace file: %1\n" + "The trace data is lost.").arg(message)); + clearAll(); + }, future); +} + +bool TimelineTraceManager::isRestrictedToRange() const +{ + return d->restrictedTraceStart != -1 || d->restrictedTraceEnd != -1; +} + +void TimelineTraceManager::TimelineTraceManagerPrivate::dispatch(const TraceEvent &event, + const TraceEventType &type) +{ + for (const TraceEventLoader &loader : eventLoaders[type.feature()]) + loader(event, type); + + if (event.timestamp() >= 0) + updateTraceTime(event.timestamp()); + ++numEvents; +} + +void TimelineTraceManager::TimelineTraceManagerPrivate::reset() +{ + restrictTraceTimeToRange(-1, -1); + traceStart = -1; + traceEnd = -1; + + for (const Clearer &clearer : qAsConst(clearers)) + clearer(); + + numEvents = 0; +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinetracemanager.h b/src/libs/tracing/timelinetracemanager.h new file mode 100644 index 00000000000..ddba03f6f74 --- /dev/null +++ b/src/libs/tracing/timelinetracemanager.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include "timelinenotesmodel.h" +#include "traceevent.h" +#include "traceeventtype.h" + +#include <QObject> +#include <QFuture> + +#include <functional> + +namespace Timeline { + +class TimelineTraceFile; +class TRACING_EXPORT TimelineTraceManager : public QObject +{ + Q_OBJECT +public: + typedef std::function<void(const TraceEvent &, const TraceEventType &)> TraceEventLoader; + typedef std::function<void()> Initializer; + typedef std::function<void()> Finalizer; + typedef std::function<void()> Clearer; + typedef std::function<void(const QString &)> ErrorHandler; + + explicit TimelineTraceManager(QObject *parent = nullptr); + ~TimelineTraceManager(); + + qint64 traceStart() const; + qint64 traceEnd() const; + qint64 traceDuration() const; + void decreaseTraceStart(qint64 start); + void increaseTraceEnd(qint64 end); + + void setNotesModel(TimelineNotesModel *notesModel); + TimelineNotesModel *notesModel() const; + + bool isEmpty() const; + int numEvents() const; + int numEventTypes() const; + + void registerFeatures(quint64 features, TraceEventLoader eventLoader, + Initializer initializer = Initializer(), + Finalizer finalizer = Finalizer(), + Clearer clearer = Clearer()); + + quint64 availableFeatures() const; + quint64 visibleFeatures() const; + void setVisibleFeatures(quint64 features); + quint64 recordedFeatures() const; + void setRecordedFeatures(quint64 features); + bool aggregateTraces() const; + void setAggregateTraces(bool aggregateTraces); + + virtual void initialize(); + virtual void finalize(); + virtual void clear(); + + void clearAll(); + void restrictToRange(qint64 startTime, qint64 endTime); + bool isRestrictedToRange() const; + + QFuture<void> save(const QString &filename); + QFuture<void> load(const QString &filename); + +signals: + void error(const QString &error); + void loadFinished(); + void saveFinished(); + + void availableFeaturesChanged(quint64 features); + void visibleFeaturesChanged(quint64 features); + void recordedFeaturesChanged(quint64 features); + +protected: + virtual void clearEventStorage(); + virtual void clearTypeStorage(); + + void addEvent(const TraceEvent &event); + void addEventType(const TraceEventType &type); + virtual const TraceEventType &lookupType(int typeId) const = 0; + + virtual TimelineTraceFile *createTraceFile() = 0; + virtual void replayEvents(qint64 rangeStart, qint64 rangeEnd, TraceEventLoader loader, + Initializer initializer, Finalizer finalizer, + ErrorHandler errorHandler, QFutureInterface<void> &future) const = 0; + +private: + class TimelineTraceManagerPrivate; + TimelineTraceManagerPrivate *d; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/timelinezoomcontrol.cpp b/src/libs/tracing/timelinezoomcontrol.cpp new file mode 100644 index 00000000000..973a88624e5 --- /dev/null +++ b/src/libs/tracing/timelinezoomcontrol.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timelinezoomcontrol.h" +#include <utils/qtcassert.h> + +namespace Timeline { + +TimelineZoomControl::TimelineZoomControl(QObject *parent) : QObject(parent), + m_traceStart(-1), m_traceEnd(-1), m_windowStart(-1), m_windowEnd(-1), + m_rangeStart(-1), m_rangeEnd(-1), m_selectionStart(-1), m_selectionEnd(-1), + m_windowLocked(false) +{ + connect(&m_timer, &QTimer::timeout, this, &TimelineZoomControl::moveWindow); +} + +void TimelineZoomControl::clear() +{ + bool changeTrace = (m_traceStart != -1 || m_traceEnd != -1); + bool changeWindow = (m_windowStart != -1 || m_windowEnd != -1); + bool changeRange = (m_rangeStart != -1 || m_rangeEnd != -1); + + setWindowLocked(false); + if (changeWindow && !m_timer.isActive()) + emit windowMovingChanged(true); + + m_traceStart = m_traceEnd = m_windowStart = m_windowEnd = m_rangeStart = m_rangeEnd = -1; + if (changeTrace) + emit traceChanged(-1, -1); + + if (changeWindow) { + emit windowChanged(-1, -1); + m_timer.stop(); + emit windowMovingChanged(false); + } else { + QTC_ASSERT(!m_timer.isActive(), m_timer.stop()); + } + + if (changeRange) + emit rangeChanged(-1, -1); + + setSelection(-1, -1); +} + +void TimelineZoomControl::setTrace(qint64 start, qint64 end) +{ + Q_ASSERT(start <= end); + if (start != m_traceStart || end != m_traceEnd) { + m_traceStart = start; + m_traceEnd = end; + emit traceChanged(start, end); + rebuildWindow(); + } +} + +void TimelineZoomControl::setRange(qint64 start, qint64 end) +{ + Q_ASSERT(start <= end); + if (m_rangeStart != start || m_rangeEnd != end) { + if (m_timer.isActive()) { + m_timer.stop(); + emit windowMovingChanged(false); + } + m_rangeStart = start; + m_rangeEnd = end; + rebuildWindow(); + if (m_rangeStart == start && m_rangeEnd == end) + emit rangeChanged(m_rangeStart, m_rangeEnd); + // otherwise rebuildWindow() has changed it again. + } +} + +void TimelineZoomControl::setSelection(qint64 start, qint64 end) +{ + if (m_selectionStart != start || m_selectionEnd != end) { + m_selectionStart = start; + m_selectionEnd = end; + emit selectionChanged(start, end); + } +} + +void TimelineZoomControl::setWindowLocked(bool windowLocked) +{ + if (windowLocked != m_windowLocked) { + m_windowLocked = windowLocked; + emit windowLockedChanged(windowLocked); + } +} + +void TimelineZoomControl::rebuildWindow() +{ + qint64 minDuration = 1; // qMax needs equal data types, so literal 1 won't do + qint64 shownDuration = qMax(rangeDuration(), minDuration); + + qint64 oldWindowStart = m_windowStart; + qint64 oldWindowEnd = m_windowEnd; + if (traceDuration() / shownDuration < maximumZoomFactor()) { + m_windowStart = m_traceStart; + m_windowEnd = m_traceEnd; + } else if (windowDuration() / shownDuration > maximumZoomFactor() || + windowDuration() / shownDuration * 2 < maximumZoomFactor() || + m_rangeStart < m_windowStart || m_rangeEnd > m_windowEnd) { + qint64 keep = shownDuration * maximumZoomFactor() / 2 - shownDuration; + m_windowStart = m_rangeStart - keep; + if (m_windowStart < m_traceStart) { + keep += m_traceStart - m_windowStart; + m_windowStart = m_traceStart; + } + + m_windowEnd = m_rangeEnd + keep; + if (m_windowEnd > m_traceEnd) { + m_windowStart = qMax(m_traceStart, m_windowStart - (m_windowEnd - m_traceEnd)); + m_windowEnd = m_traceEnd; + } + } else { + m_timer.start(501); + } + if (oldWindowStart != m_windowStart || oldWindowEnd != m_windowEnd) { + bool runTimer = m_timer.isActive(); + if (!runTimer) + m_timer.start(std::numeric_limits<int>::max()); + emit windowMovingChanged(true); + clampRangeToWindow(); // can stop the timer + emit windowChanged(m_windowStart, m_windowEnd); + if (!runTimer && m_timer.isActive()) { + m_timer.stop(); + emit windowMovingChanged(false); + } + } +} + +void TimelineZoomControl::moveWindow() +{ + if (m_windowLocked) + return; + m_timer.stop(); + + qint64 offset = (m_rangeEnd - m_windowEnd + m_rangeStart - m_windowStart) / 2; + if (offset == 0 || (offset < 0 && m_windowStart == m_traceStart) || + (offset > 0 && m_windowEnd == m_traceEnd)) { + emit windowMovingChanged(false); + return; + } else if (offset > rangeDuration()) { + offset = (offset + rangeDuration()) / 2; + } else if (offset < -rangeDuration()) { + offset = (offset - rangeDuration()) / 2; + } + m_windowStart += offset; + if (m_windowStart < m_traceStart) { + m_windowEnd += m_traceStart - m_windowStart; + m_windowStart = m_traceStart; + } + m_windowEnd += offset; + if (m_windowEnd > m_traceEnd) { + m_windowStart -= m_windowEnd - m_traceEnd; + m_windowEnd = m_traceEnd; + } + + clampRangeToWindow(); + emit windowChanged(m_windowStart, m_windowEnd); + m_timer.start(100); +} + +void TimelineZoomControl::clampRangeToWindow() +{ + qint64 rangeStart = qMin(qMax(m_rangeStart, m_windowStart), m_windowEnd); + qint64 rangeEnd = qMin(qMax(rangeStart, m_rangeEnd), m_windowEnd); + if (rangeStart != m_rangeStart || rangeEnd != m_rangeEnd) + setRange(rangeStart, rangeEnd); +} + +} // namespace Timeline diff --git a/src/libs/tracing/timelinezoomcontrol.h b/src/libs/tracing/timelinezoomcontrol.h new file mode 100644 index 00000000000..68300b84388 --- /dev/null +++ b/src/libs/tracing/timelinezoomcontrol.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" +#include <QTimer> + +namespace Timeline { + +class TRACING_EXPORT TimelineZoomControl : public QObject { + Q_OBJECT + + Q_PROPERTY(qint64 traceStart READ traceStart NOTIFY traceChanged) + Q_PROPERTY(qint64 traceEnd READ traceEnd NOTIFY traceChanged) + Q_PROPERTY(qint64 traceDuration READ traceDuration NOTIFY traceChanged) + + Q_PROPERTY(qint64 windowStart READ windowStart NOTIFY windowChanged) + Q_PROPERTY(qint64 windowEnd READ windowEnd NOTIFY windowChanged) + Q_PROPERTY(qint64 windowDuration READ windowDuration NOTIFY windowChanged) + + Q_PROPERTY(qint64 rangeStart READ rangeStart NOTIFY rangeChanged) + Q_PROPERTY(qint64 rangeEnd READ rangeEnd NOTIFY rangeChanged) + Q_PROPERTY(qint64 rangeDuration READ rangeDuration NOTIFY rangeChanged) + + Q_PROPERTY(qint64 selectionStart READ selectionStart NOTIFY selectionChanged) + Q_PROPERTY(qint64 selectionEnd READ selectionEnd NOTIFY selectionChanged) + Q_PROPERTY(qint64 selectionDuration READ selectionDuration NOTIFY selectionChanged) + + Q_PROPERTY(bool windowLocked READ windowLocked WRITE setWindowLocked NOTIFY windowLockedChanged) + Q_PROPERTY(bool windowMoving READ windowMoving NOTIFY windowMovingChanged) + + Q_PROPERTY(qint64 maximumZoomFactor READ maximumZoomFactor CONSTANT) + Q_PROPERTY(qint64 minimumRangeLength READ minimumRangeLength CONSTANT) + +public: + qint64 maximumZoomFactor() const { return 1 << 10; } + qint64 minimumRangeLength() const { return 500; } + + TimelineZoomControl(QObject *parent = 0); + qint64 traceStart() const { return m_traceStart; } + qint64 traceEnd() const { return m_traceEnd; } + qint64 traceDuration() const { return m_traceEnd - m_traceStart; } + + qint64 windowStart() const { return m_windowStart; } + qint64 windowEnd() const { return m_windowEnd; } + qint64 windowDuration() const { return m_windowEnd - m_windowStart; } + + qint64 rangeStart() const { return m_rangeStart; } + qint64 rangeEnd() const { return m_rangeEnd; } + qint64 rangeDuration() const { return m_rangeEnd - m_rangeStart; } + + qint64 selectionStart() const { return m_selectionStart; } + qint64 selectionEnd() const { return m_selectionEnd; } + qint64 selectionDuration() const { return m_selectionEnd - m_selectionStart; } + + bool windowLocked() const { return m_windowLocked; } + bool windowMoving() const { return m_timer.isActive(); } + + virtual void clear(); + + Q_INVOKABLE void setTrace(qint64 start, qint64 end); + Q_INVOKABLE void setRange(qint64 start, qint64 end); + Q_INVOKABLE void setSelection(qint64 start, qint64 end); + void setWindowLocked(bool windowLocked); + +signals: + void traceChanged(qint64 start, qint64 end); + void windowChanged(qint64 start, qint64 end); + void rangeChanged(qint64 start, qint64 end); + void selectionChanged(qint64 start, qint64 end); + void windowLockedChanged(bool windowLocked); + void windowMovingChanged(bool windowMoving); + +protected: + void moveWindow(); + void rebuildWindow(); + void clampRangeToWindow(); + + qint64 m_traceStart; + qint64 m_traceEnd; + qint64 m_windowStart; + qint64 m_windowEnd; + qint64 m_rangeStart; + qint64 m_rangeEnd; + qint64 m_selectionStart; + qint64 m_selectionEnd; + + QTimer m_timer; + bool m_windowLocked; +}; + +} // namespace Timeline diff --git a/src/libs/tracing/traceevent.h b/src/libs/tracing/traceevent.h new file mode 100644 index 00000000000..f80ba232bbc --- /dev/null +++ b/src/libs/tracing/traceevent.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" + +#include <QHash> +#include <QMetaType> + +namespace Timeline { + +class TraceEvent +{ +public: + TraceEvent(qint64 timestamp = -1, qint32 typeIndex = -1) + : m_timestamp(timestamp), m_typeIndex(typeIndex) + {} + + qint64 timestamp() const { return m_timestamp; } + void setTimestamp(qint64 timestamp) { m_timestamp = timestamp; } + + qint32 typeIndex() const { return m_typeIndex; } + void setTypeIndex(qint32 typeIndex) { m_typeIndex = typeIndex; } + + bool isValid() const { return m_typeIndex != -1; } + +private: + qint64 m_timestamp; + qint32 m_typeIndex; +}; + +} // namespace Timeline + +Q_DECLARE_METATYPE(Timeline::TraceEvent) + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(Timeline::TraceEvent, Q_MOVABLE_TYPE); +QT_END_NAMESPACE diff --git a/src/libs/tracing/traceeventtype.h b/src/libs/tracing/traceeventtype.h new file mode 100644 index 00000000000..119b5dfa9cd --- /dev/null +++ b/src/libs/tracing/traceeventtype.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "tracing_global.h" + +#include <QHash> +#include <QMetaType> +#include <QString> + +namespace Timeline { + +class TraceEventType +{ +public: + TraceEventType(quint8 feature = 255, const QString &displayName = QString()) + : m_displayName(displayName), m_feature(feature) + {} + + const QString &displayName() const { return m_displayName; } + void setDisplayName(const QString &displayName) { m_displayName = displayName; } + + quint8 feature() const { return m_feature; } + void setFeature(quint8 feature) { m_feature = feature; } + +private: + QString m_displayName; + quint8 m_feature; +}; + +} // namespace Timeline + +Q_DECLARE_METATYPE(Timeline::TraceEventType) + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(Timeline::TraceEventType, Q_MOVABLE_TYPE); +QT_END_NAMESPACE diff --git a/src/libs/tracing/tracestashfile.h b/src/libs/tracing/tracestashfile.h new file mode 100644 index 00000000000..d860ece9221 --- /dev/null +++ b/src/libs/tracing/tracestashfile.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <utils/temporaryfile.h> + +#include <QFile> +#include <QDataStream> + +namespace Timeline { + +template<typename Event> +class TraceStashFile +{ +public: + TraceStashFile(const QString &pattern) : file(pattern) {} + + bool open() + { + if (!file.open()) + return false; + + stream.setDevice(&file); + return true; + } + + void append(const Event &event) + { + stream << event; + } + + enum ReplayResult { + ReplaySuccess, + ReplayOpenFailed, + ReplayLoadFailed, + ReplayReadPastEnd + }; + + template<typename Loader> + ReplayResult replay(const Loader &loader) const + { + QFile readFile(file.fileName()); + if (!readFile.open(QIODevice::ReadOnly)) + return ReplayOpenFailed; + + QDataStream readStream(&readFile); + Event event; + while (!readStream.atEnd()) { + readStream >> event; + if (readStream.status() == QDataStream::ReadPastEnd) + return ReplayReadPastEnd; + if (!loader(event)) + return ReplayLoadFailed; + } + + return ReplaySuccess; + } + + void clear() + { + file.remove(); + stream.unsetDevice(); + } + + bool flush() + { + return file.flush(); + } + +private: + Utils::TemporaryFile file; + QDataStream stream; +}; + +} diff --git a/src/libs/tracing/tracing.pro b/src/libs/tracing/tracing.pro new file mode 100644 index 00000000000..1ef079a20bd --- /dev/null +++ b/src/libs/tracing/tracing.pro @@ -0,0 +1,63 @@ +QT += qml quick +DEFINES += TRACING_LIBRARY + +include(../../qtcreatorlibrary.pri) + +SOURCES += \ + $$PWD/flamegraph.cpp \ + $$PWD/timelinemodel.cpp \ + $$PWD/timelinemodelaggregator.cpp \ + $$PWD/timelinerenderer.cpp \ + $$PWD/timelinezoomcontrol.cpp \ + $$PWD/timelineitemsrenderpass.cpp \ + $$PWD/timelineselectionrenderpass.cpp \ + $$PWD/timelinenotesrenderpass.cpp \ + $$PWD/timelinerenderpass.cpp \ + $$PWD/timelinerenderstate.cpp \ + $$PWD/timelinenotesmodel.cpp \ + $$PWD/timelineabstractrenderer.cpp \ + $$PWD/timelineoverviewrenderer.cpp \ + $$PWD/timelinetheme.cpp \ + $$PWD/timelineformattime.cpp \ + $$PWD/timelinetracefile.cpp \ + $$PWD/timelinetracemanager.cpp + +HEADERS += \ + $$PWD/flamegraph.h \ + $$PWD/flamegraphattached.h \ + $$PWD/tracing_global.h \ + $$PWD/timelinemodel.h \ + $$PWD/timelinemodel_p.h \ + $$PWD/timelinemodelaggregator.h \ + $$PWD/timelinerenderer.h \ + $$PWD/timelinezoomcontrol.h \ + $$PWD/timelineitemsrenderpass.h \ + $$PWD/timelineselectionrenderpass.h \ + $$PWD/timelinenotesrenderpass.h \ + $$PWD/timelinerenderpass.h \ + $$PWD/timelinerenderstate.h \ + $$PWD/timelinenotesmodel.h \ + $$PWD/timelinenotesmodel_p.h \ + $$PWD/timelinerenderer_p.h \ + $$PWD/timelinerenderstate_p.h \ + $$PWD/timelineabstractrenderer.h \ + $$PWD/timelineabstractrenderer_p.h \ + $$PWD/timelineoverviewrenderer_p.h \ + $$PWD/timelineoverviewrenderer.h \ + $$PWD/timelinetheme.h \ + $$PWD/timelineformattime.h \ + $$PWD/timelinetracefile.h \ + $$PWD/timelinetracemanager.h \ + $$PWD/traceevent.h \ + $$PWD/traceeventtype.h \ + $$PWD/tracestashfile.h + +RESOURCES += \ + $$PWD/qml/tracing.qrc + +DISTFILES += README + +equals(TEST, 1) { + SOURCES += runscenegraphtest.cpp + HEADERS += runscenegraphtest.h +} diff --git a/src/libs/tracing/tracing.qbs b/src/libs/tracing/tracing.qbs new file mode 100644 index 00000000000..d91c765db00 --- /dev/null +++ b/src/libs/tracing/tracing.qbs @@ -0,0 +1,58 @@ +import qbs 1.0 + +import QtcLibrary + +Project { + name: "Tracing" + + QtcDevHeaders { } + + QtcLibrary { + Depends { name: "Qt"; submodules: ["qml", "quick", "gui"] } + Depends { name: "Utils" } + + Group { + name: "General" + files: [ + "README", + "flamegraph.cpp", "flamegraph.h", + "flamegraphattached.h", + "timelineabstractrenderer.cpp", "timelineabstractrenderer.h", + "timelineabstractrenderer_p.h", + "timelineformattime.cpp", "timelineformattime.h", + "timelineitemsrenderpass.cpp", "timelineitemsrenderpass.h", + "timelinemodel.cpp", "timelinemodel.h", "timelinemodel_p.h", + "timelinemodelaggregator.cpp", "timelinemodelaggregator.h", + "timelinenotesmodel.cpp", "timelinenotesmodel.h", "timelinenotesmodel_p.h", + "timelinenotesrenderpass.cpp", "timelinenotesrenderpass.h", + "timelineoverviewrenderer.cpp", "timelineoverviewrenderer.h", + "timelineoverviewrenderer_p.h", + "timelinerenderer.cpp", "timelinerenderer.h", "timelinerenderer_p.h", + "timelinerenderpass.cpp", "timelinerenderpass.h", + "timelinerenderstate.cpp", "timelinerenderstate.h", "timelinerenderstate_p.h", + "timelineselectionrenderpass.cpp", "timelineselectionrenderpass.h", + "timelinetheme.cpp", "timelinetheme.h", + "timelinetracefile.cpp", "timelinetracefile.h", + "timelinetracemanager.cpp", "timelinetracemanager.h", + "timelinezoomcontrol.cpp", "timelinezoomcontrol.h", + "traceevent.h", "traceeventtype.h", "tracestashfile.h" + ] + } + + Group { + name: "QML" + prefix: "qml/" + files: ["tracing.qrc"] + } + + Group { + name: "Unit test utilities" + condition: qtc.testsEnabled + files: [ + "runscenegraphtest.cpp", "runscenegraphtest.h" + ] + } + + cpp.defines: base.concat("TRACING_LIBRARY") + } +} diff --git a/src/libs/tracing/tracing_dependencies.pri b/src/libs/tracing/tracing_dependencies.pri new file mode 100644 index 00000000000..c703652c04b --- /dev/null +++ b/src/libs/tracing/tracing_dependencies.pri @@ -0,0 +1,3 @@ +QTC_LIB_NAME = Tracing + +QTC_LIB_DEPENDS = utils diff --git a/src/libs/tracing/tracing_global.h b/src/libs/tracing/tracing_global.h new file mode 100644 index 00000000000..4ec75d81ee9 --- /dev/null +++ b/src/libs/tracing/tracing_global.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +#if defined(TRACING_LIBRARY) +# define TRACING_EXPORT Q_DECL_EXPORT +#else +# define TRACING_EXPORT Q_DECL_IMPORT +#endif |