QmlProfiler: optimized timeline display

Change-Id: I0d7cf110356ef5f805b81a5fc39dca3870765ea3
Reviewed-by: Kai Koehne <kai.koehne@nokia.com>
This commit is contained in:
Christiaan Janssen
2011-10-26 11:32:01 +02:00
parent 38ad15a772
commit bf4dfd5e74
14 changed files with 730 additions and 464 deletions

View File

@@ -34,12 +34,53 @@ import QtQuick 1.0
Item {
property alias text: txt.text
property bool expanded: false
property int typeIndex: index
height: 50
width: 150 //### required, or ignored by positioner
property variant descriptions: [text]
height: root.singleRowHeight
width: 150
onExpandedChanged: {
var rE = labels.rowExpanded;
rE[typeIndex] = expanded;
labels.rowExpanded = rE;
backgroundMarks.requestPaint();
view.rowExpanded(typeIndex, expanded);
updateHeight();
}
Component.onCompleted: {
updateHeight();
}
function updateHeight() {
height = root.singleRowHeight *
(expanded ? qmlEventList.uniqueEventsOfType(typeIndex) : qmlEventList.maxNestingForType(typeIndex));
}
Connections {
target: qmlEventList
onDataReady: {
var desc=[];
for (var i=0; i<qmlEventList.uniqueEventsOfType(typeIndex); i++)
desc[i] = qmlEventList.eventTextForType(typeIndex, i);
// special case: empty
if (desc.length == 1 && desc[0]=="")
desc[0] = text;
descriptions = desc;
updateHeight();
}
onDataClear: {
descriptions = [text];
updateHeight();
}
}
Text {
id: txt;
id: txt
visible: !expanded
x: 5
font.pixelSize: 12
color: "#232323"
@@ -52,4 +93,31 @@ Item {
color: "#cccccc"
anchors.bottom: parent.bottom
}
Column {
visible: expanded
Repeater {
model: descriptions.length
Text {
height: root.singleRowHeight
x: 5
width: 140
text: descriptions[index]
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
Image {
source: expanded ? "arrow_down.png" : "arrow_right.png"
x: parent.width - 12
y: 2
MouseArea {
anchors.fill: parent
onClicked: {
expanded = !expanded;
}
}
}
}

View File

@@ -39,13 +39,17 @@ Rectangle {
// ***** properties
property int candidateHeight: 0
height: Math.max( candidateHeight, labels.rowCount * 50 + 2 )
height: Math.max( candidateHeight, labels.height + 2 )
property int singleRowHeight: 30
property bool dataAvailable: true
property int eventCount: 0
property real progress: 0
property bool mouseOverSelection : true
property alias selectionLocked : view.selectionLocked
signal updateLockButton
property alias selectedItem: view.selectedItem
property variant names: [ qsTr("Painting"), qsTr("Compiling"), qsTr("Creating"), qsTr("Binding"), qsTr("Handling Signal")]
property variant colors : [ "#99CCB3", "#99CCCC", "#99B3CC", "#9999CC", "#CC99B3", "#CC99CC", "#CCCC99", "#CCB399" ]
@@ -56,9 +60,6 @@ Rectangle {
property string fileName: ""
property int lineNumber: -1
property int selectedEventIndex : -1
property bool mouseTracking: false
property real elapsedTime
signal updateTimer
@@ -107,7 +108,11 @@ Rectangle {
onDataReady: {
if (eventCount > 0) {
view.clearData();
view.rebuildCache();
progress = 1.0;
dataAvailable = true;
view.visible = true;
view.requestPaint();
zoomControl.setRange(0, qmlEventList.traceEndTime()/10);
}
}
}
@@ -128,7 +133,6 @@ Rectangle {
function clearDisplay() {
clearData();
selectedEventIndex = -1;
view.visible = false;
}
@@ -139,19 +143,11 @@ Rectangle {
}
function nextEvent() {
if (eventCount > 0) {
++selectedEventIndex;
if (selectedEventIndex >= eventCount)
selectedEventIndex = 0;
}
view.selectNext();
}
function prevEvent() {
if (eventCount > 0) {
--selectedEventIndex;
if (selectedEventIndex < 0)
selectedEventIndex = eventCount - 1;
}
view.selectPrev();
}
function updateWindowLength(absoluteFactor) {
@@ -179,9 +175,9 @@ Rectangle {
}
var fixedPoint = (view.startTime + view.endTime) / 2;
if (root.selectedEventIndex !== -1) {
if (view.selectedItem !== -1) {
// center on selected item if it's inside the current screen
var newFixedPoint = qmlEventList.getStartTime(selectedEventIndex);
var newFixedPoint = qmlEventList.getStartTime(view.selectedItem);
if (newFixedPoint >= view.startTime && newFixedPoint < view.endTime)
fixedPoint = newFixedPoint;
}
@@ -242,37 +238,18 @@ Rectangle {
rangeDetails.type = "";
rangeDetails.file = "";
rangeDetails.line = -1;
root.mouseOverSelection = true;
selectionHighlight.visible = false;
}
// ***** slots
onSelectedEventIndexChanged: {
if ((!mouseTracking) && eventCount > 0
&& selectedEventIndex > -1 && selectedEventIndex < eventCount) {
var windowLength = view.endTime - view.startTime;
var eventStartTime = qmlEventList.getStartTime(selectedEventIndex);
var eventEndTime = eventStartTime + qmlEventList.getDuration(selectedEventIndex);
if (eventEndTime < view.startTime || eventStartTime > view.endTime) {
var center = (eventStartTime + eventEndTime)/2;
var from = Math.min(qmlEventList.traceEndTime()-windowLength,
Math.max(0, Math.floor(center - windowLength/2)));
zoomControl.setRange(from, from + windowLength);
}
}
if (selectedEventIndex === -1)
selectionHighlight.visible = false;
}
onSelectionRangeModeChanged: {
selectionRangeControl.enabled = selectionRangeMode;
selectionRange.reset(selectionRangeMode);
}
onSelectionLockedChanged: {
updateLockButton();
}
// ***** child items
Timer {
id: elapsedTimer
@@ -312,7 +289,7 @@ Rectangle {
anchors.left: labels.right
height: root.height
contentWidth: 0;
contentHeight: labels.rowCount * 50
contentHeight: labels.height
flickableDirection: Flickable.HorizontalFlick
onContentXChanged: {
@@ -322,13 +299,43 @@ Rectangle {
clip:true
MouseArea {
id: selectionRangeDrag
enabled: selectionRange.ready
anchors.fill: selectionRange
drag.target: selectionRange
drag.axis: "XAxis"
drag.minimumX: 0
drag.maximumX: flick.contentWidth - selectionRange.width
onPressed: {
selectionRange.isDragging = true;
}
onReleased: {
selectionRange.isDragging = false;
}
onDoubleClicked: {
zoomControl.setRange(selectionRange.startTime, selectionRange.startTime + selectionRange.duration);
root.selectionRangeMode = false;
root.updateRangeButton();
}
}
SelectionRange {
id: selectionRange
visible: root.selectionRangeMode
height: root.height
z: 2
}
TimelineView {
id: view
eventList: qmlEventList
x: flick.contentX
width: flick.width
height: flick.contentHeight
height: root.height
property variant startX: 0
onStartXChanged: {
@@ -349,178 +356,65 @@ Rectangle {
var newStartX = startTime * flick.width / (endTime-startTime);
if (Math.abs(newStartX - startX) >= 1)
startX = newStartX;
updateTimeline();
}
}
property real timeSpan: endTime - startTime
onTimeSpanChanged: {
if (selectedEventIndex !== -1 && selectionHighlight.visible) {
var spacing = flick.width / timeSpan;
selectionHighlight.x = (qmlEventList.getStartTime(selectedEventIndex) - qmlEventList.traceStartTime()) * spacing;
selectionHighlight.width = qmlEventList.getDuration(selectedEventIndex) * spacing;
}
}
onCachedProgressChanged: {
root.progress = 0.5 + cachedProgress * 0.5;
}
onCacheReady: {
root.progress = 1.0;
root.dataAvailable = true;
if (root.eventCount > 0) {
view.visible = true;
view.updateTimeline();
zoomControl.setRange(0, qmlEventList.traceEndTime()/10);
}
}
delegate: Rectangle {
id: obj
property color myColor: root.colors[type]
function conditionalHide() {
if (!mouseArea.containsMouse)
mouseArea.exited()
}
property int baseY: type * view.height / labels.rowCount
property int baseHeight: view.height / labels.rowCount
y: baseY + (nestingLevel-1)*(baseHeight / nestingDepth)
height: baseHeight / nestingDepth
gradient: Gradient {
GradientStop { position: 0.0; color: myColor }
GradientStop { position: 0.5; color: Qt.darker(myColor, 1.1) }
GradientStop { position: 1.0; color: myColor }
}
smooth: true
property bool componentIsCompleted: false
Component.onCompleted: {
componentIsCompleted = true;
updateDetails();
}
property bool isSelected: root.selectedEventIndex == index;
onIsSelectedChanged: {
updateDetails();
}
function updateDetails() {
if (!root.mouseTracking && componentIsCompleted) {
if (isSelected) {
enableSelected(0, 0);
}
}
}
function enableSelected(x,y) {
rangeDetails.duration = qmlEventList.getDuration(index)/1000.0;
rangeDetails.label = qmlEventList.getDetails(index);
rangeDetails.file = qmlEventList.getFilename(index);
rangeDetails.line = qmlEventList.getLine(index);
rangeDetails.type = root.names[type];
onSelectedItemChanged: {
if (selectedItem !== -1) {
// display details
rangeDetails.duration = qmlEventList.getDuration(selectedItem)/1000.0;
rangeDetails.label = qmlEventList.getDetails(selectedItem);
rangeDetails.file = qmlEventList.getFilename(selectedItem);
rangeDetails.line = qmlEventList.getLine(selectedItem);
rangeDetails.type = root.names[qmlEventList.getType(selectedItem)];
rangeDetails.visible = true;
selectionHighlight.x = obj.x;
selectionHighlight.y = obj.y;
selectionHighlight.width = width;
selectionHighlight.height = height;
selectionHighlight.visible = true;
}
// center view
var windowLength = view.endTime - view.startTime;
var eventStartTime = qmlEventList.getStartTime(selectedItem);
var eventEndTime = eventStartTime + qmlEventList.getDuration(selectedItem);
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: {
if (root.mouseOverSelection) {
root.mouseTracking = true;
root.selectedEventIndex = index;
enableSelected(mouseX, y);
root.mouseTracking = false;
}
if (eventEndTime < view.startTime || eventStartTime > view.endTime) {
var center = (eventStartTime + eventEndTime)/2;
var from = Math.min(qmlEventList.traceEndTime()-windowLength,
Math.max(0, Math.floor(center - windowLength/2)));
zoomControl.setRange(from, from + windowLength);
}
onPressed: {
root.mouseTracking = true;
root.selectedEventIndex = index;
enableSelected(mouseX, y);
root.mouseTracking = false;
root.mouseOverSelection = false;
root.gotoSourceLocation(rangeDetails.file, rangeDetails.line);
}
}
}
Rectangle {
id: selectionHighlight
color:"transparent"
border.width: 2
border.color: "blue"
z: 1
radius: 2
visible: false
}
MouseArea {
width: parent.width
height: parent.height
x: flick.contentX
onClicked: {
} else {
root.hideRangeDetails();
}
}
MouseArea {
id: selectionRangeControl
enabled: false
width: flick.width
height: root.height
x: flick.contentX
hoverEnabled: enabled
z: 2
onReleased: {
selectionRange.releasedOnCreation();
}
onPressed: {
selectionRange.pressedOnCreation();
}
onMousePositionChanged: {
selectionRange.movedOnCreation();
onItemPressed: {
if (pressedItem !== -1) {
root.gotoSourceLocation(qmlEventList.getFilename(pressedItem), qmlEventList.getLine(pressedItem));
}
}
SelectionRange {
id: selectionRange
visible: root.selectionRangeMode
height: root.height
z: 2
}
// hack to pass mouse events to the other mousearea if enabled
startDragArea: selectionRangeDrag.enabled ? selectionRangeDrag.x : -flick.contentX
endDragArea: selectionRangeDrag.enabled ?
selectionRangeDrag.x + selectionRangeDrag.width : -flick.contentX-1
}
MouseArea {
id: selectionRangeControl
enabled: false
width: flick.width
height: root.height
x: flick.contentX
hoverEnabled: enabled
z: 2
MouseArea {
id: selectionRangeDrag
enabled: selectionRange.ready
anchors.fill: selectionRange
drag.target: selectionRange
drag.axis: "XAxis"
drag.minimumX: 0
drag.maximumX: flick.contentWidth - selectionRange.width
onPressed: {
selectionRange.isDragging = true;
}
onReleased: {
selectionRange.isDragging = false;
}
onDoubleClicked: {
zoomControl.setRange(selectionRange.startTime, selectionRange.startTime + selectionRange.duration);
root.selectionRangeMode = false;
root.updateRangeButton();
}
onReleased: {
selectionRange.releasedOnCreation();
}
onPressed: {
selectionRange.pressedOnCreation();
}
onMousePositionChanged: {
selectionRange.movedOnCreation();
}
}
}
@@ -542,9 +436,10 @@ Rectangle {
id: labels
width: 150
color: "#dcdcdc"
height: flick.contentHeight
height: col.height
property int rowCount: 5
property variant rowExpanded: [false,false,false,false,false];
Column {
id: col

View File

@@ -36,13 +36,13 @@ import Monitor 1.0
BorderImage {
id: rangeDetails
property string duration //###int?
property string duration
property string label
property string type
property string file
property int line
property bool locked: !root.mouseOverSelection
property bool locked: view.selectionLocked
source: "popup_green.png"
border {
@@ -107,6 +107,15 @@ BorderImage {
}
}
MouseArea {
width: col.width + 30
height: col.height + typeTitle.height + 30
drag.target: parent
onClicked: {
root.gotoSourceLocation(file, line);
}
}
Image {
id: lockIcon
source: locked?"lock_closed.png" : "lock_open.png"
@@ -118,7 +127,7 @@ BorderImage {
MouseArea {
anchors.fill: parent
onClicked: {
root.mouseOverSelection = !root.mouseOverSelection;
root.selectionLocked = !root.selectionLocked;
}
}
}
@@ -132,20 +141,10 @@ BorderImage {
anchors.fill: parent
onClicked: {
root.hideRangeDetails();
view.selectedItem = -1;
}
}
}
MouseArea {
width: col.width
height: col.height + typeTitle.height + 30
drag.target: parent
onClicked: {
// force reload of selected item
var selectedItem = root.selectedEventIndex;
root.selectedEventIndex = -1;
root.selectedEventIndex = selectedItem;
root.gotoSourceLocation(file, line);
}
}
}

View File

@@ -107,13 +107,17 @@ TiledCanvas {
}
function drawBackgroundBars( ctxt, region ) {
var barHeight = Math.round(labels.height / labels.rowCount);
var cumulatedHeight = 0;
for (var i=0; i<labels.rowCount; i++) {
ctxt.fillStyle = i%2 ? "#f3f3f3" : "white"
ctxt.strokeStyle = i%2 ? "#f3f3f3" : "white"
ctxt.fillRect(0, i * barHeight, width, barHeight);
var barHeight = labels.rowExpanded[i] ?
qmlEventList.uniqueEventsOfType(i) * root.singleRowHeight :
qmlEventList.maxNestingForType(i) * root.singleRowHeight;
ctxt.fillStyle = i%2 ? "#f0f0f0" : "white"
ctxt.strokeStyle = i%2 ? "#f0f0f0" : "white"
ctxt.fillRect(0, cumulatedHeight, width, barHeight);
cumulatedHeight += barHeight;
}
ctxt.fillStyle = "white";
ctxt.fillStyle = "#f5f5f5";
ctxt.fillRect(0, labels.height, width, height - labels.height);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

View File

@@ -24,5 +24,8 @@
<file>SelectionRange.qml</file>
<file>SelectionRangeDetails.qml</file>
<file>magnifier.png</file>
<file>arrow_down.png</file>
<file>arrow_right.png</file>
<file>arrow_select.png</file>
</qresource>
</RCC>

View File

@@ -35,223 +35,274 @@
#include <qdeclarativecontext.h>
#include <qdeclarativeproperty.h>
#include <QtCore/QTimer>
#include <QtGui/QPixmap>
#include <QtGui/QPainter>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <math.h>
using namespace QmlProfiler::Internal;
#define CACHE_ENABLED true
#define CACHE_UPDATEDELAY 10
#define CACHE_STEP 200
const int DefaultRowHeight = 30;
TimelineView::TimelineView(QDeclarativeItem *parent) :
QDeclarativeItem(parent), m_delegate(0), m_itemCount(0), m_startTime(0), m_endTime(0), m_spacing(0),
prevMin(0), prevMax(0), m_eventList(0), m_totalWidth(0), m_lastCachedIndex(0), m_creatingCache(false), m_oldCacheSize(0)
QDeclarativeItem(parent), m_startTime(0), m_endTime(0), m_spacing(0),
m_lastStartTime(0), m_lastEndTime(0), m_eventList(0)
{
clearData();
setFlag(QGraphicsItem::ItemHasNoContents, false);
setAcceptedMouseButtons(Qt::LeftButton);
setAcceptHoverEvents(true);
for (int i=0; i<QmlJsDebugClient::MaximumQmlEventType; i++)
m_rowsExpanded << false;
}
void TimelineView::componentComplete()
{
const QMetaObject *metaObject = this->metaObject();
int propertyCount = metaObject->propertyCount();
int requestPaintMethod = metaObject->indexOfMethod("requestPaint()");
for (int ii = TimelineView::staticMetaObject.propertyCount(); ii < propertyCount; ++ii) {
QMetaProperty p = metaObject->property(ii);
if (p.hasNotifySignal())
QMetaObject::connect(this, p.notifySignalIndex(), this, requestPaintMethod, 0, 0);
}
QDeclarativeItem::componentComplete();
}
void TimelineView::requestPaint()
{
update();
}
void TimelineView::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
{
qint64 windowDuration = m_endTime - m_startTime;
if (windowDuration <= 0)
return;
m_spacing = qreal(width()) / windowDuration;
m_rowWidths.clear();
for (int i=0; i<QmlJsDebugClient::MaximumQmlEventType; i++) {
m_rowWidths << (m_rowsExpanded[i] ? m_eventList->uniqueEventsOfType(i) : m_eventList->maxNestingForType(i));
}
// event rows
m_rowStarts.clear();
int pos = 0;
for (int i=0; i<QmlJsDebugClient::MaximumQmlEventType; i++) {
m_rowStarts << pos;
pos += DefaultRowHeight * m_rowWidths[i];
}
p->setPen(Qt::transparent);
// speedup: don't draw overlapping events, just skip them
m_rowLastX.clear();
for (int i=0; i<QmlJsDebugClient::MaximumQmlEventType; i++)
for (int j=0; j<m_rowWidths[i]; j++)
m_rowLastX << -m_startTime * m_spacing;
int firstIndex = m_eventList->findFirstIndex(m_startTime);
int lastIndex = m_eventList->findLastIndex(m_endTime);
drawItemsToPainter(p, firstIndex, lastIndex);
drawSelectionBoxes(p);
m_lastStartTime = m_startTime;
m_lastEndTime = m_endTime;
}
QColor TimelineView::colorForItem(int itemIndex)
{
int ndx = m_eventList->getHash(itemIndex);
return QColor::fromHsl((ndx*25)%360, 76, 166);
}
QLinearGradient *TimelineView::gradientForItem(int itemIndex)
{
int ndx = m_eventList->getHash(itemIndex);
if (!m_hashedGradients.contains(ndx)) {
QLinearGradient *linearGrad = new QLinearGradient(0,0,0,DefaultRowHeight);
linearGrad->setColorAt(0, colorForItem(itemIndex));
linearGrad->setColorAt(0.5, colorForItem(itemIndex).darker(115));
linearGrad->setColorAt(1, colorForItem(itemIndex));
m_hashedGradients[ndx] = linearGrad;
}
return m_hashedGradients[ndx];
}
void TimelineView::drawItemsToPainter(QPainter *p, int fromIndex, int toIndex)
{
int x,y,width,rowNumber, eventType;
for (int i = fromIndex; i <= toIndex; i++) {
x = (m_eventList->getStartTime(i) - m_startTime) * m_spacing;
eventType = m_eventList->getType(i);
if (m_rowsExpanded[eventType])
y = m_rowStarts[eventType] + DefaultRowHeight*m_eventList->eventPosInType(i);
else
y = m_rowStarts[eventType] + DefaultRowHeight*(m_eventList->getNestingLevel(i)-1);
width = m_eventList->getDuration(i)*m_spacing;
if (width<1)
width = 1;
rowNumber = y/DefaultRowHeight;
if (m_rowLastX[rowNumber] > x+width)
continue;
m_rowLastX[rowNumber] = x+width;
p->setBrush(*gradientForItem(i));
p->drawRect(x,y,width,DefaultRowHeight);
}
}
void TimelineView::drawSelectionBoxes(QPainter *p)
{
if (m_selectedItem == -1)
return;
int fromIndex = m_eventList->findFirstIndex(m_startTime);
int toIndex = m_eventList->findLastIndex(m_endTime);
int id = m_eventList->getHash(m_selectedItem);
p->setBrush(Qt::transparent);
QPen strongPen(QBrush(Qt::blue), 3);
QPen lightPen(QBrush(QColor(Qt::blue).lighter(130)), 2);
p->setPen(lightPen);
int x,y,width,rowNumber,eventType;
for (int i = fromIndex; i <= toIndex; i++) {
if (m_eventList->getHash(i) != id)
continue;
if (i == m_selectedItem)
p->setPen(strongPen);
else
p->setPen(lightPen);
x = (m_eventList->getStartTime(i) - m_startTime) * m_spacing;
eventType = m_eventList->getType(i);
if (m_rowsExpanded[eventType])
y = m_rowStarts[eventType] + DefaultRowHeight*m_eventList->eventPosInType(i);
else
y = m_rowStarts[eventType] + DefaultRowHeight*(m_eventList->getNestingLevel(i)-1);
width = m_eventList->getDuration(i)*m_spacing;
if (width<1)
width = 1;
rowNumber = y/DefaultRowHeight;
p->drawRect(x,y,width,DefaultRowHeight);
}
}
void TimelineView::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// special case: if there is a drag area below me, don't accept the
// events unless I'm actually clicking inside an item
if (m_currentSelection.eventIndex == -1 &&
event->pos().x()+x() >= m_startDragArea &&
event->pos().x()+x() <= m_endDragArea)
event->setAccepted(false);
}
void TimelineView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
manageClicked();
}
void TimelineView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->setAccepted(false);
}
void TimelineView::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
Q_UNUSED(event);
manageHovered(event->pos().x(), event->pos().y());
if (m_currentSelection.eventIndex == -1)
event->setAccepted(false);
}
void TimelineView::manageClicked()
{
if (m_currentSelection.eventIndex != -1) {
if (m_currentSelection.eventIndex == m_selectedItem)
setSelectionLocked(!m_selectionLocked);
else
setSelectionLocked(true);
emit itemPressed(m_currentSelection.eventIndex);
} else {
// setSelectionLocked(false);
}
setSelectedItem(m_currentSelection.eventIndex);
}
void TimelineView::manageHovered(int x, int y)
{
if (m_endTime - m_startTime <=0)
return;
qint64 time = x * (m_endTime - m_startTime) / width() + m_startTime;
int row = y / DefaultRowHeight;
// already covered? nothing to do
if (m_currentSelection.eventIndex != -1 && time >= m_currentSelection.startTime && time <= m_currentSelection.endTime && row == m_currentSelection.row) {
return;
}
// find if there's items in the time range
int eventFrom = m_eventList->findFirstIndex(time);
int eventTo = m_eventList->findLastIndex(time);
if (eventTo < eventFrom) {
m_currentSelection.eventIndex = -1;
return;
}
// find if we are in the right column
int itemRow, eventType;
for (int i=eventTo; i>=eventFrom; --i) {
if (ceil(m_eventList->getEndTime(i)*m_spacing) < floor(time*m_spacing))
continue;
eventType = m_eventList->getType(i);
if (m_rowsExpanded[eventType])
itemRow = m_rowStarts[eventType]/DefaultRowHeight + m_eventList->eventPosInType(i);
else
itemRow = m_rowStarts[eventType]/DefaultRowHeight + m_eventList->getNestingLevel(i)-1;
if (itemRow == row) {
// match
m_currentSelection.eventIndex = i;
m_currentSelection.startTime = m_eventList->getStartTime(i);
m_currentSelection.endTime = m_eventList->getEndTime(i);
m_currentSelection.row = row;
if (!m_selectionLocked)
setSelectedItem(i);
return;
}
}
m_currentSelection.eventIndex = -1;
return;
}
void TimelineView::clearData()
{
if (CACHE_ENABLED)
foreach (QDeclarativeItem *item, m_items.values())
item->setVisible(false);
else
foreach (QDeclarativeItem *item, m_items.values())
delete m_items.take(m_items.key(item));
m_startTime = 0;
m_endTime = 0;
prevMin = 0;
prevMax = 0;
m_totalWidth = 0;
m_lastCachedIndex = 0;
}
void TimelineView::updateTimeline()
{
if (!m_delegate)
return;
if (!m_eventList)
return;
qreal totalRange = m_eventList->traceEndTime() - m_eventList->traceStartTime();
qreal window = m_endTime - m_startTime;
if (window == 0) //###
return;
qreal newSpacing = width() / window;
bool spacingChanged = (newSpacing != m_spacing);
m_spacing = newSpacing;
qreal oldtw = m_totalWidth;
m_totalWidth = totalRange * m_spacing;
int minsample = m_eventList->findFirstIndex(m_startTime + m_eventList->traceStartTime());
int maxsample = m_eventList->findLastIndex(m_endTime + m_eventList->traceStartTime());
//### emitting this before startXChanged was causing issues
if (m_totalWidth != oldtw)
emit totalWidthChanged(m_totalWidth);
// the next loops have to be modified with the new implementation of the cache
// hide items that are not visible any more
if (maxsample < prevMin || minsample > prevMax) {
for (int i = prevMin; i <= prevMax; ++i)
if (m_items.contains(i)) {
if (CACHE_ENABLED)
m_items.value(i)->setVisible(false);
else
delete m_items.take(i);
}
} else {
if (minsample > prevMin && minsample <= prevMax)
for (int i = prevMin; i < minsample; ++i)
if (m_items.contains(i)) {
if (CACHE_ENABLED)
m_items.value(i)->setVisible(false);
else
delete m_items.take(i);
}
if (maxsample >= prevMin && maxsample < prevMax)
for (int i = maxsample + 1; i <= prevMax; ++i)
if (m_items.contains(i)) {
if (CACHE_ENABLED)
m_items.value(i)->setVisible(false);
else
delete m_items.take(i);
}
}
// Update visible items
for (int i = minsample; i <= maxsample; ++i) {
if (!m_items.contains(i)) {
createItem(i);
m_items.value(i)->setVisible(true);
}
else
if (spacingChanged || !m_items.value(i)->isVisible()) {
m_items.value(i)->setVisible(true);
updateItemPosition(i);
}
}
prevMin = minsample;
prevMax = maxsample;
}
void TimelineView::createItem(int itemIndex)
{
QDeclarativeContext *ctxt = new QDeclarativeContext(qmlContext(this));
QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(m_delegate->beginCreate(ctxt));
m_items.insert(itemIndex, item);
ctxt->setParent(item); //### QDeclarative_setParent_noEvent(ctxt, item); instead?
ctxt->setContextProperty("index", itemIndex);
ctxt->setContextProperty("type", m_eventList->getType(itemIndex));
ctxt->setContextProperty("nestingLevel", m_eventList->getNestingLevel(itemIndex));
ctxt->setContextProperty("nestingDepth", m_eventList->getNestingDepth(itemIndex));
updateItemPosition(itemIndex);
item->setVisible(false);
item->setParentItem(this);
m_delegate->completeCreate();
m_itemCount++;
}
void TimelineView::updateItemPosition(int itemIndex)
{
QDeclarativeItem *item = m_items.value(itemIndex);
if (item) {
qreal itemStartPos = (m_eventList->getStartTime(itemIndex) - m_eventList->traceStartTime()) * m_spacing;
item->setX(itemStartPos);
qreal width = (m_eventList->getEndTime(itemIndex) - m_eventList->getStartTime(itemIndex)) * m_spacing;
item->setWidth(width > 1 ? width : 1);
}
}
void TimelineView::rebuildCache()
{
if (CACHE_ENABLED) {
m_lastCachedIndex = 0;
m_creatingCache = false;
m_oldCacheSize = m_items.count();
emit cachedProgressChanged();
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(purgeCache()));
} else {
m_creatingCache = true;
m_lastCachedIndex = m_eventList->count();
emit cacheReady();
}
}
qreal TimelineView::cachedProgress() const
{
qreal progress;
if (!m_creatingCache) {
if (m_oldCacheSize == 0)
progress = 0.5;
else
progress = (m_lastCachedIndex * 0.5) / m_oldCacheSize;
}
else
progress = 0.5 + (m_lastCachedIndex * 0.5) / m_eventList->count();
return progress;
}
void TimelineView::increaseCache()
{
int totalCount = m_eventList->count();
if (m_lastCachedIndex >= totalCount) {
emit cacheReady();
return;
}
for (int i = 0; i < CACHE_STEP; i++) {
createItem(m_lastCachedIndex);
m_lastCachedIndex++;
if (m_lastCachedIndex >= totalCount)
break;
}
emit cachedProgressChanged();
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(increaseCache()));
}
void TimelineView::purgeCache()
{
if (m_items.isEmpty()) {
m_creatingCache = true;
m_lastCachedIndex = 0;
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(increaseCache()));
return;
}
for (int i=0; i < CACHE_STEP; i++)
{
if (m_items.contains(m_lastCachedIndex))
delete m_items.take(m_lastCachedIndex);
m_lastCachedIndex++;
if (m_items.isEmpty())
break;
}
emit cachedProgressChanged();
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(purgeCache()));
m_lastStartTime = 0;
m_lastEndTime = 0;
m_currentSelection.startTime = -1;
m_currentSelection.endTime = -1;
m_currentSelection.row = -1;
m_currentSelection.eventIndex = -1;
m_selectedItem = -1;
m_selectionLocked = true;
}
qint64 TimelineView::getDuration(int index) const
@@ -277,3 +328,65 @@ QString TimelineView::getDetails(int index) const
Q_ASSERT(m_eventList);
return m_eventList->getDetails(index);
}
void TimelineView::rowExpanded(int rowIndex, bool expanded)
{
m_rowsExpanded[rowIndex] = expanded;
update();
}
void TimelineView::selectNext()
{
if (m_eventList->count() == 0)
return;
if (m_selectionLocked && m_selectedItem !=-1 ) {
// find next item with same hashId
int hashId = m_eventList->getHash(m_selectedItem);
int i = m_selectedItem+1;
while (i<m_eventList->count() && m_eventList->getHash(i) != hashId)
i++;
if (i == m_eventList->count()) {
i = 0;
while (i<m_selectedItem && m_eventList->getHash(i) != hashId)
i++;
}
setSelectedItem(i);
} else {
// select next in view or after
int newIndex = m_selectedItem+1;
if (newIndex >= m_eventList->count())
newIndex = 0;
if (m_eventList->getEndTime(newIndex) < m_startTime)
newIndex = m_eventList->findFirstIndexNoParents(m_startTime);
setSelectedItem(newIndex);
}
}
void TimelineView::selectPrev()
{
if (m_eventList->count() == 0)
return;
if (m_selectionLocked && m_selectedItem !=-1) {
// find previous item with same hashId
int hashId = m_eventList->getHash(m_selectedItem);
int i = m_selectedItem-1;
while (i>-1 && m_eventList->getHash(i) != hashId)
i--;
if (i == -1) {
i = m_eventList->count()-1;
while (i>m_selectedItem && m_eventList->getHash(i) != hashId)
i--;
}
setSelectedItem(i);
} else {
// select last in view or before
int newIndex = m_selectedItem-1;
if (newIndex < 0)
newIndex = m_eventList->count()-1;
if (m_eventList->getStartTime(newIndex) > m_endTime)
newIndex = m_eventList->findLastIndex(m_endTime);
setSelectedItem(newIndex);
}
}

View File

@@ -43,21 +43,17 @@ namespace Internal {
class TimelineView : public QDeclarativeItem
{
Q_OBJECT
Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
Q_PROPERTY(qint64 startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
Q_PROPERTY(qint64 endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
Q_PROPERTY(qreal totalWidth READ totalWidth NOTIFY totalWidthChanged)
Q_PROPERTY(QObject* eventList READ eventList WRITE setEventList NOTIFY eventListChanged)
Q_PROPERTY(qreal cachedProgress READ cachedProgress NOTIFY cachedProgressChanged)
Q_PROPERTY(bool selectionLocked READ selectionLocked WRITE setSelectionLocked NOTIFY selectionLockedChanged)
Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem NOTIFY selectedItemChanged)
Q_PROPERTY(int startDragArea READ startDragArea WRITE setStartDragArea NOTIFY startDragAreaChanged)
Q_PROPERTY(int endDragArea READ endDragArea WRITE setEndDragArea NOTIFY endDragAreaChanged)
public:
explicit TimelineView(QDeclarativeItem *parent = 0);
QDeclarativeComponent * delegate() const
{
return m_delegate;
}
qint64 startTime() const
{
return m_startTime;
@@ -68,12 +64,25 @@ public:
return m_endTime;
}
qreal totalWidth() const
bool selectionLocked() const
{
return m_totalWidth;
return m_selectionLocked;
}
qreal cachedProgress() const;
int selectedItem() const
{
return m_selectedItem;
}
int startDragArea() const
{
return m_startDragArea;
}
int endDragArea() const
{
return m_endDragArea;
}
QmlJsDebugClient::QmlProfilerEventList *eventList() const { return m_eventList; }
void setEventList(QObject *eventList)
@@ -82,33 +91,30 @@ public:
emit eventListChanged(m_eventList);
}
Q_INVOKABLE void selectNext();
Q_INVOKABLE void selectPrev();
Q_INVOKABLE void rowExpanded(int rowIndex, bool expanded);
Q_INVOKABLE qint64 getDuration(int index) const;
Q_INVOKABLE QString getFilename(int index) const;
Q_INVOKABLE int getLine(int index) const;
Q_INVOKABLE QString getDetails(int index) const;
Q_INVOKABLE void rebuildCache();
signals:
void delegateChanged(QDeclarativeComponent * arg);
void startTimeChanged(qint64 arg);
void endTimeChanged(qint64 arg);
void totalWidthChanged(qreal arg);
void eventListChanged(QmlJsDebugClient::QmlProfilerEventList *list);
void cachedProgressChanged();
void cacheReady();
void selectionLockedChanged(bool locked);
void selectedItemChanged(int itemIndex);
void startDragAreaChanged(int startDragArea);
void endDragAreaChanged(int endDragArea);
void itemPressed(int pressedItem);
public slots:
void clearData();
void updateTimeline();
void requestPaint();
void setDelegate(QDeclarativeComponent * arg)
{
if (m_delegate != arg) {
m_delegate = arg;
emit delegateChanged(arg);
}
}
void setStartTime(qint64 arg)
{
@@ -126,34 +132,82 @@ public slots:
}
}
void setSelectionLocked(bool locked)
{
if (m_selectionLocked != locked) {
m_selectionLocked = locked;
emit selectionLockedChanged(locked);
}
}
void setSelectedItem(int itemIndex)
{
if (m_selectedItem != itemIndex) {
m_selectedItem = itemIndex;
update();
emit selectedItemChanged(itemIndex);
}
}
void setStartDragArea(int startDragArea)
{
if (m_startDragArea != startDragArea) {
m_startDragArea = startDragArea;
emit startDragAreaChanged(startDragArea);
}
}
void setEndDragArea(int endDragArea)
{
if (m_endDragArea != endDragArea) {
m_endDragArea = endDragArea;
emit endDragAreaChanged(endDragArea);
}
}
protected:
void componentComplete();
virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
virtual void componentComplete();
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
private:
void createItem(int itemIndex);
void updateItemPosition(int itemIndex);
QColor colorForItem(int itemIndex);
QLinearGradient *gradientForItem(int itemIndex);
void drawItemsToPainter(QPainter *p, int fromIndex, int toIndex);
void drawSelectionBoxes(QPainter *p);
public slots:
void increaseCache();
void purgeCache();
void manageClicked();
void manageHovered(int x, int y);
private:
QDeclarativeComponent * m_delegate;
QHash<int,QDeclarativeItem*> m_items;
qint64 m_itemCount;
qint64 m_startTime;
qint64 m_endTime;
qreal m_spacing;
int prevMin;
int prevMax;
qint64 m_lastStartTime;
qint64 m_lastEndTime;
QmlJsDebugClient::QmlProfilerEventList *m_eventList;
QHash<int, QLinearGradient*> m_hashedGradients;
qreal m_totalWidth;
int m_lastCachedIndex;
bool m_creatingCache;
int m_oldCacheSize;
QList<int> m_rowLastX;
QList<int> m_rowStarts;
QList<int> m_rowWidths;
QList<bool> m_rowsExpanded;
struct {
qint64 startTime;
qint64 endTime;
int row;
int eventIndex;
} m_currentSelection;
int m_selectedItem;
bool m_selectionLocked;
int m_startDragArea;
int m_endDragArea;
};
} // namespace Internal

View File

@@ -174,6 +174,16 @@ QWidget *TraceWindow::createToolbar()
connect(this, SIGNAL(enableToolbar(bool)), m_buttonRange, SLOT(setEnabled(bool)));
connect(this, SIGNAL(rangeModeChanged(bool)), m_buttonRange, SLOT(setChecked(bool)));
m_buttonLock = new QToolButton;
m_buttonLock->setIcon(QIcon(":/qmlprofiler/arrow_select.png"));
m_buttonLock->setToolTip(tr("View event information on mouseover"));
m_buttonLock->setCheckable(true);
m_buttonLock->setChecked(false);
connect(m_buttonLock, SIGNAL(clicked(bool)), this, SLOT(toggleLockMode(bool)));
connect(this, SIGNAL(enableToolbar(bool)), m_buttonLock, SLOT(setEnabled(bool)));
connect(this, SIGNAL(lockModeChanged(bool)), m_buttonLock, SLOT(setChecked(bool)));
toolBarLayout->addWidget(m_buttonLock);
toolBarLayout->addWidget(buttonPrev);
toolBarLayout->addWidget(buttonNext);
toolBarLayout->addWidget(buttonZoomControls);
@@ -264,6 +274,7 @@ void TraceWindow::reset(QDeclarativeDebugConnection *conn)
connect(m_mainView->rootObject(), SIGNAL(updateCursorPosition()), this, SLOT(updateCursorPosition()));
connect(m_mainView->rootObject(), SIGNAL(updateTimer()), this, SLOT(updateTimer()));
connect(m_mainView->rootObject(), SIGNAL(updateRangeButton()), this, SLOT(updateRangeButton()));
connect(m_mainView->rootObject(), SIGNAL(updateLockButton()), this, SLOT(updateLockButton()));
connect(m_eventList, SIGNAL(countChanged()), this, SLOT(updateToolbar()));
connect(this, SIGNAL(jumpToPrev()), m_mainView->rootObject(), SLOT(prevEvent()));
connect(this, SIGNAL(jumpToNext()), m_mainView->rootObject(), SLOT(nextEvent()));
@@ -340,6 +351,21 @@ void TraceWindow::updateRangeButton()
emit rangeModeChanged(rangeMode);
}
void TraceWindow::toggleLockMode(bool active)
{
bool lockMode = !m_mainView->rootObject()->property("selectionLocked").toBool();
if (active != lockMode) {
m_mainView->rootObject()->setProperty("selectionLocked", QVariant(!active));
m_mainView->rootObject()->setProperty("selectedItem", QVariant(-1));
}
}
void TraceWindow::updateLockButton()
{
bool lockMode = !m_mainView->rootObject()->property("selectionLocked").toBool();
emit lockModeChanged(lockMode);
}
void TraceWindow::setRecording(bool recording)
{
if (recording) {

View File

@@ -103,7 +103,9 @@ public slots:
void clearDisplay();
void updateToolbar();
void toggleRangeMode(bool);
void toggleLockMode(bool);
void updateRangeButton();
void updateLockButton();
void setZoomLevel(int zoomLevel);
void updateRange();
void mouseWheelMoved(int x, int y, int delta);
@@ -124,6 +126,7 @@ signals:
void jumpToPrev();
void jumpToNext();
void rangeModeChanged(bool);
void lockModeChanged(bool);
void enableToolbar(bool);
void zoomLevelChanged(int);
void updateViewZoom(QVariant zoomLevel);
@@ -155,6 +158,7 @@ private:
QWeakPointer<ZoomControl> m_zoomControl;
QToolButton *m_buttonRange;
QToolButton *m_buttonLock;
QWidget *m_zoomToolbar;
int m_currentZoomLevel;
};