Unify flame graph and timeline details windows

They are mostly the same.

Change-Id: I41be570989ecc58cf2ae33f692c89946b55a0e1d
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Ulf Hermann
2018-05-03 17:46:42 +02:00
parent f6b791815a
commit def017e5cb
7 changed files with 236 additions and 517 deletions

View File

@@ -57,7 +57,7 @@ Item {
anchors.right: flamegraphItem.right
anchors.bottom: flamegraphItem.bottom
FlameGraphText {
TimelineText {
id: text
visible: textVisible
anchors.fill: parent

View File

@@ -1,234 +0,0 @@
/****************************************************************************
**
** 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
}
}
}

View File

@@ -1,34 +0,0 @@
/****************************************************************************
**
** 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
}

View File

@@ -265,21 +265,15 @@ ScrollView {
}
}
FlameGraphDetails {
RangeDetails {
id: tooltip
minimumX: 0
maximumX: flickable.width
minimumY: flickable.contentY
maximumY: flickable.contentY + flickable.height
noteReadonly: true
titleBarColor: Theme.color(Theme.Timeline_PanelHeaderColor)
titleBarTextColor: Theme.color(Theme.PanelTextColorLight)
contentColor: Theme.color(Theme.Timeline_PanelBackgroundColor)
contentTextColor: Theme.color(Theme.Timeline_TextColor)
noteTextColor: Theme.color(Theme.Timeline_HighlightColor)
buttonHoveredColor: Theme.color(Theme.FancyToolButtonHoverColor)
buttonSelectedColor: Theme.color(Theme.FancyToolButtonSelectedColor)
borderWidth: 0
property var hoveredNode: null;
@@ -310,7 +304,7 @@ ScrollView {
}
model: currentNode ? currentNode.details() : []
note: currentNode ? currentNode.note() : ""
noteText: currentNode ? currentNode.note() : ""
Connections {
target: root.model

View File

@@ -224,11 +224,12 @@ Rectangle {
if (selectedModel !== -1 && selectedModel !== newModel)
select(selectedModel, -1);
rangeDetails.saveNote();
selectedItem = newItem
selectedModel = newModel
if (selectedItem !== -1) {
// display details
rangeDetails.showInfo(selectedModel, selectedItem);
rangeDetails.showInfo();
// update in other views
var model = timelineModelAggregator.models[selectedModel];
@@ -362,20 +363,73 @@ Rectangle {
x: 200
y: 25
noteReadonly: false
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);
}
onUpdateNote: {
if (timelineModelAggregator.notes && selectedModel != -1 && selectedItem != -1) {
timelineModelAggregator.notes.setText(
timelineModelAggregator.models[selectedModel].modelId,
selectedItem, text);
}
}
function hide() {
model = [];
file = "";
line = -1;
column = 0;
noteText = "";
dialogTitle = "";
}
function saveNote() {
noteFocus = false;
}
function showInfo() {
var timelineModel = timelineModelAggregator.models[selectedModel];
var eventData = timelineModel.details(selectedItem)
var content = [];
for (var k in eventData) {
if (k === "displayName") {
dialogTitle = eventData[k];
} else {
content.push(k);
content.push(eventData[k]);
}
}
rangeDetails.model = content;
var location = timelineModel.location(selectedItem)
if (location.hasOwnProperty("file")) { // not empty
rangeDetails.file = location.file;
rangeDetails.line = location.line;
rangeDetails.column = location.column;
} else {
// reset to default values
rangeDetails.file = "";
rangeDetails.line = 0;
rangeDetails.column = -1;
}
var notes = timelineModelAggregator.notes;
var noteId = notes ? notes.get(timelineModel.modelId, selectedItem) : -1;
rangeDetails.noteText = (noteId !== -1) ? notes.text(noteId) : "";
}
}
Rectangle {

View File

@@ -29,272 +29,213 @@ 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 real titleBarHeight: 20
property real borderWidth: 1
property real outerMargin: 10
property real innerMargin: 5
property real minimumInnerWidth: 150
property real initialWidth: 300
property int selectedModel: -1
property int selectedItem: -1
property real minimumX: 0
property real maximumX: parent.width
property real minimumY: 0
property real maximumY: parent.height
property bool locked
property string dialogTitle: ""
property string file: ""
property int line: -1
property int column: -1
property var models
property var notes
property bool locked: false
property var model: []
property alias noteText: noteEdit.text
property alias noteFocus: noteEdit.focus
property alias noteReadonly: noteEdit.readOnly
signal recenterOnItem
signal toggleSelectionLocked
signal clearSelection
signal updateNote(string text)
width: col.width + 20
height: hasContents ? contentArea.height + titleBar.height : 0
visible: dialogTitle.length > 0 || model.length > 0
function hide() {
noteEdit.focus = false;
hasContents = false;
selectedModel = selectedItem = -1;
noteEdit.text = "";
duration = "";
label = "";
file = "";
line = -1;
column = 0;
isBindingLoop = false;
}
width: dragHandle.x + dragHandle.width
height: contentArea.height + titleBar.height
Connections {
target: rangeDetails.parent
// keep inside view
onWidthChanged: fitInView();
onHeightChanged: fitInView();
}
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))
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;
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
onClicked: rangeDetails.recenterOnItem()
}
Rectangle {
id: titleBar
width: parent.width
height: 20
height: titleBarHeight
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)
border.width: borderWidth
border.color: Theme.color(Theme.PanelTextColorMid)
TimelineText {
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: Theme.color(Theme.PanelTextColorLight)
elide: Text.ElideRight
}
ImageToolButton {
id: editIcon
imageSource: "image://icons/edit"
anchors.top: parent.top
anchors.right: lockIcon.left
implicitHeight: typeTitle.height
visible: !rangeDetails.noteReadonly
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()
}
}
//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.top: titleBar.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: typeTitle.height
imageSource: "image://icons/close_window"
onClicked: rangeDetails.clearSelection()
anchors.bottom: dragHandle.bottom
border.width: borderWidth
border.color: Theme.color(Theme.PanelTextColorMid)
}
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
Detail {
labelWidth: col.labelWidth
valueWidth: col.valueWidth
isLabel: index % 2 === 0
text: isLabel ? (modelData + ":") : modelData
}
}
}
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: rangeDetails.noteReadonly
visible: (text.length > 0 || focus)
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) {
saveTimer.stop();
if (!readOnly)
rangeDetails.updateNote(text);
}
}
Timer {
id: saveTimer
onTriggered: {
if (!rangeDetails.readOnly)
rangeDetails.updateNote(noteEdit.text);
}
interval: 1000
}
}
Item {
id: dragHandle
width: 10
height: 10
x: 300
anchors.bottom: parent.bottom
width: outerMargin
height: outerMargin
x: initialWidth
anchors.top: noteEdit.bottom
clip: true
MouseArea {
anchors.fill: parent
drag.target: parent
drag.minimumX: col.minimumWidth - col.outerMargin
drag.minimumX: col.minimumWidth - outerMargin
drag.axis: Drag.XAxis
cursorShape: Qt.SizeHorCursor
}

View File

@@ -4,8 +4,6 @@
<file>CategoryLabel.qml</file>
<file>Detail.qml</file>
<file>FlameGraphDelegate.qml</file>
<file>FlameGraphDetails.qml</file>
<file>FlameGraphText.qml</file>
<file>FlameGraphView.qml</file>
<file>ico_edit.png</file>
<file>ico_edit@2x.png</file>