aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/tracing/qml/CategoryLabel.qml
blob: 4994d3b9627b7225ad3feaf4d05a9acfc59775ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/****************************************************************************
**
** 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 2.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

    Button {
        // dummy button to display a tooltip
        anchors.fill: txt
        ToolTip.text: labelContainer.text
        ToolTip.visible: enabled && hovered
        ToolTip.delay: 1000
        background: Item {}
    }

    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: labelsArea
        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: labelsArea.parentModel ? labelsArea.parentModel.rowHeight(index + 1) : 0

                sourceComponent: RowLabel {
                    label: labels[index];
                    onSelectBySelectionId: {
                        if (labelContainer.reverseSelect) {
                            labelContainer.selectPrevBySelectionId(label.id);
                        } else {
                            labelContainer.selectNextBySelectionId(label.id);
                        }
                    }
                    onSetRowHeight: {
                        labelsArea.parentModel.setExpandedRowHeight(index + 1, newHeight);
                        loader.height = labelsArea.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.text: 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.text: 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
    }

}