]> git.cworth.org Git - apitrace/commitdiff
Rewrote profile graph drawing code.
authorJames Benton <jbenton@vmware.com>
Fri, 7 Sep 2012 17:38:15 +0000 (18:38 +0100)
committerJames Benton <jbenton@vmware.com>
Fri, 7 Sep 2012 17:38:56 +0000 (18:38 +0100)
Graphing is now reasonably generic to allow for future graphs to be fairly
easily added without too much replication of code.

30 files changed:
gui/CMakeLists.txt
gui/calldurationgraph.h [new file with mode: 0644]
gui/graphing/frameaxiswidget.cpp [new file with mode: 0644]
gui/graphing/frameaxiswidget.h [new file with mode: 0644]
gui/graphing/graphaxiswidget.cpp [new file with mode: 0644]
gui/graphing/graphaxiswidget.h [new file with mode: 0644]
gui/graphing/graphing.h [new file with mode: 0644]
gui/graphing/graphlabelwidget.h [new file with mode: 0644]
gui/graphing/graphview.cpp [new file with mode: 0644]
gui/graphing/graphview.h [new file with mode: 0644]
gui/graphing/graphwidget.cpp [new file with mode: 0644]
gui/graphing/graphwidget.h [new file with mode: 0644]
gui/graphing/heatmapverticalaxiswidget.cpp [new file with mode: 0644]
gui/graphing/heatmapverticalaxiswidget.h [new file with mode: 0644]
gui/graphing/heatmapview.cpp [new file with mode: 0644]
gui/graphing/heatmapview.h [new file with mode: 0644]
gui/graphing/histogramview.cpp [new file with mode: 0644]
gui/graphing/histogramview.h [new file with mode: 0644]
gui/graphing/timeaxiswidget.cpp [new file with mode: 0644]
gui/graphing/timeaxiswidget.h [new file with mode: 0644]
gui/graphwidget.cpp [deleted file]
gui/graphwidget.h [deleted file]
gui/profiledialog.cpp
gui/profiledialog.h
gui/profileheatmap.h [new file with mode: 0644]
gui/profiletablemodel.cpp
gui/profiling.h [new file with mode: 0644]
gui/timelinewidget.cpp [deleted file]
gui/timelinewidget.h [deleted file]
gui/ui/profiledialog.ui

index 89075da1a618a65cd2dd84f2f73453968afef1da..ece66434a1ad922e502bb58583e0294144450118 100644 (file)
@@ -10,7 +10,6 @@ set(qapitrace_SRCS
    apitracemodel.cpp
    argumentseditor.cpp
    glsledit.cpp
-   graphwidget.cpp
    imageviewer.cpp
    jumpwidget.cpp
    mainwindow.cpp
@@ -26,8 +25,15 @@ set(qapitrace_SRCS
    traceloader.cpp
    traceprocess.cpp
    trimprocess.cpp
-   timelinewidget.cpp
    vertexdatainterpreter.cpp
+   graphing/frameaxiswidget.cpp
+   graphing/graphwidget.cpp
+   graphing/graphaxiswidget.cpp
+   graphing/graphview.cpp
+   graphing/heatmapview.cpp
+   graphing/heatmapverticalaxiswidget.cpp
+   graphing/histogramview.cpp
+   graphing/timeaxiswidget.cpp
  )
 
 qt4_automoc(${qapitrace_SRCS})
diff --git a/gui/calldurationgraph.h b/gui/calldurationgraph.h
new file mode 100644 (file)
index 0000000..0c7e8c9
--- /dev/null
@@ -0,0 +1,173 @@
+#ifndef CALLDURATIONGRAPH_H
+#define CALLDURATIONGRAPH_H
+
+#include "graphing/graphwidget.h"
+#include "trace_profiler.hpp"
+#include "profiling.h"
+
+/**
+ * Wrapper for call duration graphs.
+ *
+ * This implements the transformSelectionIn and transformSelectionOut to
+ * allow sharing the selection between the graphs and the heatmap as they
+ * are using different scales. The duration graphs have call.no on the X-axis
+ * whereas the heatmap has time on the X axis.
+ */
+class CallDurationGraph : public GraphWidget {
+public:
+    CallDurationGraph(QWidget* parent = 0) :
+        GraphWidget(parent),
+        m_profile(NULL)
+    {
+    }
+
+    void setProfile(const trace::Profile* profile)
+    {
+        m_profile = profile;
+    }
+
+protected:
+    /* Transform from time-based horizontal selection to call no based. */
+    virtual SelectionState transformSelectionIn(SelectionState state)
+    {
+        if (!m_profile || state.type != SelectionState::Horizontal) {
+            return state;
+        }
+
+        qint64 timeStart = state.start;
+        qint64 timeEnd = state.end;
+
+        std::vector<trace::Profile::Call>::const_iterator itr;
+
+        itr =
+            Profiling::binarySearchTimespan<
+                trace::Profile::Call,
+                &trace::Profile::Call::cpuStart,
+                &trace::Profile::Call::cpuDuration>
+            (m_profile->calls.begin(), m_profile->calls.end(), timeStart, true);
+
+        state.start = itr - m_profile->calls.begin();
+
+        itr =
+            Profiling::binarySearchTimespan<
+                trace::Profile::Call,
+                &trace::Profile::Call::cpuStart,
+                &trace::Profile::Call::cpuDuration>
+            (m_profile->calls.begin(), m_profile->calls.end(), timeEnd, true);
+
+        state.end = itr - m_profile->calls.begin();
+
+        return state;
+    }
+
+    virtual SelectionState transformSelectionOut(SelectionState state)
+    {
+        if (!m_profile || state.type != SelectionState::Horizontal) {
+            return state;
+        }
+
+        qint64 start = qMax<qint64>(0, state.start);
+        qint64 end = qMin<qint64>(state.end, m_profile->calls.size());
+
+        /* Call based -> time based */
+        state.start = m_profile->calls[start].cpuStart;
+        state.end = m_profile->calls[end].cpuStart + m_profile->calls[end].cpuDuration;
+
+        return state;
+    }
+
+private:
+    const trace::Profile* m_profile;
+};
+
+/* Data provider for call duration graphs */
+class CallDurationDataProvider : public GraphDataProvider {
+public:
+    CallDurationDataProvider(const trace::Profile* profile, bool gpu) :
+        m_gpu(gpu),
+        m_profile(profile),
+        m_selectionState(NULL)
+    {
+    }
+
+    virtual qint64 size() const
+    {
+        return m_profile ? m_profile->calls.size() : 0;
+    }
+
+    virtual bool selected(qint64 index) const
+    {
+        if (m_selectionState) {
+            if (m_selectionState->type == SelectionState::Horizontal) {
+                if (m_selectionState->start <= index && index < m_selectionState->end) {
+                    return true;
+                }
+            } else if (m_selectionState->type == SelectionState::Vertical) {
+                return m_profile->calls[index].program == m_selectionState->start;
+            }
+        }
+
+        return false;
+    }
+
+    virtual void setSelectionState(SelectionState* state)
+    {
+        m_selectionState = state;
+    }
+
+    virtual qint64 value(qint64 index) const
+    {
+        if (m_gpu) {
+            return m_profile->calls[index].gpuDuration;
+        } else {
+            return m_profile->calls[index].cpuDuration;
+        }
+    }
+
+    virtual void itemDoubleClicked(qint64 index) const
+    {
+        if (!m_profile) {
+            return;
+        }
+
+        if (index < 0 || index >= m_profile->calls.size()) {
+            return;
+        }
+
+        const trace::Profile::Call& call = m_profile->calls[index];
+        Profiling::jumpToCall(call.no);
+    }
+
+    virtual QString itemTooltip(qint64 index) const
+    {
+        if (!m_profile) {
+            return QString();
+        }
+
+        if (index < 0 || index >= m_profile->calls.size()) {
+            return QString();
+        }
+
+        const trace::Profile::Call& call = m_profile->calls[index];
+
+        QString text;
+        text  = QString::fromStdString(call.name);
+        text += QString("\nCall: %1").arg(call.no);
+        text += QString("\nCPU Duration: %1").arg(Profiling::getTimeString(call.cpuDuration));
+
+        if (call.pixels >= 0) {
+            text += QString("\nGPU Duration: %1").arg(Profiling::getTimeString(call.gpuDuration));
+            text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call.pixels));
+            text += QString("\nProgram: %1").arg(call.program);
+        }
+
+        return text;
+    }
+
+private:
+    bool m_gpu;
+    const trace::Profile* m_profile;
+    SelectionState* m_selectionState;
+};
+
+#endif
diff --git a/gui/graphing/frameaxiswidget.cpp b/gui/graphing/frameaxiswidget.cpp
new file mode 100644 (file)
index 0000000..ad5d868
--- /dev/null
@@ -0,0 +1,99 @@
+#include "frameaxiswidget.h"
+
+#include <QPainter>
+
+FrameAxisWidget::FrameAxisWidget(QWidget* parent) :
+    GraphAxisWidget(parent),
+    m_data(NULL)
+{
+    setSelectable(GraphAxisWidget::Range);
+}
+
+void FrameAxisWidget::setDataProvider(FrameDataProvider* data)
+{
+    delete m_data;
+    m_data = data;
+}
+
+void FrameAxisWidget::paintEvent(QPaintEvent *)
+{
+    if (!m_data || m_orientation != GraphAxisWidget::Horizontal) {
+        /* TODO: Vertical axis support */
+        return;
+    }
+
+    QPainter painter(this);
+    painter.setPen(Qt::black);
+    painter.setBrush(Qt::lightGray);
+    painter.drawRect(0, 0, width() - 1, height() - 1);
+
+    qint64 range = m_valueEnd - m_valueBegin;
+    double dxdv = width() / (double)range;
+    double scroll = dxdv * m_valueBegin;
+    int lastLabel = -9999;
+
+    /* Iterate over frames, drawing a label when there is space to do so */
+    for (unsigned i = 0; i < m_data->size(); ++i) {
+        static const int padding = 4;
+        qint64 start = m_data->frameStart(i);
+        qint64 end = m_data->frameEnd(i);
+        bool visible = false;
+
+        if (start > m_valueEnd) {
+            break;
+        }
+
+        if (end < m_valueBegin) {
+            visible = false;
+        }
+
+        double left = dxdv * start;
+        double right = dxdv * end;
+        QString text = QString("%1").arg(i);
+
+        int width = painter.fontMetrics().width(text) + padding * 2;
+
+        if (right > scroll) {
+            visible = true;
+        }
+
+        if (left - lastLabel > width) {
+            lastLabel = left + width;
+
+            if (visible) {
+                int textX;
+
+                if (left < scroll && right - left > width) {
+                    if (right - scroll > width) {
+                        textX = 0;
+                    } else {
+                        textX = right - scroll - width;
+                    }
+                } else {
+                    textX = left - scroll;
+                }
+
+                painter.drawText(textX + padding, 0, width - padding, height() - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
+                painter.drawLine(left - scroll, height() / 2, left - scroll, height() - 1);
+            }
+        } else if (visible) {
+            painter.drawLine(left - scroll, height() * 3/4.0, left - scroll, height() - 1);
+        }
+    }
+
+    /* Draw selection */
+    if (hasSelection()) {
+        double left = (dxdv * m_selectionState->start) - scroll;
+        double right = (dxdv * m_selectionState->end) - scroll;
+
+        painter.setPen(Qt::green);
+
+        if (left >= 0 && left <= width()) {
+            painter.drawLine(left, 0, left, height());
+        }
+
+        if (right >= 0 && right <= width()) {
+            painter.drawLine(right, 0, right, height());
+        }
+    }
+}
diff --git a/gui/graphing/frameaxiswidget.h b/gui/graphing/frameaxiswidget.h
new file mode 100644 (file)
index 0000000..db091d9
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef FRAMEAXISWIDGET_H
+#define FRAMEAXISWIDGET_H
+
+#include "graphaxiswidget.h"
+
+class FrameDataProvider {
+public:
+    /* Number of frames */
+    virtual unsigned size() const = 0;
+
+    /* Start and end values of frame */
+    virtual qint64 frameStart(unsigned frame) const = 0;
+    virtual qint64 frameEnd(unsigned frame) const = 0;
+};
+
+/**
+ * A generic axis which will draw frame numbers over a period of values.
+ * Frames designated by start / end values.
+ */
+class FrameAxisWidget : public GraphAxisWidget {
+public:
+    FrameAxisWidget(QWidget* parent = 0);
+
+    void setDataProvider(FrameDataProvider* data);
+
+    virtual void paintEvent(QPaintEvent *e);
+
+protected:
+    FrameDataProvider* m_data;
+};
+
+#endif
diff --git a/gui/graphing/graphaxiswidget.cpp b/gui/graphing/graphaxiswidget.cpp
new file mode 100644 (file)
index 0000000..0adeadd
--- /dev/null
@@ -0,0 +1,141 @@
+#include "graphaxiswidget.h"
+
+#include <QMouseEvent>
+
+GraphAxisWidget::GraphAxisWidget(QWidget* parent) :
+    QWidget(parent),
+    m_selectable(None),
+    m_selectionState(NULL)
+{
+}
+
+
+bool GraphAxisWidget::hasSelection()
+{
+    if (!m_selectionState) {
+        return false;
+    }
+
+    if (m_selectionState->type == SelectionState::Horizontal && m_orientation == GraphAxisWidget::Horizontal) {
+        return true;
+    }
+
+    if (m_selectionState->type == SelectionState::Vertical && m_orientation == GraphAxisWidget::Vertical) {
+        return true;
+    }
+
+    return false;
+}
+
+
+void GraphAxisWidget::setSelectable(SelectionStyle selectable)
+{
+    m_selectable = selectable;
+}
+
+
+void GraphAxisWidget::setSelectionState(SelectionState* state)
+{
+    m_selectionState = state;
+}
+
+
+void GraphAxisWidget::setOrientation(Orientation v)
+{
+    m_orientation = v;
+
+    if (m_orientation == Horizontal) {
+        setMinimumWidth(60);
+    } else {
+        setMinimumHeight(60);
+    }
+}
+
+
+void GraphAxisWidget::mouseMoveEvent(QMouseEvent *e)
+{
+    if (m_selectable == None) {
+        return;
+    }
+
+    int pos, max;
+
+    if (m_orientation == Horizontal) {
+        pos = e->x();
+        max = width();
+    } else {
+        pos = e->y();
+        max = height();
+    }
+
+    double value = m_valueEnd - m_valueBegin;
+    value *= pos / (double)max;
+    value += m_valueBegin;
+
+    if (e->buttons().testFlag(Qt::LeftButton)) {
+        m_selectionState->start = qMin<qint64>(m_mousePressValue, value);
+        m_selectionState->end = qMax<qint64>(m_mousePressValue, value);
+        m_selectionState->type = m_orientation == Horizontal ? SelectionState::Horizontal : SelectionState::Vertical;
+        emit selectionChanged();
+        update();
+    }
+}
+
+
+void GraphAxisWidget::mousePressEvent(QMouseEvent *e)
+{
+    if (m_selectable == None) {
+        return;
+    }
+
+    int pos, max;
+
+    if (m_orientation == Horizontal) {
+        pos = e->x();
+        max = width();
+    } else {
+        pos = e->y();
+        max = height();
+    }
+
+    double value = m_valueEnd - m_valueBegin;
+    value *= pos / (double)max;
+    value += m_valueBegin;
+
+    m_mousePressPosition = e->pos();
+    m_mousePressValue = value;
+}
+
+
+void GraphAxisWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (m_selectable == None) {
+        return;
+    }
+
+    int dx = qAbs(m_mousePressPosition.x() - e->x());
+    int dy = qAbs(m_mousePressPosition.y() - e->y());
+
+    if (dx + dy < 2) {
+        m_selectionState->type = SelectionState::None;
+        emit selectionChanged();
+    }
+}
+
+
+void GraphAxisWidget::setRange(qint64 min, qint64 max)
+{
+    m_valueMin = min;
+    m_valueMax = max;
+    update();
+}
+
+
+void GraphAxisWidget::setView(qint64 start, qint64 end)
+{
+    m_valueBegin = start;
+    m_valueEnd = end;
+    update();
+}
+
+#include "graphaxiswidget.moc"
diff --git a/gui/graphing/graphaxiswidget.h b/gui/graphing/graphaxiswidget.h
new file mode 100644 (file)
index 0000000..6ae0080
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef GRAPHAXISWIDGET_H
+#define GRAPHAXISWIDGET_H
+
+#include "graphing.h"
+
+#include <QWidget>
+
+/**
+ * The generic base class of all graph axes.
+ *
+ * Handles orientation, simple selections, and view area.
+ */
+class GraphAxisWidget : public QWidget {
+    Q_OBJECT
+public:
+    enum Orientation {
+        Horizontal,
+        Vertical
+    };
+
+    enum SelectionStyle {
+        None,
+        Single,
+        Range
+    };
+
+public:
+    GraphAxisWidget(QWidget* parent = 0);
+    virtual ~GraphAxisWidget(){}
+
+    /* Is this axis part of the active selection */
+    bool hasSelection();
+
+    void setSelectable(SelectionStyle selectable);
+    void setSelectionState(SelectionState* state);
+
+    void setOrientation(Orientation v);
+
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseReleaseEvent(QMouseEvent *e);
+
+public slots:
+    /* The minimum and maximum values of this axis */
+    void setRange(qint64 min, qint64 max);
+
+    /* The currently visible range of values */
+    void setView(qint64 start, qint64 end);
+
+signals:
+    void selectionChanged();
+
+protected:
+    Orientation m_orientation;
+
+    /* The min/max value of this axis */
+    qint64 m_valueMin;
+    qint64 m_valueMax;
+
+    /* The highest and lowest currently visible value */
+    qint64 m_valueBegin;
+    qint64 m_valueEnd;
+
+    /* Selection */
+    SelectionStyle m_selectable;
+    SelectionState* m_selectionState;
+
+    /* Mouse tracking */
+    QPoint m_mousePressPosition;
+    qint64 m_mousePressValue;
+};
+
+#endif
diff --git a/gui/graphing/graphing.h b/gui/graphing/graphing.h
new file mode 100644 (file)
index 0000000..35b4a91
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef GRAPHING_H
+#define GRAPHING_H
+
+#include <QString>
+
+/**
+ * A simple struct to hold a horizontal or vertical selection
+ */
+struct SelectionState {
+    enum SelectionType {
+        None,
+        Horizontal,
+        Vertical
+    };
+
+    SelectionType type;
+    qint64 start;
+    qint64 end;
+};
+
+
+/**
+ * Fairly generic data provider for graphs
+ */
+class GraphDataProvider {
+public:
+    virtual ~GraphDataProvider(){}
+
+    /* Number of elements in graph */
+    virtual qint64 size() const = 0;
+
+    /* Returns value for index */
+    virtual qint64 value(qint64 index) const = 0;
+
+    /* Is the item at index selected */
+    virtual bool selected(qint64 index) const = 0;
+
+    /* Get mouse hover tooltip for item */
+    virtual QString itemTooltip(qint64 index) const = 0;
+
+    /* Called on item double click */
+    virtual void itemDoubleClicked(qint64 index) const = 0;
+
+    /* Set pointer to selection state */
+    virtual void setSelectionState(SelectionState* state) = 0;
+};
+
+#endif
diff --git a/gui/graphing/graphlabelwidget.h b/gui/graphing/graphlabelwidget.h
new file mode 100644 (file)
index 0000000..49c0cd4
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef GRAPHLABELWIDGET_H
+#define GRAPHLABELWIDGET_H
+
+#include <QWidget>
+#include <QPainter>
+
+/**
+ * A very simple label widget, basically a box with text in.
+ */
+class GraphLabelWidget : public QWidget {
+public:
+    GraphLabelWidget(QString text = QString(), QWidget* parent = 0) :
+        QWidget(parent),
+        m_flags(Qt::AlignHCenter | Qt::AlignVCenter),
+        m_text(text)
+    {
+    }
+
+    void setText(const QString& text)
+    {
+        m_text = text;
+    }
+
+    void setFlags(int flags)
+    {
+        m_flags = flags;
+    }
+
+    virtual void paintEvent(QPaintEvent *)
+    {
+        QPainter painter(this);
+        painter.setPen(Qt::black);
+        painter.fillRect(rect(), Qt::lightGray);
+        painter.drawText(rect(), m_flags, m_text);
+    }
+
+protected:
+    int m_flags;
+    QString m_text;
+};
+
+#endif
diff --git a/gui/graphing/graphview.cpp b/gui/graphing/graphview.cpp
new file mode 100644 (file)
index 0000000..6ecac57
--- /dev/null
@@ -0,0 +1,187 @@
+#include "graphview.h"
+
+#include <QMouseEvent>
+#include <QApplication>
+
+GraphView::GraphView(QWidget* parent) :
+    QWidget(parent),
+    m_selectionState(NULL),
+    m_viewLeft(0),
+    m_viewRight(0),
+    m_viewBottom(0),
+    m_viewTop(0),
+    m_graphLeft(0),
+    m_graphRight(0),
+    m_graphBottom(0),
+    m_graphTop(0),
+    m_viewWidth(0),
+    m_viewWidthMin(0),
+    m_viewWidthMax(0),
+    m_viewHeight(0),
+    m_viewHeightMin(0),
+    m_viewHeightMax(0)
+{
+    memset(&m_previous, -1, sizeof(m_previous));
+}
+
+void GraphView::update()
+{
+    if (m_graphLeft != m_previous.m_graphLeft || m_graphRight != m_previous.m_graphRight) {
+        m_previous.m_graphLeft = m_graphLeft;
+        m_previous.m_graphRight = m_graphRight;
+
+        emit horizontalRangeChanged(m_graphLeft, m_graphRight);
+    }
+
+    if (m_viewLeft != m_previous.m_viewLeft || m_viewRight != m_previous.m_viewRight) {
+        m_previous.m_viewLeft = m_viewLeft;
+        m_previous.m_viewRight = m_viewRight;
+
+        emit horizontalViewChanged(m_viewLeft, m_viewRight);
+    }
+
+    if (m_graphBottom != m_previous.m_graphBottom || m_graphTop != m_previous.m_graphTop) {
+        m_previous.m_graphBottom = m_graphBottom;
+        m_previous.m_graphTop = m_graphTop;
+
+        emit verticalRangeChanged(m_graphBottom, m_graphTop);
+    }
+
+    if (m_viewBottom != m_previous.m_viewBottom || m_viewTop != m_previous.m_viewTop) {
+        m_previous.m_viewBottom = m_viewBottom;
+        m_previous.m_viewTop = m_viewTop;
+
+        emit verticalViewChanged(m_viewBottom, m_viewTop);
+    }
+
+    QWidget::update();
+}
+
+void GraphView::resizeEvent(QResizeEvent *)
+{
+    m_viewHeight = height();
+    m_viewHeightMin = m_viewHeight;
+    m_viewHeightMax = m_viewHeight;
+
+    m_viewTop = m_viewBottom + m_viewHeight;
+
+    update();
+}
+
+void GraphView::wheelEvent(QWheelEvent *e)
+{
+    int zoomPercent = 10;
+
+    /* If holding Ctrl key then zoom 2x faster */
+    if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
+        zoomPercent = 20;
+    }
+
+    /* Zoom view by adjusting width */
+    double dt = m_viewWidth;
+    double size = dt;
+    size *= -e->delta();
+
+    /* Zoom deltas normally come in increments of 120 */
+    size /= 120 * (100 / zoomPercent);
+
+    m_viewWidth += size;
+    m_viewWidth = qBound(m_viewWidthMin, m_viewWidth, m_viewWidthMax);
+
+    /* Scroll view to zoom around mouse */
+    dt -= m_viewWidth;
+    dt *= e->x();
+    dt /= width();
+
+    m_viewLeft = dt + m_viewLeft;
+    m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth);
+    m_viewRight = m_viewLeft + m_viewWidth;
+
+    update();
+}
+
+void GraphView::mouseMoveEvent(QMouseEvent *e)
+{
+    if (e->buttons().testFlag(Qt::LeftButton)) {
+        /* Horizontal scroll */
+        double dvdx = m_viewWidth / (double)width();
+        dvdx *= m_mousePressPosition.x() - e->pos().x();
+
+        m_viewLeft = m_mousePressViewLeft + dvdx;
+        m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth);
+        m_viewRight = m_viewLeft + m_viewWidth;
+
+        /* Vertical scroll */
+        double dvdy = m_viewHeight / (double)height();
+        dvdy *= m_mousePressPosition.y() - e->pos().y();
+
+        m_viewBottom = m_mousePressViewBottom + dvdy;
+        m_viewBottom = qBound(m_graphBottom, m_viewBottom, m_graphTop - m_viewHeight);
+        m_viewTop = m_viewBottom + m_viewHeight;
+
+        update();
+    }
+}
+
+void GraphView::mousePressEvent(QMouseEvent *e)
+{
+    m_mousePressPosition = e->pos();
+    m_mousePressViewLeft = m_viewLeft;
+    m_mousePressViewBottom = m_viewBottom;
+}
+
+void GraphView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (m_selectionState) {
+        m_selectionState->type = SelectionState::None;
+        emit selectionChanged();
+    }
+}
+
+void GraphView::setSelectionState(SelectionState* state)
+{
+    m_selectionState = state;
+}
+
+void GraphView::setHorizontalView(qint64 start, qint64 end)
+{
+    m_viewLeft = qBound(m_graphLeft, start, m_graphRight - (end - start));
+    m_viewRight = qBound(m_graphLeft, end, m_graphRight);
+    m_viewWidth = m_viewRight - m_viewLeft;
+    update();
+}
+
+void GraphView::setVerticalView(qint64 start, qint64 end)
+{
+    m_viewBottom = qBound(m_graphBottom, start, m_graphTop - (end - start));
+    m_viewTop = qBound(m_graphBottom, end, m_graphTop);
+    m_viewHeight = m_viewTop - m_viewBottom;
+    update();
+}
+
+void GraphView::setDefaultView(qint64 min, qint64 max)
+{
+    m_graphLeft = min;
+    m_graphRight = max;
+    m_viewWidth = max - min;
+
+    m_viewWidthMin = 1;
+    m_viewWidthMax = m_viewWidth;
+
+    m_viewLeft = min;
+    m_viewRight = max;
+
+    m_viewHeight = height();
+    m_viewHeightMin = m_viewHeight;
+    m_viewHeightMax = m_viewHeight;
+
+    m_viewBottom = 0;
+    m_viewTop = m_viewHeight;
+
+    m_graphBottom = 0;
+    m_graphTop = m_viewHeight;
+
+    update();
+}
+
+#include "graphview.moc"
diff --git a/gui/graphing/graphview.h b/gui/graphing/graphview.h
new file mode 100644 (file)
index 0000000..6b881d4
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef GRAPHVIEW_H
+#define GRAPHVIEW_H
+
+#include "graphing.h"
+
+#include <QWidget>
+
+/**
+ * The generic base class for a graph's view, this is the component that
+ * displays the actual data for the graph.
+ *
+ * - Stores the view area within the graph
+ * - Simple user interaction such as translating and zooming with mouse
+ * - Selection tracking synchronised with axis
+ */
+class GraphView : public QWidget {
+    Q_OBJECT
+public:
+    GraphView(QWidget* parent = 0);
+    virtual ~GraphView(){}
+
+    virtual void update();
+
+    virtual void resizeEvent(QResizeEvent *);
+
+    virtual void wheelEvent(QWheelEvent *e);
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+    virtual void setSelectionState(SelectionState* state);
+
+    void setHorizontalView(qint64 start, qint64 end);
+    void setVerticalView(qint64 start, qint64 end);
+
+protected:
+    void setDefaultView(qint64 min, qint64 max);
+
+signals:
+    void selectionChanged();
+
+    void verticalViewChanged(qint64 start, qint64 end);
+    void verticalRangeChanged(qint64 min, qint64 max);
+
+    void horizontalRangeChanged(qint64 min, qint64 max);
+    void horizontalViewChanged(qint64 start, qint64 end);
+
+protected:
+    /* Viewport area */
+    qint64 m_viewLeft;
+    qint64 m_viewRight;
+    qint64 m_viewBottom;
+    qint64 m_viewTop;
+
+    /* Graph limits */
+    qint64 m_graphLeft;
+    qint64 m_graphRight;
+    qint64 m_graphBottom;
+    qint64 m_graphTop;
+
+    /* Viewport width (m_viewRight - m_viewLeft), used for zoom */
+    qint64 m_viewWidth;
+    qint64 m_viewWidthMin;
+    qint64 m_viewWidthMax;
+
+    /* Viewport height (m_viewTop - m_viewBottom), used for zoom */
+    qint64 m_viewHeight;
+    qint64 m_viewHeightMin;
+    qint64 m_viewHeightMax;
+
+    /* Mouse tracking */
+    QPoint m_mousePressPosition;
+    qint64 m_mousePressViewLeft;
+    qint64 m_mousePressViewBottom;
+
+    /* Selection */
+    SelectionState* m_selectionState;
+
+    /* State from the last update() call */
+    struct PreviousUpdate {
+        qint64 m_viewLeft;
+        qint64 m_viewRight;
+        qint64 m_viewBottom;
+        qint64 m_viewTop;
+
+        qint64 m_graphLeft;
+        qint64 m_graphRight;
+        qint64 m_graphBottom;
+        qint64 m_graphTop;
+    } m_previous;
+};
+
+#endif
diff --git a/gui/graphing/graphwidget.cpp b/gui/graphing/graphwidget.cpp
new file mode 100644 (file)
index 0000000..88a86cc
--- /dev/null
@@ -0,0 +1,583 @@
+#include "graphwidget.h"
+
+#include <QScrollBar>
+
+GraphWidget::GraphWidget(QWidget* parent) :
+    QWidget(parent),
+    m_view(NULL),
+    m_label(NULL),
+    m_axisTop(NULL),
+    m_axisLeft(NULL),
+    m_axisRight(NULL),
+    m_axisBottom(NULL),
+    m_horizontalScrollbar(NULL),
+    m_horizontalMin(0),
+    m_horizontalMax(0),
+    m_horizontalStart(0),
+    m_horizontalEnd(0),
+    m_horizontalScrollbarPolicy(Qt::ScrollBarAlwaysOff),
+    m_verticalScrollbar(NULL),
+    m_verticalMin(0),
+    m_verticalMax(0),
+    m_verticalStart(0),
+    m_verticalEnd(0),
+    m_verticalScrollbarPolicy(Qt::ScrollBarAlwaysOff)
+{
+    m_selection.type = SelectionState::None;
+
+    m_verticalScrollbar = new QScrollBar(this);
+    m_verticalScrollbar->setOrientation(Qt::Vertical);
+    m_verticalScrollbar->hide();
+    m_verticalScrollbar->resize(m_verticalScrollbar->sizeHint());
+
+    m_horizontalScrollbar = new QScrollBar(this);
+    m_horizontalScrollbar->setOrientation(Qt::Horizontal);
+    m_horizontalScrollbar->hide();
+    m_horizontalScrollbar->resize(m_horizontalScrollbar->sizeHint());
+
+    updateLayout();
+    setAutoFillBackground(true);
+}
+
+
+GraphView* GraphWidget::view()
+{
+    return m_view;
+}
+
+
+GraphLabelWidget* GraphWidget::label()
+{
+    return m_label;
+}
+
+
+GraphAxisWidget* GraphWidget::axis(AxisPosition pos)
+{
+    switch(pos) {
+        case AxisTop:
+            return m_axisTop;
+
+        case AxisLeft:
+            return m_axisLeft;
+
+        case AxisRight:
+            return m_axisRight;
+
+        case AxisBottom:
+            return m_axisBottom;
+
+        default:
+            return NULL;
+    }
+}
+
+
+void GraphWidget::setView(GraphView* view)
+{
+    delete m_view;
+    m_view = view;
+
+    updateConnections();
+
+    m_view->setSelectionState(&m_selection);
+    m_view->show();
+    m_view->update();
+}
+
+
+void GraphWidget::setLabel(GraphLabelWidget* label)
+{
+    delete m_label;
+    m_label = label;
+}
+
+
+void GraphWidget::setAxis(AxisPosition pos, GraphAxisWidget* axis)
+{
+    switch(pos) {
+        case AxisTop:
+            delete m_axisTop;
+            m_axisTop = axis;
+            m_axisTop->setOrientation(GraphAxisWidget::Horizontal);
+            m_axisTop->setSelectionState(&m_selection);
+            break;
+
+        case AxisLeft:
+            delete m_axisLeft;
+            m_axisLeft = axis;
+            m_axisLeft->setOrientation(GraphAxisWidget::Vertical);
+            m_axisLeft->setSelectionState(&m_selection);
+            break;
+
+        case AxisRight:
+            delete m_axisRight;
+            m_axisRight = axis;
+            m_axisRight->setOrientation(GraphAxisWidget::Vertical);
+            m_axisRight->setSelectionState(&m_selection);
+            break;
+
+        case AxisBottom:
+            delete m_axisBottom;
+            m_axisBottom = axis;
+            m_axisBottom->setOrientation(GraphAxisWidget::Horizontal);
+            m_axisBottom->setSelectionState(&m_selection);
+            break;
+    }
+
+    updateConnections();
+    updateSelection();
+    axis->show();
+}
+
+
+void GraphWidget::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy)
+{
+    m_horizontalScrollbarPolicy = policy;
+    updateScrollbars();
+}
+
+
+void GraphWidget::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
+{
+    m_verticalScrollbarPolicy = policy;
+    updateScrollbars();
+}
+
+
+void GraphWidget::resizeEvent(QResizeEvent *e)
+{
+    updateLayout();
+    update();
+}
+
+
+/* Used if a selection would be shared between graphs with different axis */
+SelectionState GraphWidget::transformSelectionIn(SelectionState state)
+{
+    return state;
+}
+
+
+/* Used if a selection would be shared between graphs with different axis */
+SelectionState GraphWidget::transformSelectionOut(SelectionState state)
+{
+    return state;
+}
+
+
+/* Update the scrollbars based on current view */
+void GraphWidget::updateScrollbars()
+{
+    /* Vertical scroll bar */
+    qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);
+
+    if (size <= INT_MAX) {
+        m_verticalScrollbar->setValue(m_verticalStart - m_verticalMin);
+        m_verticalScrollbar->setPageStep(m_verticalEnd - m_verticalStart);
+        m_verticalScrollbar->setRange(0, size);
+    } else {
+        /* QScrollBar only supports up to INT_MAX values,
+         * here we must scale our values to match this */
+        double curSize = m_verticalEnd - m_verticalStart;
+        double pages = (m_verticalMax - m_verticalMin) / curSize;
+        double value = (m_verticalStart - m_verticalMin) / curSize;
+
+        m_verticalScrollbar->setValue(value);
+        m_verticalScrollbar->setPageStep(1);
+        m_verticalScrollbar->setRange(0, pages);
+    }
+
+    /* Adhere to scrollbar policy */
+    bool visible = false;
+
+    if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
+        visible = true;
+    } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
+        visible = false;
+    } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
+        visible = m_verticalMin != m_verticalStart || m_verticalMax != m_verticalEnd;
+    }
+
+    if (visible != m_verticalScrollbar->isVisible()) {
+        m_verticalScrollbar->setVisible(visible);
+        updateLayout();
+    }
+
+    /* Horizontal scroll bar */
+    size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);
+
+    if (size <= INT_MAX) {
+        m_horizontalScrollbar->setValue(m_horizontalStart - m_horizontalMin);
+        m_horizontalScrollbar->setPageStep(m_horizontalEnd - m_horizontalStart);
+        m_horizontalScrollbar->setRange(0, size);
+    } else {
+        /* QScrollBar only supports up to INT_MAX values,
+         * here we must scale our values to match this */
+        double dxdv = INT_MAX / (double)size;
+        double value = (m_horizontalStart - m_horizontalMin) * dxdv;
+        double pageStep = (m_horizontalEnd - m_horizontalStart) * dxdv;
+
+        m_horizontalScrollbar->setValue((int)value);
+        m_horizontalScrollbar->setPageStep((int)pageStep);
+        m_horizontalScrollbar->setRange(0, INT_MAX);
+    }
+
+    /* Adhere to scrollbar policy */
+    visible = false;
+
+    if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
+        visible = true;
+    } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
+        visible = false;
+    } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
+        visible =  m_horizontalMin != m_horizontalStart || m_horizontalMax != m_horizontalEnd;
+    }
+
+    if (visible != m_horizontalScrollbar->isVisible()) {
+        m_horizontalScrollbar->setVisible(visible);
+        updateLayout();
+    }
+}
+
+
+/* Update all signal / slot connections */
+void GraphWidget::updateConnections()
+{
+    if (m_view) {
+        connect(m_view, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+
+        connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), this, SLOT(horizontalViewChange(qint64,qint64)), Qt::UniqueConnection);
+        connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), this, SLOT(horizontalRangeChange(qint64,qint64)), Qt::UniqueConnection);
+
+        connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), this, SLOT(verticalViewChange(qint64,qint64)), Qt::UniqueConnection);
+        connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), this, SLOT(verticalRangeChange(qint64,qint64)), Qt::UniqueConnection);
+    }
+
+    if (m_axisTop) {
+        if (m_view) {
+            connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisTop, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+            connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisTop, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+        }
+
+        connect(m_axisTop, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+    }
+
+    if (m_axisLeft) {
+        if (m_view) {
+            connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisLeft, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+            connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisLeft, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+        }
+
+        connect(m_axisLeft, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+    }
+
+    if (m_axisRight) {
+        if (m_view) {
+            connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisRight, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+            connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisRight, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+        }
+
+        connect(m_axisRight, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+    }
+
+    if (m_axisBottom) {
+        if (m_view) {
+            connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisBottom, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+            connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisBottom, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+        }
+
+        connect(m_axisBottom, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+    }
+
+    if (m_horizontalScrollbar) {
+        connect(m_horizontalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(horizontalScrollAction(int)));
+    }
+
+    if (m_verticalScrollbar) {
+        connect(m_verticalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(verticalScrollAction(int)));
+    }
+}
+
+
+/* Recalculate the layout */
+void GraphWidget::updateLayout()
+{
+    int x, y;
+    int padX = 0, padY = 0;
+
+    if (m_axisTop) {
+        padY += m_axisTop->height();
+    }
+
+    if (m_axisBottom) {
+        padY += m_axisBottom->height();
+    }
+
+    if (m_axisLeft) {
+        padX += m_axisLeft->width();
+    }
+
+    if (m_axisRight) {
+        padX += m_axisRight->width();
+    }
+
+    if (m_horizontalScrollbar->isVisible()) {
+        padY += m_horizontalScrollbar->height();
+    }
+
+    if (m_verticalScrollbar->isVisible()) {
+        padX += m_verticalScrollbar->width();
+    }
+
+    if (m_axisTop) {
+        x = m_axisLeft ? m_axisLeft->width() : 0;
+        y = 0;
+
+        m_axisTop->move(x, y);
+        m_axisTop->resize(width() - padX, m_axisTop->height());
+    }
+
+    if (m_axisBottom) {
+        x = m_axisLeft ? m_axisLeft->width() : 0;
+        y = height() - m_axisBottom->height();
+
+        if (m_horizontalScrollbar->isVisible())  {
+            y -= m_horizontalScrollbar->height();
+        }
+
+        m_axisBottom->move(x, y);
+        m_axisBottom->resize(width() - padX, m_axisBottom->height());
+    }
+
+    if (m_axisLeft) {
+        x = 0;
+        y = m_axisTop ? m_axisTop->height() : 0;
+
+        m_axisLeft->move(x, y);
+        m_axisLeft->resize(m_axisLeft->width(), height() - padY);
+    }
+
+    if (m_axisRight) {
+        x = width() - m_axisRight->width();
+        y = m_axisTop ? m_axisTop->height() : 0;
+
+        if (m_verticalScrollbar->isVisible())  {
+            x -= m_verticalScrollbar->width();
+        }
+
+        m_axisRight->move(x, y);
+        m_axisRight->resize(m_axisRight->width(), height() - padY);
+    }
+
+    if (m_view) {
+        x = m_axisLeft ? m_axisLeft->width() : 0;
+        y = m_axisTop ? m_axisTop->height() : 0;
+
+        m_view->move(x, y);
+        m_view->resize(width() - padX, height() - padY);
+    }
+
+    if (m_label) {
+        if (m_axisTop && m_axisLeft) {
+            m_label->move(0, 0);
+            m_label->resize(m_axisLeft->width(), m_axisTop->height());
+        }
+    }
+
+    if (m_verticalScrollbar) {
+        m_verticalScrollbar->move(width() - m_verticalScrollbar->width(), 0);
+
+        if (m_horizontalScrollbar) {
+            m_verticalScrollbar->resize(m_verticalScrollbar->width(), height() - m_horizontalScrollbar->height());
+        } else {
+            m_verticalScrollbar->resize(m_verticalScrollbar->width(), height());
+        }
+    }
+
+    if (m_horizontalScrollbar) {
+        m_horizontalScrollbar->move(0, height() - m_horizontalScrollbar->height());
+
+        if (m_verticalScrollbar) {
+            m_horizontalScrollbar->resize(width() - m_verticalScrollbar->width(), m_horizontalScrollbar->height());
+        } else {
+            m_horizontalScrollbar->resize(width(), m_horizontalScrollbar->height());
+        }
+    }
+}
+
+
+void GraphWidget::setSelection(SelectionState state)
+{
+    m_selection = transformSelectionIn(state);
+    updateSelection(false);
+}
+
+
+void GraphWidget::setHorizontalView(qint64 start, qint64 end)
+{
+    if (m_view) {
+        m_view->setHorizontalView(start, end);
+    }
+}
+
+
+void GraphWidget::setVerticalView(qint64 start, qint64 end)
+{
+    if (m_view) {
+        m_view->setVerticalView(start, end);
+    }
+}
+
+
+/* Called when the view is translated / zoomed */
+void GraphWidget::verticalViewChange(qint64 start, qint64 end)
+{
+    m_verticalStart = start;
+    m_verticalEnd = end;
+    updateScrollbars();
+
+    emit verticalViewChanged(start, end);
+}
+
+
+void GraphWidget::verticalRangeChange(qint64 start, qint64 end)
+{
+    m_verticalMin = start;
+    m_verticalMax = end;
+    updateScrollbars();
+
+    emit verticalRangeChanged(start, end);
+}
+
+
+void GraphWidget::horizontalViewChange(qint64 start, qint64 end)
+{
+    m_horizontalStart = start;
+    m_horizontalEnd = end;
+    updateScrollbars();
+
+    emit horizontalViewChanged(start, end);
+}
+
+
+void GraphWidget::horizontalRangeChange(qint64 start, qint64 end)
+{
+    m_horizontalMin = start;
+    m_horizontalMax = end;
+    updateScrollbars();
+
+    emit horizontalRangeChanged(start, end);
+}
+
+
+/* User interaction with horizontal scroll bar */
+void GraphWidget::horizontalScrollAction(int /*action*/)
+{
+    int value = m_horizontalScrollbar->sliderPosition();
+    qint64 size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);
+
+    /* Calculate the new scroll values */
+    if (size <= INT_MAX) {
+        m_horizontalEnd -= m_horizontalStart;
+        m_horizontalStart = value + m_horizontalMin;
+        m_horizontalEnd += value;
+    } else {
+        /* QScrollBar only supports up to INT_MAX values, here we must scale
+         * our values to match this */
+        double dxdv = INT_MAX / (double)size;
+
+        size = m_horizontalEnd - m_horizontalStart;
+        m_horizontalStart = value / dxdv + m_horizontalMin;
+        m_horizontalEnd = m_horizontalStart + size;
+    }
+
+    /* Update view */
+    if (m_view) {
+        m_view->setHorizontalView(m_horizontalStart, m_horizontalEnd);
+    }
+
+    /* Update horizontal axes */
+    if (m_axisTop) {
+        m_axisTop->setView(m_horizontalStart, m_horizontalEnd);
+    }
+
+    if (m_axisBottom) {
+        m_axisBottom->setView(m_horizontalStart, m_horizontalEnd);
+    }
+
+    /* Inform the world of our changes! */
+    emit horizontalViewChanged(m_horizontalStart, m_horizontalEnd);
+}
+
+
+/* User interaction with vertical scroll bar */
+void GraphWidget::verticalScrollAction(int /*action*/)
+{
+    int value = m_verticalScrollbar->sliderPosition();
+    qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);
+
+    /* Calculate the new scroll values */
+    if (size <= INT_MAX) {
+        m_verticalEnd -= m_verticalStart;
+        m_verticalStart = value + m_verticalMin;
+        m_verticalEnd += value;
+    } else {
+        /* QScrollBar only supports up to INT_MAX values, here we must scale
+         * our values to match this */
+        double dxdv = INT_MAX / (double)size;
+
+        size = m_verticalEnd - m_verticalStart;
+        m_verticalStart = value / dxdv + m_verticalMin;
+        m_verticalEnd = m_verticalStart + size;
+    }
+
+    /* Update view */
+    if (m_view) {
+        m_view->setVerticalView(m_verticalStart, m_verticalEnd);
+    }
+
+    /* Update vertical axes */
+    if (m_axisLeft) {
+        m_axisLeft->setView(m_verticalStart, m_verticalEnd);
+    }
+
+    if (m_axisRight) {
+        m_axisRight->setView(m_verticalStart, m_verticalEnd);
+    }
+
+    /* Inform the world of our changes! */
+    emit verticalViewChanged(m_verticalStart, m_verticalEnd);
+}
+
+
+/* Update child elements when selection changes */
+void GraphWidget::updateSelection(bool emitSignal)
+{
+    if (m_view) {
+        m_view->update();
+    }
+
+    if (m_axisTop) {
+        m_axisTop->update();
+    }
+
+    if (m_axisLeft) {
+        m_axisLeft->update();
+    }
+
+    if (m_axisRight) {
+        m_axisRight->update();
+    }
+
+    if (m_axisBottom) {
+        m_axisBottom->update();
+    }
+
+    if (emitSignal) {
+        emit selectionChanged(transformSelectionOut(m_selection));
+    }
+}
+
+
+#include "graphwidget.moc"
diff --git a/gui/graphing/graphwidget.h b/gui/graphing/graphwidget.h
new file mode 100644 (file)
index 0000000..16b7786
--- /dev/null
@@ -0,0 +1,112 @@
+#ifndef GRAPHWIDGET_H
+#define GRAPHWIDGET_H
+
+#include "graphview.h"
+#include "graphaxiswidget.h"
+#include "graphlabelwidget.h"
+
+class QScrollBar;
+
+/**
+ * The generic GraphWidget class which combines the elements of a graph,
+ * the axis, view, scrollbars and label.
+ */
+class GraphWidget : public QWidget {
+    Q_OBJECT
+public:
+    enum AxisPosition {
+        AxisTop,
+        AxisLeft,
+        AxisRight,
+        AxisBottom
+    };
+
+public:
+    GraphWidget(QWidget* parent = 0);
+    virtual ~GraphWidget(){}
+
+    GraphView* view();
+    GraphLabelWidget* label();
+    GraphAxisWidget* axis(AxisPosition pos);
+
+    void setView(GraphView* view);
+    void setLabel(GraphLabelWidget* label);
+    void setAxis(AxisPosition pos, GraphAxisWidget* axis);
+
+    void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy);
+    void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy);
+
+    virtual void resizeEvent(QResizeEvent *e);
+
+protected:
+    /* Used if a selection would be shared between graphs with different axis */
+    virtual SelectionState transformSelectionIn(SelectionState state);
+    virtual SelectionState transformSelectionOut(SelectionState state);
+
+    /* Update the scrollbars based on current view */
+    void updateScrollbars();
+
+    /* Update all signal / slot connections */
+    void updateConnections();
+
+    /* Recalculate child widget layout */
+    void updateLayout();
+
+public slots:
+    void setSelection(SelectionState state);
+
+    /* Set view areas */
+    void setHorizontalView(qint64 start, qint64 end);
+    void setVerticalView(qint64 start, qint64 end);
+
+protected slots:
+    /* View changed by translation / zooming */
+    void verticalViewChange(qint64 start, qint64 end);
+    void verticalRangeChange(qint64 start, qint64 end);
+    void horizontalViewChange(qint64 start, qint64 end);
+    void horizontalRangeChange(qint64 start, qint64 end);
+
+    /* User interaction with scroll bars */
+    void horizontalScrollAction(int action);
+    void verticalScrollAction(int action);
+
+    /* Update child elements when selection changes */
+    void updateSelection(bool emitSignal = true);
+
+signals:
+    void selectionChanged(SelectionState state);
+
+    void verticalViewChanged(qint64 start, qint64 end);
+    void verticalRangeChanged(qint64 start, qint64 end);
+
+    void horizontalViewChanged(qint64 start, qint64 end);
+    void horizontalRangeChanged(qint64 start, qint64 end);
+
+protected:
+    SelectionState m_selection;
+
+    GraphView* m_view;
+
+    GraphLabelWidget* m_label;
+
+    GraphAxisWidget* m_axisTop;
+    GraphAxisWidget* m_axisLeft;
+    GraphAxisWidget* m_axisRight;
+    GraphAxisWidget* m_axisBottom;
+
+    QScrollBar* m_horizontalScrollbar;
+    qint64 m_horizontalMin;
+    qint64 m_horizontalMax;
+    qint64 m_horizontalStart;
+    qint64 m_horizontalEnd;
+    Qt::ScrollBarPolicy m_horizontalScrollbarPolicy;
+
+    QScrollBar* m_verticalScrollbar;
+    qint64 m_verticalMin;
+    qint64 m_verticalMax;
+    qint64 m_verticalStart;
+    qint64 m_verticalEnd;
+    Qt::ScrollBarPolicy m_verticalScrollbarPolicy;
+};
+
+#endif
diff --git a/gui/graphing/heatmapverticalaxiswidget.cpp b/gui/graphing/heatmapverticalaxiswidget.cpp
new file mode 100644 (file)
index 0000000..ae127c0
--- /dev/null
@@ -0,0 +1,82 @@
+#include "heatmapverticalaxiswidget.h"
+
+#include <qmath.h>
+#include <QPainter>
+#include <QMouseEvent>
+
+HeatmapVerticalAxisWidget::HeatmapVerticalAxisWidget(QWidget* parent) :
+    GraphAxisWidget(parent),
+    m_data(NULL)
+{
+    m_rowHeight = 20;
+}
+
+void HeatmapVerticalAxisWidget::setDataProvider(HeatmapDataProvider* data)
+{
+    delete m_data;
+    m_data = data;
+
+    m_valueMin = 0;
+    m_valueMax = (data->dataRows() + data->headerRows()) * m_rowHeight;
+
+    m_valueBegin = m_valueMin;
+    m_valueEnd = m_valueMax;
+
+    update();
+}
+
+void HeatmapVerticalAxisWidget::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (e->pos().y() < m_data->headerRows() * m_rowHeight) {
+        m_selectionState->type = SelectionState::None;
+        emit selectionChanged();
+    } else {
+        int row = e->pos().y();
+        row -= m_data->headerRows() * m_rowHeight;
+        row += m_valueBegin;
+        row /= m_rowHeight;
+
+        if (row >= m_data->dataRows()) {
+            m_selectionState->type = SelectionState::None;
+            emit selectionChanged();
+        } else {
+            m_selectionState->type = SelectionState::Vertical;
+            m_selectionState->start = m_data->dataRowAt(row);
+            m_selectionState->end = m_selectionState->start;
+            emit selectionChanged();
+        }
+    }
+}
+
+void HeatmapVerticalAxisWidget::paintEvent(QPaintEvent *)
+{
+    if (!m_data) {
+        return;
+    }
+
+    QPainter painter(this);
+    painter.setPen(Qt::black);
+    painter.setBrush(Qt::lightGray);
+    painter.drawRect(0, 0, width() - 1, height() - 1);
+
+    /* Draw scrollable rows */
+    painter.translate(0, m_data->headerRows() * m_rowHeight - m_valueBegin % m_rowHeight);
+    int rowStart = m_valueBegin / m_rowHeight;
+    int rowEnd = qMin<int>(qCeil(m_valueEnd / (double)m_rowHeight), m_data->dataRows());
+
+    for (unsigned i = rowStart; i < rowEnd; ++i) {
+        painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->dataLabel(i));
+        painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+        painter.translate(0, m_rowHeight);
+    }
+
+    /* Draw fixed position headers */
+    painter.resetTransform();
+    painter.drawRect(0, 0, width() - 1, m_data->headerRows() * m_rowHeight);
+
+    for (unsigned i = 0; i < m_data->headerRows(); ++i) {
+        painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->headerLabel(i));
+        painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+        painter.translate(0, m_rowHeight);
+    }
+}
diff --git a/gui/graphing/heatmapverticalaxiswidget.h b/gui/graphing/heatmapverticalaxiswidget.h
new file mode 100644 (file)
index 0000000..ca64adb
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef HEATMAPVERTICALAXISWIDGET_H
+#define HEATMAPVERTICALAXISWIDGET_H
+
+#include "heatmapview.h"
+#include "graphaxiswidget.h"
+
+/**
+ * Vertical axis specifically for heatmap displaying header and data rows
+ */
+class HeatmapVerticalAxisWidget : public GraphAxisWidget {
+public:
+    HeatmapVerticalAxisWidget(QWidget* parent);
+
+    void setDataProvider(HeatmapDataProvider* data);
+
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+    virtual void paintEvent(QPaintEvent *);
+
+protected:
+    int m_rowHeight;
+    HeatmapDataProvider* m_data;
+};
+
+#endif
diff --git a/gui/graphing/heatmapview.cpp b/gui/graphing/heatmapview.cpp
new file mode 100644 (file)
index 0000000..9bb485c
--- /dev/null
@@ -0,0 +1,276 @@
+#include "heatmapview.h"
+
+#include <qmath.h>
+#include <QToolTip>
+#include <QPainter>
+#include <QMouseEvent>
+
+HeatmapView::HeatmapView(QWidget* parent) :
+    GraphView(parent),
+    m_data(NULL)
+{
+    m_rowHeight = 20;
+    setMouseTracking(true);
+}
+
+
+void HeatmapView::setDataProvider(HeatmapDataProvider* data)
+{
+    delete m_data;
+    m_data = data;
+
+    if (m_data) {
+        m_data->setSelectionState(m_selectionState);
+
+        setDefaultView(m_data->start(), m_data->end());
+
+        m_viewWidthMin = 1000;
+
+        m_graphBottom = 0;
+        m_graphTop = (m_data->headerRows() + m_data->dataRows()) * m_rowHeight;
+    } else {
+        setDefaultView(0, 0);
+        m_graphBottom = m_graphTop = 0;
+    }
+
+    update();
+}
+
+
+void HeatmapView::setSelectionState(SelectionState* state)
+{
+    if (m_data) {
+        m_data->setSelectionState(state);
+    }
+
+    GraphView::setSelectionState(state);
+}
+
+
+void HeatmapView::mouseMoveEvent(QMouseEvent *e)
+{
+    GraphView::mouseMoveEvent(e);
+
+    if (e->buttons() || !m_data) {
+        QToolTip::hideText();
+        return;
+    }
+
+    qint64 index = itemAtPosition(e->pos());
+
+    if (index >= 0) {
+        QToolTip::showText(e->globalPos(), m_data->itemTooltip(index));
+    } else {
+        QToolTip::hideText();
+    }
+}
+
+
+void HeatmapView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (m_data && e->button() == Qt::LeftButton) {
+        qint64 index = itemAtPosition(e->pos());
+
+        if (index >= 0) {
+            m_data->itemDoubleClicked(index);
+            return;
+        }
+    }
+
+    GraphView::mouseDoubleClickEvent(e);
+}
+
+
+void HeatmapView::paintEvent(QPaintEvent *)
+{
+    if (!m_data) {
+        return;
+    }
+
+    QPainter painter(this);
+    painter.fillRect(0, m_data->headerRows() * m_rowHeight, width(), height(), Qt::white);
+
+    /* Draw data rows */
+    painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+    int rowStart = m_viewBottom / m_rowHeight;
+    int rowEnd = qMin<int>(qCeil(m_viewTop / (double)m_rowHeight), m_data->dataRows());
+
+    for (unsigned i = rowStart; i < rowEnd; ++i) {
+        HeatmapRowIterator* itr = m_data->dataRowIterator(i, m_viewLeft, m_viewRight, width());
+        paintRow(painter, itr);
+        painter.translate(0, m_rowHeight);
+        delete itr;
+    }
+
+    /* Draw Header */
+    painter.resetTransform();
+    painter.fillRect(0, 0, width(), m_data->headerRows() * m_rowHeight, Qt::white);
+
+    for (unsigned i = 0; i < m_data->headerRows(); ++i) {
+        HeatmapRowIterator* itr = m_data->headerRowIterator(i, m_viewLeft, m_viewRight, width());
+        paintRow(painter, itr);
+        painter.translate(0, m_rowHeight);
+        delete itr;
+    }
+
+    /* Draw Axis Lines */
+    painter.resetTransform();
+    painter.setPen(Qt::black);
+    painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+    painter.drawLine(0, m_rowHeight * 2, width(), m_rowHeight * 2);
+
+    painter.setPen(QColor(240, 240, 240));
+    painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+
+    for (unsigned i = rowStart; i < rowEnd; ++i) {
+        painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+        painter.translate(0, m_rowHeight);
+    }
+
+    /* Draw selection borders */
+    painter.resetTransform();
+    painter.setPen(Qt::green);
+
+    if (m_selectionState->type == SelectionState::Horizontal) {
+        double dxdt = width() / (double)m_viewWidth;
+        double scroll = m_viewLeft * dxdt;
+        double left = (m_selectionState->start * dxdt) - scroll;
+        double right = (m_selectionState->end * dxdt) - scroll;
+
+        /* Highlight time */
+        if (left >= 0 && left <= width()) {
+            painter.drawLine(left, 0, left, height());
+        }
+
+        if (right >= 0 && right <= width()) {
+            painter.drawLine(right, 0, right, height());
+        }
+    } else if (m_selectionState->type == SelectionState::Vertical) {
+        /* Highlight row */
+        int row = m_selectionState->start;
+
+        for (unsigned i = rowStart; i < rowEnd; ++i) {
+            if (m_data->dataRowAt(i) == row) {
+                row = i - rowStart;
+
+                painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+                painter.drawLine(0, (row + 1) * m_rowHeight, width(), (row + 1) * m_rowHeight);
+
+                if (row > 0) {
+                    painter.drawLine(0, row * m_rowHeight, width(), row * m_rowHeight);
+                }
+
+                break;
+            }
+        }
+    }
+}
+
+
+void HeatmapView::paintRow(QPainter& painter, HeatmapRowIterator* itr)
+{
+    bool selection = m_selectionState ? m_selectionState->type != SelectionState::None : false;
+
+    while (itr->next())
+    {
+        double heat = itr->heat();
+        int width = itr->width();
+        int x = itr->step();
+
+        /* Gamma correction */
+        heat = qPow(heat, 1.0 / 2.0);
+
+        if (width == 1) {
+            /* Draw single line */
+            if (selection) {
+                double selectedHeat = itr->selectedHeat();
+
+                if (selectedHeat >= 0.999) {
+                    heat = 255.0 - qBound(0.0, heat * 255.0, 255.0);
+
+                    if (itr->isGpu()) {
+                        painter.setPen(QColor(255, heat, heat));
+                    } else {
+                        painter.setPen(QColor(heat, heat, 255));
+                    }
+
+                    painter.drawLine(x, 0, x, m_rowHeight - 1);
+                } else {
+                    heat = 255.0 - qBound(0.0, heat * 100.0, 100.0);
+                    painter.setPen(QColor(heat, heat, heat));
+                    painter.drawLine(x, 0, x, m_rowHeight - 1);
+
+                    if (selectedHeat > 0.001) {
+                        selectedHeat = qPow(selectedHeat, 1.0 / 2.0);
+                        selectedHeat = qBound(0.0, selectedHeat * 255.0, 255.0);
+
+                        if (itr->isGpu()) {
+                            painter.setPen(QColor(255, 0, 0, selectedHeat));
+                        } else {
+                            painter.setPen(QColor(0, 0, 255, selectedHeat));
+                        }
+
+                        painter.drawLine(x, 0, x, m_rowHeight - 1);
+                    }
+                }
+            } else {
+                heat = qBound(0.0, heat * 255.0, 255.0);
+
+                if (itr->isGpu()) {
+                    painter.setPen(QColor(255, 255 - heat, 255 - heat));
+                } else {
+                    painter.setPen(QColor(255 - heat, 255 - heat, 255));
+                }
+
+                painter.drawLine(x, 0, x, m_rowHeight - 1);
+            }
+        } else {
+            double selectedHeat = itr->selectedHeat();
+
+            if (selection && selectedHeat < 0.9) {
+                painter.fillRect(x, 0, width, m_rowHeight, QColor(255 - 100, 255 - 100, 255 - 100));
+            } else if (itr->isGpu()) {
+                painter.fillRect(x, 0, width, m_rowHeight, QColor(255, 0, 0));
+            } else {
+                painter.fillRect(x, 0, width, m_rowHeight, QColor(0, 0, 255));
+            }
+
+            if (width > 6) {
+                painter.setPen(Qt::white);
+                QString elided = painter.fontMetrics().elidedText(itr->label(), Qt::ElideRight, width - 1);
+
+                painter.drawText(x + 1, 0, width - 1, m_rowHeight - 1,
+                                 Qt::AlignLeft | Qt::AlignVCenter,
+                                 elided);
+            }
+        }
+    }
+}
+
+
+qint64 HeatmapView::itemAtPosition(QPoint pos)
+{
+    if (!m_data) {
+        return -1;
+    }
+
+    double t = m_viewWidth / (double)width();
+    t *= pos.x();
+    t += m_viewLeft;
+
+    qint64 time = (qint64)t;
+    qint64 index;
+
+    if (pos.y() < m_data->headerRows() * m_rowHeight) {
+        int row = pos.y() / m_rowHeight;
+        index = m_data->headerItemAt(row, time);
+    } else {
+        int row = pos.y();
+        row -= m_data->headerRows() * m_rowHeight;
+        row += m_viewBottom;
+        row /= m_rowHeight;
+        index = m_data->dataItemAt(row, time);
+    }
+
+    return index;
+}
diff --git a/gui/graphing/heatmapview.h b/gui/graphing/heatmapview.h
new file mode 100644 (file)
index 0000000..781c9a7
--- /dev/null
@@ -0,0 +1,124 @@
+#ifndef HEATMAPVIEW_H
+#define HEATMAPVIEW_H
+
+#include "graphview.h"
+
+/**
+ * The heatmap iterator will only return data when there is something to draw,
+ * this allows much faster access to the data in the case where the view is
+ * zoomed out to the point of where there is multiple calls in one pixel,
+ * it automagically calculates the heat for that pixel.
+ */
+class HeatmapRowIterator {
+public:
+    virtual ~HeatmapRowIterator(){}
+
+    /* Go to the next visible heat map */
+    virtual bool next() = 0;
+
+    /* Is the current value GPU or CPU heat */
+    virtual bool isGpu() const = 0;
+
+    /* Current step (normally x coordinate) */
+    virtual int step() const = 0;
+
+    /* Current width (in steps) */
+    virtual int width() const = 0;
+
+    /* Current heat */
+    virtual float heat() const = 0;
+
+    /* Heat value for selected calls */
+    virtual float selectedHeat() const = 0;
+
+    /* Label only used when there is a single call in this heat */
+    virtual QString label() const = 0;
+};
+
+
+/**
+ * Data provider for the whole heatmap
+ */
+class HeatmapDataProvider {
+public:
+    virtual ~HeatmapDataProvider(){}
+
+    /* The start and end values (time on x-axis) for the heatmap */
+    virtual qint64 start() const = 0;
+    virtual qint64 end() const = 0;
+
+    /*
+     * Header rows (fixed at top of heatmap view)
+     */
+
+    /* Header row count */
+    virtual unsigned headerRows() const = 0;
+
+    /* Label to be used in the vertical axis */
+    virtual QString headerLabel(unsigned row) const = 0;
+
+    /* Get identifier (program no) for row */
+    virtual qint64 headerRowAt(unsigned row) const = 0;
+
+    /* Get item at row and time */
+    virtual qint64 headerItemAt(unsigned row, qint64 time) const = 0;
+
+    /* Get iterator for a row between start and end time for steps */
+    virtual HeatmapRowIterator* headerRowIterator(int row, qint64 start, qint64 end, int steps) const = 0;
+
+    /*
+     * Data rows (scrollable in heatmap view)
+     */
+
+    /* Data row count */
+    virtual unsigned dataRows() const = 0;
+
+    /* Label to be used in the vertical axis */
+    virtual QString dataLabel(unsigned row) const = 0;
+
+    /* Get identifier (program no) for row */
+    virtual qint64 dataRowAt(unsigned row) const = 0;
+
+    /* Get item at row and time */
+    virtual qint64 dataItemAt(unsigned row, qint64 time) const = 0;
+
+    /* Get iterator for a row between start and end time for steps */
+    virtual HeatmapRowIterator* dataRowIterator(int row, qint64 start, qint64 end, int steps) const = 0;
+
+    /* Handle double click on item */
+    virtual void itemDoubleClicked(qint64 index) const = 0;
+
+    /* Get mouse over tooltip for item */
+    virtual QString itemTooltip(qint64 index) const = 0;
+
+    /* Set the selection */
+    virtual void setSelectionState(SelectionState* state) = 0;
+};
+
+
+/**
+ * A not very generic heatmap for row based data
+ */
+class HeatmapView : public GraphView {
+public:
+    HeatmapView(QWidget* parent);
+
+    void setDataProvider(HeatmapDataProvider* data);
+    void setSelectionState(SelectionState* state);
+
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+    virtual void paintEvent(QPaintEvent *e);
+    virtual void paintRow(QPainter& painter, HeatmapRowIterator* itr);
+
+
+protected:
+    qint64 itemAtPosition(QPoint pos);
+
+protected:
+    int m_rowHeight;
+    HeatmapDataProvider* m_data;
+};
+
+#endif
diff --git a/gui/graphing/histogramview.cpp b/gui/graphing/histogramview.cpp
new file mode 100644 (file)
index 0000000..0b94577
--- /dev/null
@@ -0,0 +1,258 @@
+#include "histogramview.h"
+
+#include <QPen>
+#include <QBrush>
+#include <qmath.h>
+#include <QPainter>
+#include <QToolTip>
+#include <QMouseEvent>
+
+HistogramView::HistogramView(QWidget* parent) :
+    GraphView(parent),
+    m_data(NULL)
+{
+    setMouseTracking(true);
+
+    m_gradientUnselected.setColorAt(0.9, QColor(200, 200, 200));
+    m_gradientUnselected.setColorAt(0.0, QColor(220, 220, 220));
+
+    m_gradientSelected.setColorAt(0.9, QColor(0, 0, 210));
+    m_gradientSelected.setColorAt(0.0, QColor(130, 130, 255));
+}
+
+
+void HistogramView::setDataProvider(GraphDataProvider* data)
+{
+    delete m_data;
+    m_data = data;
+
+    if (m_data) {
+        m_data->setSelectionState(m_selectionState);
+        setDefaultView(0, m_data->size());
+        m_viewWidthMin = 10;
+    } else {
+        setDefaultView(0, 0);
+    }
+}
+
+
+void HistogramView::setSelectionState(SelectionState* state)
+{
+    if (m_data) {
+        m_data->setSelectionState(state);
+    }
+
+    GraphView::setSelectionState(state);
+}
+
+
+void HistogramView::setSelectedGradient(const QLinearGradient& gradient)
+{
+    m_gradientSelected = gradient;
+}
+
+
+void HistogramView::setUnelectedGradient(const QLinearGradient& gradient)
+{
+    m_gradientUnselected = gradient;
+}
+
+
+void HistogramView::mouseMoveEvent(QMouseEvent *e)
+{
+    GraphView::mouseMoveEvent(e);
+
+    if (e->buttons() || !m_data) {
+        QToolTip::hideText();
+        return;
+    }
+
+    qint64 index = itemAtPosition(e->pos());
+    qint64 time = valueAtPosition(e->pos());
+
+    if (m_data->value(index) >= time) {
+        QToolTip::showText(e->globalPos(), m_data->itemTooltip(index));
+    } else {
+        QToolTip::hideText();
+    }
+}
+
+
+void HistogramView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (e->button() == Qt::LeftButton) {
+        qint64 index = itemAtPosition(e->pos());
+        qint64 time = valueAtPosition(e->pos());
+
+        if (m_data->value(index) >= time) {
+            m_data->itemDoubleClicked(index);
+            return;
+        }
+    }
+
+    GraphView::mouseDoubleClickEvent(e);
+}
+
+
+void HistogramView::update()
+{
+    m_graphBottom = 0;
+    m_graphTop = 0;
+
+    if (m_data) {
+        for (qint64 i = m_viewLeft; i < m_viewRight; ++i) {
+            qint64 value = m_data->value(i);
+
+            if (value > m_graphTop) {
+                m_graphTop = value;
+            }
+        }
+    }
+
+    GraphView::update();
+}
+
+
+void HistogramView::resizeEvent(QResizeEvent *)
+{
+    m_gradientSelected.setStart(0, height());
+    m_gradientUnselected.setStart(0, height());
+}
+
+
+/* Draw the histogram
+ *
+ * When the view is zoomed such that there is more than one item occupying a single pixel
+ * the one with the highest value will be displayed.
+ */
+void HistogramView::paintEvent(QPaintEvent *)
+{
+    if (!m_data) {
+        return;
+    }
+
+    QBrush selectedBrush = QBrush(m_gradientSelected);
+    QPen selectedPen = QPen(selectedBrush, 1);
+
+    QBrush unselectedBrush = QBrush(m_gradientUnselected);
+    QPen unselectedPen = QPen(unselectedBrush, 1);
+
+    QPainter painter(this);
+    painter.fillRect(0, 0, width(), height(), Qt::white);
+
+    double dydv = height() / (double)m_graphTop;
+    double dxdv = width() / (double)(m_viewRight - m_viewLeft);
+    bool selection = m_selectionState && m_selectionState->type != SelectionState::None;
+
+    if (dxdv < 1.0) {
+        /* Less than one pixel per item */
+        qint64 longestValue = m_graphBottom;
+        qint64 longestSelected = m_graphBottom;
+        int lastX = 0;
+        double x = 0;
+
+        if (selection) {
+            painter.setPen(unselectedPen);
+        } else {
+            painter.setPen(selectedPen);
+        }
+
+        for (qint64 i = m_viewLeft; i < m_viewRight; ++i) {
+            qint64 value = m_data->value(i);
+            int ix;
+
+            if (value > longestValue) {
+                longestValue = value;
+            }
+
+            if (selection && m_data->selected(i) && value > longestSelected) {
+                longestSelected = value;
+            }
+
+            x += dxdv;
+            ix = (int)x;
+
+            if (lastX != ix) {
+                painter.drawLine(lastX, height(), lastX, height() - (longestValue * dydv));
+
+                if (selection && longestSelected > m_graphBottom) {
+                    painter.setPen(selectedPen);
+                    painter.drawLine(lastX, height(), lastX, height() - (longestSelected * dydv));
+                    painter.setPen(unselectedPen);
+                    longestSelected = m_graphBottom;
+                }
+
+                longestValue = m_graphBottom;
+                lastX = ix;
+            }
+        }
+    } else {
+        /* Draw rectangles for graph */
+        double x = 0;
+
+        for (qint64 i = m_viewLeft; i < m_viewRight; ++i, x += dxdv) {
+            qint64 value = m_data->value(i);
+            int y = qMax<int>(1, value * dydv);
+
+            if (!selection || m_data->selected(i)) {
+                painter.fillRect(x, height() - y, dxdv, y, selectedBrush);
+            } else {
+                painter.fillRect(x, height() - y, dxdv, y, unselectedBrush);
+            }
+        }
+    }
+
+    /* Draw the borders for the selection */
+    if (m_selectionState && m_selectionState->type == SelectionState::Horizontal) {
+        double dxdt = width() / (double)m_viewWidth;
+        double scroll = m_viewLeft * dxdt;
+        double left = (m_selectionState->start * dxdt) - scroll;
+        double right = (m_selectionState->end * dxdt) - scroll;
+
+        painter.setPen(Qt::green);
+
+        if (left >= 0 && left <= width()) {
+            painter.drawLine(left, 0, left, height());
+        }
+
+        if (right >= 0 && right <= width()) {
+            painter.drawLine(right, 0, right, height());
+        }
+    }
+}
+
+
+/* Find the item with the highest value at pos.x() +/- 1,
+ * the mouse must be within the bar height-wise.
+ */
+qint64 HistogramView::itemAtPosition(QPoint pos) {
+    double dvdx = m_viewWidth / (double)width();
+
+    qint64 left = qFloor(dvdx) * (pos.x() - 1) + m_viewLeft;
+    qint64 right = qCeil(dvdx) * (pos.x() + 1) + m_viewLeft;
+
+    qint64 longestIndex = 0;
+    qint64 longestValue = 0;
+
+    left = qBound<qint64>(0, left, m_data->size() - 1);
+    right = qBound<qint64>(0, right, m_data->size() - 1);
+
+    for (qint64 i = left; i <= right; ++i) {
+        if (m_data->value(i) > longestValue) {
+            longestValue = m_data->value(i);
+            longestIndex = i;
+        }
+    }
+
+    return longestIndex;
+}
+
+
+/* Return the value at position */
+qint64 HistogramView::valueAtPosition(QPoint pos) {
+    double value = m_graphTop / (double)height();
+    value *= height() - pos.y();
+    value += m_graphBottom;
+    return (qint64)value;
+}
+
diff --git a/gui/graphing/histogramview.h b/gui/graphing/histogramview.h
new file mode 100644 (file)
index 0000000..563d664
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef HISTOGRAMVIEW_H
+#define HISTOGRAMVIEW_H
+
+#include "graphview.h"
+
+/**
+ * Histogram graph view.
+ *
+ * When the view is zoomed such that there is more than one item occupying
+ * a single pixel the one with the highest value will be displayed.
+ */
+class HistogramView : public GraphView {
+public:
+    HistogramView(QWidget* parent);
+
+    void setDataProvider(GraphDataProvider* data);
+    void setSelectionState(SelectionState* state);
+
+    /* Gradient colours for selected and unselected items */
+    void setSelectedGradient(const QLinearGradient& gradient);
+    void setUnelectedGradient(const QLinearGradient& gradient);
+
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+    virtual void update();
+    virtual void resizeEvent(QResizeEvent *e);
+    virtual void paintEvent(QPaintEvent *e);
+
+protected:
+    qint64 itemAtPosition(QPoint pos);
+    qint64 valueAtPosition(QPoint pos);
+
+protected:
+    QLinearGradient m_gradientSelected;
+    QLinearGradient m_gradientUnselected;
+
+    GraphDataProvider* m_data;
+};
+
+#endif
diff --git a/gui/graphing/timeaxiswidget.cpp b/gui/graphing/timeaxiswidget.cpp
new file mode 100644 (file)
index 0000000..cebabf7
--- /dev/null
@@ -0,0 +1,80 @@
+#include "timeaxiswidget.h"
+#include "profiling.h"
+
+#include <qmath.h>
+#include <QPainter>
+
+TimeAxisWidget::TimeAxisWidget(QWidget* parent) :
+    GraphAxisWidget(parent)
+{
+}
+
+void TimeAxisWidget::paintEvent(QPaintEvent *)
+{
+    /* TODO: Support selections? */
+    /* TODO: Vertical scrolling? */
+
+    QPainter painter(this);
+    painter.setPen(Qt::black);
+    painter.setBrush(Qt::lightGray);
+    painter.drawRect(0, 0, width() - 1, height() - 1);
+
+    if (m_orientation == GraphAxisWidget::Vertical) {
+        int axisHeight = height() - 1;
+        int fontHeight = painter.fontMetrics().height();
+        int ticks     = axisHeight / (fontHeight * 2);
+
+        double range  = m_valueMax - m_valueMin;
+        double step   = range / (double)ticks;
+        double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
+        step = qFloor((step / step10) * 2) * (step10 / 2);
+
+        if (step <= 0) {
+            return;
+        }
+
+        painter.resetTransform();
+
+        for (double tick = 0; tick <= range; tick += step) {
+            int y = axisHeight - ((tick / range) * axisHeight);
+
+            painter.drawLine(width() - 8, y, width(), y);
+
+            painter.drawText(0,
+                             qBound(0, y - fontHeight / 2, axisHeight - fontHeight),
+                             width() - 10,
+                             fontHeight,
+                             Qt::AlignRight | Qt::AlignVCenter,
+                             Profiling::getTimeString(tick, m_valueMax));
+        }
+    } else {
+        int axisWidth = width() - 1;
+        int fontWidth = painter.fontMetrics().width("0.000 ns");
+        int fontHeight= painter.fontMetrics().height();
+        int ticks     = axisWidth / (fontWidth * 2);
+
+        double range  = m_valueMax - m_valueMin;
+        double step   = range / (double)ticks;
+        double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
+        step = qFloor((step / step10) * 2) * (step10 / 2);
+
+        if (step <= 0) {
+            return;
+        }
+
+        painter.resetTransform();
+
+        for (double tick = 0; tick <= range; tick += step) {
+            int x = (tick / range) * axisWidth;
+
+            painter.drawLine(x, 0, x, 8);
+
+            painter.drawText(qBound(0, x - fontWidth / 2, axisWidth - fontWidth),
+                             8,
+                             fontWidth,
+                             fontHeight,
+                             Qt::AlignHCenter | Qt::AlignTop,
+                             Profiling::getTimeString(tick, m_valueMax));
+        }
+    }
+}
diff --git a/gui/graphing/timeaxiswidget.h b/gui/graphing/timeaxiswidget.h
new file mode 100644 (file)
index 0000000..836651c
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef TIMEAXISWIDGET_H
+#define TIMEAXISWIDGET_H
+
+#include "graphaxiswidget.h"
+
+/**
+ * A simple axis that draws text using getTimeString to nicely format time values
+ */
+class TimeAxisWidget : public GraphAxisWidget {
+public:
+    TimeAxisWidget(QWidget* parent = 0);
+
+    virtual void paintEvent(QPaintEvent *e);
+};
+
+#endif
diff --git a/gui/graphwidget.cpp b/gui/graphwidget.cpp
deleted file mode 100644 (file)
index ea0f6a7..0000000
+++ /dev/null
@@ -1,666 +0,0 @@
-#include "graphwidget.h"
-#include "timelinewidget.h"
-#include "profiledialog.h"
-
-#include <qmath.h>
-#include <QLocale>
-#include <QPainter>
-#include <QToolTip>
-#include <QMouseEvent>
-#include <QApplication>
-
-typedef trace::Profile::Call Call;
-typedef trace::Profile::Frame Frame;
-typedef trace::Profile::Program Program;
-
-GraphWidget::GraphWidget(QWidget *parent)
-    : QWidget(parent),
-      m_profile(0),
-      m_axisWidth(80),
-      m_axisHeight(30),
-      m_axisForeground(Qt::black),
-      m_axisBackground(Qt::lightGray)
-{
-    m_selection.type = SelectNone;
-
-    m_graphGradientGpu.setColorAt(0.9, QColor(210, 0, 0));
-    m_graphGradientGpu.setColorAt(0.0, QColor(255, 130, 130));
-
-    m_graphGradientCpu.setColorAt(0.9, QColor(0, 0, 210));
-    m_graphGradientCpu.setColorAt(0.0, QColor(130, 130, 255));
-
-    m_graphGradientDeselected.setColorAt(0.9, QColor(200, 200, 200));
-    m_graphGradientDeselected.setColorAt(0.0, QColor(220, 220, 220));
-
-    setMouseTracking(true);
-    setAutoFillBackground(true);
-    setBackgroundRole(QPalette::Base);
-}
-
-
-/**
- * Setup graph data from profile results
- */
-void GraphWidget::setProfile(trace::Profile* profile, GraphType type)
-{
-    m_type = type;
-    m_profile = profile;
-    m_timeMax = 0;
-
-    /* Find longest call to use as y axis */
-    if (m_type == GraphGpu) {
-        for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
-            const Call& call = *itr;
-
-            if (call.gpuDuration > m_timeMax) {
-                m_timeMax = call.gpuDuration;
-            }
-        }
-    } else {
-        for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
-            const Call& call = *itr;
-
-            if (call.cpuDuration > m_timeMax) {
-                m_timeMax = call.cpuDuration;
-            }
-        }
-    }
-
-    m_callMin = 0;
-    m_callMax = m_profile->calls.size();
-
-    m_callWidthMin = 10;
-    m_callWidthMax = m_callMax - m_callMin;
-
-    m_call = m_callMin;
-    m_callWidth = m_callWidthMax;
-
-    selectNone();
-    update();
-}
-
-
-/**
- * Set selection to nothing
- */
-void GraphWidget::selectNone(bool notify)
-{
-    m_selection.type = SelectNone;
-
-    if (notify) {
-        emit selectedNone();
-    }
-
-    update();
-}
-
-
-/**
- * Set selection to a time period
- */
-void GraphWidget::selectTime(int64_t start, int64_t end, bool notify)
-{
-    m_selection.timeStart = start;
-    m_selection.timeEnd = end;
-    m_selection.type = (start == end) ? SelectNone : SelectTime;
-
-    if (notify) {
-        emit selectedTime(start, end);
-    }
-
-    update();
-}
-
-
-/**
- * Set selection to a program
- */
-void GraphWidget::selectProgram(unsigned program, bool notify)
-{
-    m_selection.program = program;
-    m_selection.type = SelectProgram;
-
-    if (notify) {
-        emit selectedProgram(program);
-    }
-
-    update();
-}
-
-
-/**
- * Slot to synchronise with other graph views
- */
-void GraphWidget::changeView(int call, int width)
-{
-    m_call = call;
-    m_callWidth = width;
-    update();
-}
-
-
-/**
- * Calculate the maxTime variable when model is updated
- */
-void GraphWidget::update()
-{
-    m_timeMax = 0;
-
-    if (m_type == GraphGpu) {
-        for (int i = m_call; i < m_call + m_callWidth; ++i) {
-            const Call& call =  m_profile->calls[i];
-
-            if (call.gpuDuration > m_timeMax) {
-                m_timeMax = call.gpuDuration;
-            }
-        }
-    } else {
-        for (int i = m_call; i < m_call + m_callWidth; ++i) {
-            const Call& call =  m_profile->calls[i];
-
-            if (call.cpuDuration > m_timeMax) {
-                m_timeMax = call.cpuDuration;
-            }
-        }
-    }
-
-    QWidget::update();
-}
-
-
-/**
- * Find the call at (x, y) position
- */
-const Call* GraphWidget::callAtPosition(const QPoint& pos)
-{
-    int left, right, size;
-    int64_t time;
-
-    if (!m_profile) {
-        return NULL;
-    }
-
-    int posX = qMax(0, pos.x() - m_axisWidth);
-    int posY = qMax(0, pos.y() - m_axisHeight);
-
-    time  = ((m_graphHeight - posY) / (double)m_graphHeight) * m_timeMax;
-    time -= (2 * m_timeMax) / m_graphHeight;
-
-    size  = m_callWidth / (double)m_graphWidth;
-
-    left  = m_call + (posX / (double)m_graphWidth) * m_callWidth;
-    left  = qMax(m_callMin, left - size);
-
-    right = qMin(m_callMax - 1, left + size * 2);
-
-    if (m_type == GraphGpu) {
-        const Call* longest = NULL;
-
-        for (int i = left; i <= right; ++i) {
-            const Call& call = m_profile->calls[i];
-
-            if (call.pixels >= 0) {
-                if (!longest || call.gpuDuration > longest->gpuDuration) {
-                    longest = &call;
-                }
-            }
-        }
-
-        if (longest && time < longest->gpuDuration) {
-            return longest;
-        }
-    } else {
-        const Call* longest = NULL;
-
-        for (int i = left; i <= right; ++i) {
-            const Call& call = m_profile->calls[i];
-
-            if (!longest || call.cpuDuration > longest->cpuDuration) {
-                longest = &call;
-            }
-        }
-
-        if (longest && time < longest->cpuDuration) {
-            return longest;
-        }
-    }
-
-    return NULL;
-}
-
-
-void GraphWidget::mousePressEvent(QMouseEvent *e)
-{
-    if (e->button() == Qt::LeftButton) {
-        m_mousePressPosition = e->pos();
-        m_mousePressCall = m_call;
-    }
-}
-
-
-void GraphWidget::mouseReleaseEvent(QMouseEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    if (e->button() == Qt::LeftButton) {
-        int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
-
-        if (dxy <= 2) {
-            int x = qMax(m_axisWidth, e->pos().x());
-            double dcdx = m_callWidth / (double)m_graphWidth;
-
-            int call = m_mousePressCall + dcdx * (x - m_axisWidth);
-
-            int64_t start = m_profile->calls[call].cpuStart;
-            int64_t end = m_profile->calls[call].cpuStart + m_profile->calls[call].cpuDuration;
-
-            if (start < m_selection.timeStart || end > m_selection.timeEnd) {
-                selectNone(true);
-            }
-        }
-    }
-}
-
-
-void GraphWidget::mouseDoubleClickEvent(QMouseEvent *e)
-{
-    const Call* call = callAtPosition(e->pos());
-
-    if (call) {
-        emit jumpToCall(call->no);
-    }
-}
-
-
-void GraphWidget::mouseMoveEvent(QMouseEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    if (e->buttons().testFlag(Qt::LeftButton)) {
-        double dcdx = m_callWidth / (double)m_graphWidth;
-
-        if (m_mousePressPosition.y() > m_axisHeight) {
-            dcdx *= m_mousePressPosition.x() - e->pos().x();
-
-            /* Horizontal scroll */
-            m_call = m_mousePressCall + dcdx;
-            m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
-
-            emit viewChanged(m_call, m_callWidth);
-            update();
-        } else {
-            int x = qMax(m_axisWidth, e->pos().x());
-
-            int down = m_mousePressCall + dcdx * (m_mousePressPosition.x() - m_axisWidth);
-            int up = m_mousePressCall + dcdx * (x - m_axisWidth);
-
-            int left = qMax(qMin(down, up), 0);
-            int right = qMin<int>(qMax(down, up), m_profile->calls.size() - 1);
-
-            selectTime(m_profile->calls[left].cpuStart, m_profile->calls[right].cpuStart + m_profile->calls[right].cpuDuration, true);
-        }
-    }
-
-    const Call* call = callAtPosition(e->pos());
-
-    if (e->button() == Qt::NoButton && call) {
-        QString text;
-        text  = QString::fromStdString(call->name);
-        text += QString("\nCall: %1").arg(call->no);
-        text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
-
-        if (call->pixels >= 0) {
-            text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
-            text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
-            text += QString("\nProgram: %1").arg(call->program);
-        }
-
-        QToolTip::showText(e->globalPos(), text);
-    } else {
-        QToolTip::hideText();
-    }
-}
-
-
-void GraphWidget::wheelEvent(QWheelEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    if (e->pos().x() < m_axisWidth) {
-        return;
-    }
-
-    int zoomPercent = 10;
-
-    /* If holding Ctrl key then zoom 2x faster */
-    if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
-        zoomPercent = 20;
-    }
-
-    /* Zoom view by adjusting width */
-    double dt = m_callWidth;
-    double size = dt * -e->delta();
-
-    /* Zoom deltas normally come in increments of 120 */
-    size /= 120 * (100 / zoomPercent);
-
-    m_callWidth += size;
-    m_callWidth = qBound(m_callWidthMin, m_callWidth, m_callWidthMax);
-
-    /* Scroll view to zoom around mouse */
-    dt -= m_callWidth;
-    dt *= e->x() - m_axisWidth;
-    dt /= m_graphWidth;
-
-    m_call = dt + m_call;
-    m_call = qBound(m_callMin, m_call, m_callMax - m_callWidth);
-
-    emit viewChanged(m_call, m_callWidth);
-    update();
-}
-
-
-void GraphWidget::resizeEvent(QResizeEvent *e)
-{
-    m_graphWidth = qMax(0, width() - m_axisWidth);
-    m_graphHeight = qMax(0, height() - m_axisHeight);
-
-    m_graphGradientGpu.setStart(0, m_graphHeight);
-    m_graphGradientCpu.setStart(0, m_graphHeight);
-    m_graphGradientDeselected.setStart(0, m_graphHeight);
-}
-
-
-/**
- * Draw the vertical axis of time
- */
-void GraphWidget::paintVerticalAxis(QPainter& painter)
-{
-    int height = painter.fontMetrics().height();
-    int ticks  = m_graphHeight / (height * 2);
-
-    double step   = m_timeMax / (double)ticks;
-    double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
-    step = qFloor((step / step10) * 2) * (step10 / 2);
-
-    if (step <= 0) {
-        return;
-    }
-
-    painter.resetTransform();
-    painter.translate(0, m_axisHeight);
-    painter.setPen(m_axisForeground);
-
-    for (double tick = 0; tick <= m_timeMax; tick += step) {
-        int y = m_graphHeight - ((tick / m_timeMax) * m_graphHeight);
-
-        painter.drawLine(m_axisWidth - 8, y, m_axisWidth - 1, y);
-
-        painter.drawText(0,
-                         qBound(0, y - height / 2, m_graphHeight - height),
-                         m_axisWidth - 10,
-                         height,
-                         Qt::AlignRight | Qt::AlignVCenter,
-                         getTimeString(tick, m_timeMax));
-    }
-}
-
-
-/**
- * Draw horizontal axis of frame numbers
- */
-void GraphWidget::paintHorizontalAxis(QPainter& painter)
-{
-    double dxdc = m_graphWidth / (double)m_callWidth;
-    double scroll = dxdc * m_call;
-    int lastLabel = -9999;
-
-    painter.resetTransform();
-    painter.fillRect(0, 0, width(), m_axisHeight, m_axisBackground);
-    painter.fillRect(0, 0, m_axisWidth, height(), m_axisBackground);
-
-    painter.setPen(m_axisForeground);
-    painter.drawLine(0, m_axisHeight - 1, width(), m_axisHeight - 1);
-    painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, height());
-
-    painter.translate(m_axisWidth, 0);
-
-    for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
-        static const int padding = 4;
-        const Frame& frame = *itr;
-        bool draw = true;
-        int width;
-
-        if (frame.calls.begin > m_call + m_callWidth) {
-            break;
-        }
-
-        if (frame.calls.end < m_call) {
-            draw = false;
-        }
-
-        double left = dxdc * frame.calls.begin;
-        double right = dxdc * frame.calls.end;
-        QString text = QString("%1").arg(frame.no);
-
-        width = painter.fontMetrics().width(text) + padding * 2;
-
-        if (left + width > scroll)
-            draw = true;
-
-        /* Draw a frame number if we have space since the last one */
-        if (left - lastLabel > width) {
-            lastLabel = left + width;
-
-            if (draw) {
-                int textX;
-
-                if (left < scroll && right - left > width) {
-                    if (right - scroll > width) {
-                        textX = 0;
-                    } else {
-                        textX = right - scroll - width;
-                    }
-                } else {
-                    textX = left - scroll;
-                }
-
-                /* Draw frame number and major ruler marking */
-                painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
-                painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
-            }
-        } else if (draw) {
-            /* Draw a minor ruler marking */
-            painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
-        }
-    }
-}
-
-
-void GraphWidget::paintEvent(QPaintEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    QPainter painter(this);
-    QBrush deselectBrush;
-    QPen deselectPen;
-    QBrush brush;
-    QPen pen;
-
-    /* Draw axes */
-    paintHorizontalAxis(painter);
-    paintVerticalAxis(painter);
-
-    /* Draw the label */
-    painter.resetTransform();
-    painter.fillRect(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::lightGray);
-
-    if (m_type == GraphGpu) {
-        painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
-    } else {
-        painter.drawText(0, 0, m_axisWidth, m_axisHeight, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
-    }
-
-    /* Draw graph */
-    deselectBrush = QBrush(m_graphGradientDeselected);
-
-    if (m_type == GraphGpu) {
-        brush = QBrush(m_graphGradientGpu);
-    } else {
-        brush = QBrush(m_graphGradientCpu);
-    }
-
-    pen = QPen(brush, 1);
-    deselectPen = QPen(deselectBrush, 1);
-
-    painter.setBrush(brush);
-    painter.setPen(pen);
-    painter.translate(m_axisWidth, m_axisHeight);
-
-    double x = 0;
-    double dydt = m_graphHeight / (double)m_timeMax;
-    double dxdc = m_graphWidth / (double)m_callWidth;
-
-    int selectLeft = m_call + m_callWidth;
-    int selectRight = -1;
-
-    if (m_selection.type == SelectProgram) {
-        painter.setPen(deselectPen);
-    }
-
-    if (dxdc < 1.0) {
-        /* Draw the longest call in a pixel */
-        int64_t selectedLongest = 0;
-        int64_t pixelLongest = 0;
-        int lastX = 0;
-
-        for (int i = m_call; i < m_call + m_callWidth; ++i) {
-            const Call& call =  m_profile->calls[i];
-            int ix;
-
-            if (m_type == GraphGpu) {
-                if (call.gpuDuration > pixelLongest) {
-                    pixelLongest = call.gpuDuration;
-                }
-
-                if (m_selection.type == SelectProgram && call.program == m_selection.program) {
-                    if (call.gpuDuration > selectedLongest) {
-                        selectedLongest = call.gpuDuration;
-                    }
-                }
-            } else {
-                if (call.cpuDuration > pixelLongest) {
-                    pixelLongest = call.cpuDuration;
-                }
-
-                if (m_selection.type == SelectProgram && call.program == m_selection.program) {
-                    if (call.cpuDuration > selectedLongest) {
-                        selectedLongest = call.cpuDuration;
-                    }
-                }
-            }
-
-            x += dxdc;
-            ix = (int)x;
-
-            if (lastX != ix) {
-                if (m_selection.type == SelectTime) {
-                    if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) {
-                        painter.setPen(deselectPen);
-                    } else {
-                        if (ix < selectLeft) {
-                            selectLeft = ix;
-                        }
-
-                        if (ix > selectRight) {
-                            selectRight = ix;
-                        }
-
-                        painter.setPen(pen);
-                    }
-                }
-
-                painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (pixelLongest * dydt));
-                pixelLongest = 0;
-
-                if (selectedLongest > 0) {
-                    painter.setPen(pen);
-                    painter.drawLine(lastX, m_graphHeight, lastX, m_graphHeight - (selectedLongest * dydt));
-                    painter.setPen(deselectPen);
-                    selectedLongest = 0;
-                }
-
-                lastX = ix;
-            }
-        }
-    } else {
-        /* Draw rectangles for graph */
-        for (int i = m_call; i < m_call + m_callWidth; ++i, x += dxdc) {
-            const Call& call =  m_profile->calls[i];
-            int y;
-
-            if (m_type == GraphGpu) {
-                y = qMax<int>(1, call.gpuDuration * dydt);
-            } else {
-                y = qMax<int>(1, call.cpuDuration * dydt);
-            }
-
-            if (m_selection.type == SelectTime) {
-                if (call.cpuStart < m_selection.timeStart || call.cpuStart > m_selection.timeEnd) {
-                    if (m_type == GraphCpu || call.pixels >= 0) {
-                        painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush);
-                    }
-
-                    continue;
-                } else {
-                    if (x < selectLeft) {
-                        selectLeft = x;
-                    }
-
-                    if (x + dxdc > selectRight) {
-                        selectRight = x + dxdc;
-                    }
-                }
-            }
-
-            if (m_type == GraphCpu || call.pixels >= 0) {
-                if (m_selection.type == SelectProgram && call.program != m_selection.program) {
-                    painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), deselectBrush);
-                } else {
-                    painter.fillRect(QRectF(x, m_graphHeight - y, dxdc, y), brush);
-                }
-            }
-        }
-    }
-
-    /* Draw the selection borders */
-    if (m_selection.type == SelectTime && selectLeft < selectRight) {
-        selectLeft += m_axisWidth;
-        selectRight += m_axisWidth;
-
-        painter.resetTransform();
-        painter.setPen(Qt::green);
-
-        if (m_profile->calls[m_call].cpuStart <= m_selection.timeStart) {
-            painter.drawLine(selectLeft, 0, selectLeft, height());
-        }
-
-        if (m_profile->calls[m_call + m_callWidth - 1].cpuStart >= m_selection.timeEnd) {
-            painter.drawLine(selectRight, 0, selectRight, height());
-        }
-
-        selectLeft = qBound(m_axisWidth, selectLeft, width());
-        selectRight = qBound(m_axisWidth, selectRight, width());
-        painter.drawLine(selectLeft, m_axisHeight - 1, selectRight, m_axisHeight - 1);
-    }
-}
-
-#include "graphwidget.moc"
diff --git a/gui/graphwidget.h b/gui/graphwidget.h
deleted file mode 100644 (file)
index 7ab17c6..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-#ifndef GRAPHWIDGET_H
-#define GRAPHWIDGET_H
-
-#include <QPen>
-#include <QWidget>
-#include <QPainter>
-#include <QLinearGradient>
-#include "trace_profiler.hpp"
-
-enum GraphType {
-    GraphGpu,
-    GraphCpu
-};
-
-enum SelectType {
-    SelectNone,
-    SelectTime,
-    SelectProgram
-};
-
-class GraphWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    GraphWidget(QWidget *parent = 0);
-
-    void setProfile(trace::Profile* profile, GraphType type);
-    const trace::Profile::Call* callAtPosition(const QPoint& pos);
-
-    void selectNone(bool notify = false);
-    void selectTime(int64_t start, int64_t end, bool notify = false);
-    void selectProgram(unsigned program, bool notify = false);
-
-protected:
-    virtual void paintEvent(QPaintEvent *e);
-    virtual void resizeEvent(QResizeEvent *e);
-
-    virtual void wheelEvent(QWheelEvent *e);
-    virtual void mouseMoveEvent(QMouseEvent *e);
-    virtual void mousePressEvent(QMouseEvent *e);
-    virtual void mouseReleaseEvent(QMouseEvent *e);
-    virtual void mouseDoubleClickEvent(QMouseEvent *e);
-
-signals:
-    void jumpToCall(int no);
-    void viewChanged(int call, int width);
-
-    void selectedNone();
-    void selectedProgram(unsigned program);
-    void selectedTime(int64_t start, int64_t end);
-
-public slots:
-    void changeView(int call, int width);
-
-private:
-    void update();
-
-    void paintVerticalAxis(QPainter& painter);
-    void paintHorizontalAxis(QPainter& painter);
-
-private:
-    /* Data */
-    trace::Profile* m_profile;
-    GraphType m_type;
-
-    /* Vertical Axis */
-    int64_t m_timeMax;
-
-    /* Horizontal axis */
-    int m_call;
-    int m_callMin;
-    int m_callMax;
-    int m_callWidth;
-    int m_callWidthMin;
-    int m_callWidthMax;
-
-    /* Viewport */
-    int m_graphWidth;
-    int m_graphHeight;
-
-    /* Mouse track data */
-    int m_mousePressCall;
-    QPoint m_mousePressPosition;
-
-    /* Style */
-    int m_axisWidth;
-    int m_axisHeight;
-    QPen m_axisForeground;
-    QBrush m_axisBackground;
-    QLinearGradient m_graphGradientGpu;
-    QLinearGradient m_graphGradientCpu;
-    QLinearGradient m_graphGradientDeselected;
-
-    struct {
-        SelectType type;
-
-        unsigned program;
-
-        int64_t timeStart;
-        int64_t timeEnd;
-    } m_selection;
-};
-
-#endif // GRAPHWIDGET_H
index f33020386796def64e5d8f3c71e4eacd347c9568..a9892cf07a0f74d82391f671b5410b86eb49bf42 100644 (file)
 #include "profiletablemodel.h"
 #include <QSortFilterProxyModel>
 
-ProfileDialog::ProfileDialog(QWidget *parent)
-    : QDialog(parent),
-      m_profile(0)
-{
-    setupUi(this);
-
-    connect(m_timeline, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int)));
-    connect(m_gpuGraph, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int)));
-    connect(m_cpuGraph, SIGNAL(jumpToCall(int)), SIGNAL(jumpToCall(int)));
-}
+#include "graphing/histogramview.h"
+#include "graphing/timeaxiswidget.h"
+#include "graphing/frameaxiswidget.h"
+#include "graphing/heatmapverticalaxiswidget.h"
+#include "profileheatmap.h"
 
+/* Handy function to allow selection of a call in main window */
+ProfileDialog* g_profileDialog = 0;
 
-ProfileDialog::~ProfileDialog()
+void Profiling::jumpToCall(int index)
 {
-    delete m_profile;
+    if (g_profileDialog) {
+        g_profileDialog->showCall(index);
+    }
 }
 
+/* Provides frame numbers based off call index */
+class FrameCallDataProvider : public FrameDataProvider {
+public:
+    FrameCallDataProvider(const trace::Profile* profile)
+    {
+        m_profile = profile;
+    }
 
-void ProfileDialog::tableDoubleClicked(const QModelIndex& index)
-{
-    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
-    const trace::Profile::Call* call = model->getJumpCall(index);
+    unsigned size() const {
+        return m_profile->frames.size();
+    }
 
-    if (call) {
-        emit jumpToCall(call->no);
-    } else {
-        unsigned program = model->getProgram(index);
-        m_timeline->selectProgram(program);
-        m_cpuGraph->selectProgram(program);
-        m_gpuGraph->selectProgram(program);
+    qint64 frameStart(unsigned index) const {
+        return m_profile->frames[index].calls.begin;
     }
-}
 
+    qint64 frameEnd(unsigned index) const {
+        return m_profile->frames[index].calls.end;
+    }
 
-void ProfileDialog::setProfile(trace::Profile* profile)
-{
-    delete m_profile;
+private:
+    const trace::Profile* m_profile;
+};
 
-    if (profile->frames.size() == 0) {
-        m_profile = NULL;
-    } else {
+/* Provides frame numbers based off time */
+class FrameTimeDataProvider : public FrameDataProvider {
+public:
+    FrameTimeDataProvider(const trace::Profile* profile)
+    {
         m_profile = profile;
-        m_timeline->setProfile(m_profile);
-        m_gpuGraph->setProfile(m_profile, GraphGpu);
-        m_cpuGraph->setProfile(m_profile, GraphCpu);
+    }
 
-        ProfileTableModel* model = new ProfileTableModel(m_table);
-        model->setProfile(m_profile);
+    unsigned size() const {
+        return m_profile->frames.size();
+    }
 
-        delete m_table->model();
-        m_table->setModel(model);
-        m_table->update(QModelIndex());
-        m_table->sortByColumn(1, Qt::DescendingOrder);
-        m_table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
-        m_table->resizeColumnsToContents();
+    qint64 frameStart(unsigned index) const {
+        return m_profile->frames[index].cpuStart;
     }
-}
 
+    qint64 frameEnd(unsigned index) const {
+        return m_profile->frames[index].cpuStart + m_profile->frames[index].cpuDuration;
+    }
+
+private:
+    const trace::Profile* m_profile;
+};
 
-void ProfileDialog::selectNone()
+ProfileDialog::ProfileDialog(QWidget *parent)
+    : QDialog(parent),
+      m_profile(0)
 {
-    QObject* src = QObject::sender();
+    setupUi(this);
+    g_profileDialog = this;
 
-    /* Update table model */
-    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
-    model->selectNone();
-    m_table->reset();
+    /* Gradients for call duration histograms */
+    QLinearGradient cpuGradient;
+    cpuGradient.setColorAt(0.9, QColor(0, 0, 210));
+    cpuGradient.setColorAt(0.0, QColor(130, 130, 255));
 
-    /* Update graphs */
-    if (src != m_gpuGraph) {
-        m_gpuGraph->selectNone();
-    }
+    QLinearGradient gpuGradient;
+    gpuGradient.setColorAt(0.9, QColor(210, 0, 0));
+    gpuGradient.setColorAt(0.0, QColor(255, 130, 130));
 
-    if (src != m_cpuGraph) {
-        m_cpuGraph->selectNone();
-    }
 
-    /* Update timeline */
-    if (src != m_timeline) {
-        m_timeline->selectNone();
-    }
-}
+    /* Setup heatmap timeline */
+    m_timeline->setLabel(new GraphLabelWidget("Frames ", m_timeline));
+    m_timeline->label()->setFlags(Qt::AlignVCenter | Qt::AlignRight);
 
+    m_timeline->setView(new HeatmapView(m_timeline));
 
-void ProfileDialog::selectProgram(unsigned program)
-{
-    QObject* src = QObject::sender();
+    m_timeline->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_timeline));
+    m_timeline->setAxis(GraphWidget::AxisLeft, new HeatmapVerticalAxisWidget(m_timeline));
+    m_timeline->axis(GraphWidget::AxisLeft)->resize(80, 0);
 
-    /* Update table model */
-    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
-    model->selectNone();
-    m_table->reset();
-    m_table->selectRow(model->getRowIndex(program));
+    m_timeline->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+    m_timeline->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 
-    /* Update graphs */
-    if (src != m_gpuGraph) {
-        m_gpuGraph->selectProgram(program);
-    }
 
-    if (src != m_cpuGraph) {
-        m_cpuGraph->selectProgram(program);
-    }
+    /* Setup Cpu call duration histogram */
+    m_cpuGraph->setLabel(new GraphLabelWidget("CPU", m_cpuGraph));
 
-    /* Update timeline */
-    if (src != m_timeline) {
-        m_timeline->selectProgram(program);
-    }
+    m_cpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_cpuGraph));
+    m_cpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_cpuGraph));
+    m_cpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0);
+
+    HistogramView* cpuView = new HistogramView(m_cpuGraph);
+    cpuView->setSelectedGradient(cpuGradient);
+    m_cpuGraph->setView(cpuView);
+
+
+    /* Setup Gpu call duration histogram */
+    m_gpuGraph->setLabel(new GraphLabelWidget("GPU", m_gpuGraph));
+
+    m_gpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_gpuGraph));
+    m_gpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_gpuGraph));
+    m_gpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0);
+
+    HistogramView* gpuView = new HistogramView(m_gpuGraph);
+    gpuView->setSelectedGradient(gpuGradient);
+    m_gpuGraph->setView(gpuView);
+
+
+    /* Synchronise selections */
+    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState)));
+    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState)));
+
+    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState)));
+    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState)));
+
+    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState)));
+    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState)));
+
+    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));
+    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));
+    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));
+
+
+    /* Synchronise views between cpuGraph and gpuGraph */
+    connect(m_cpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_gpuGraph, SLOT(setHorizontalView(qint64,qint64)));
+    connect(m_gpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_cpuGraph, SLOT(setHorizontalView(qint64,qint64)));
 }
 
 
-void ProfileDialog::selectTime(int64_t start, int64_t end)
+ProfileDialog::~ProfileDialog()
 {
-    QObject* src = QObject::sender();
+    delete m_profile;
+}
 
-    /* Update table model */
-    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
-    model->selectTime(start, end);
-    m_table->reset();
 
-    /* Update graphs */
-    if (src != m_gpuGraph) {
-        m_gpuGraph->selectTime(start, end);
-    }
+void ProfileDialog::showCall(int call)
+{
+    emit jumpToCall(call);
+}
 
-    if (src != m_cpuGraph) {
-        m_cpuGraph->selectTime(start, end);
-    }
 
-    /* Update timeline */
-    if (src != m_timeline) {
-        m_timeline->selectTime(start, end);
+void ProfileDialog::tableDoubleClicked(const QModelIndex& index)
+{
+    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
+
+    if (!model) {
+        return;
     }
-}
 
+    const trace::Profile::Call* call = model->getJumpCall(index);
 
-void ProfileDialog::setVerticalScrollMax(int max)
-{
-    if (max <= 0) {
-        m_verticalScrollBar->hide();
+    if (call) {
+        emit jumpToCall(call->no);
     } else {
-        m_verticalScrollBar->show();
-        m_verticalScrollBar->setMinimum(0);
-        m_verticalScrollBar->setMaximum(max);
+        unsigned program = model->getProgram(index);
+
+        SelectionState state;
+        state.type = SelectionState::Vertical;
+        state.end = state.start = program;
+
+        m_timeline->setSelection(state);
+        m_cpuGraph->setSelection(state);
+        m_gpuGraph->setSelection(state);
     }
 }
 
 
-void ProfileDialog::setHorizontalScrollMax(int max)
+void ProfileDialog::setProfile(trace::Profile* profile)
 {
-    if (max <= 0) {
-        m_horizontalScrollBar->hide();
-    } else {
-        m_horizontalScrollBar->show();
-        m_horizontalScrollBar->setMinimum(0);
-        m_horizontalScrollBar->setMaximum(max);
+
+    if (profile && profile->frames.size()) {
+        HeatmapVerticalAxisWidget* programAxis;
+        FrameAxisWidget* frameAxis;
+        HistogramView* histogram;
+        HeatmapView* heatmap;
+
+
+        /* Setup data providers for Cpu graph */
+        m_cpuGraph->setProfile(profile);
+        histogram = (HistogramView*)m_cpuGraph->view();
+        frameAxis = (FrameAxisWidget*)m_cpuGraph->axis(GraphWidget::AxisTop);
+
+        histogram->setDataProvider(new CallDurationDataProvider(profile, false));
+        frameAxis->setDataProvider(new FrameCallDataProvider(profile));
+
+        /* Setup data provider for Gpu graph */
+        m_gpuGraph->setProfile(profile);
+        histogram = (HistogramView*)m_gpuGraph->view();
+        frameAxis = (FrameAxisWidget*)m_gpuGraph->axis(GraphWidget::AxisTop);
+
+        histogram->setDataProvider(new CallDurationDataProvider(profile, true));
+        frameAxis->setDataProvider(new FrameCallDataProvider(profile));
+
+        /* Setup data provider for heatmap timeline */
+        heatmap = (HeatmapView*)m_timeline->view();
+        frameAxis = (FrameAxisWidget*)m_timeline->axis(GraphWidget::AxisTop);
+        programAxis = (HeatmapVerticalAxisWidget*)m_timeline->axis(GraphWidget::AxisLeft);
+
+        heatmap->setDataProvider(new ProfileHeatmapDataProvider(profile));
+        frameAxis->setDataProvider(new FrameTimeDataProvider(profile));
+        programAxis->setDataProvider(new ProfileHeatmapDataProvider(profile));
+
+        /* Setup data model for table view */
+        ProfileTableModel* model = new ProfileTableModel(m_table);
+        model->setProfile(profile);
+
+        delete m_table->model();
+        m_table->setModel(model);
+        m_table->update(QModelIndex());
+        m_table->sortByColumn(2, Qt::DescendingOrder);
+        m_table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+        m_table->resizeColumnsToContents();
+
+        /* Reset selection */
+        SelectionState emptySelection;
+        emptySelection.type = SelectionState::None;
+        m_cpuGraph->setSelection(emptySelection);
+        m_gpuGraph->setSelection(emptySelection);
+        m_timeline->setSelection(emptySelection);
     }
+
+    delete m_profile;
+    m_profile = profile;
 }
 
 
-/**
- * Convert a CPU / GPU time to a textual representation.
- * This includes automatic unit selection.
- */
-QString getTimeString(int64_t time, int64_t unitTime)
+void ProfileDialog::graphSelectionChanged(SelectionState state)
 {
-    QString text;
-    QString unit = " ns";
-    double unitScale = 1;
+    ProfileTableModel* model = (ProfileTableModel*)m_table->model();
 
-    if (unitTime == 0) {
-        unitTime = time;
+    if (!model) {
+        return;
     }
 
-    if (unitTime >= 60e9) {
-        int64_t mins = time / 60e9;
-        text += QString("%1 m ").arg(mins);
-
-        time -= mins * 60e9;
-        unit = " s";
-        unitScale = 1e9;
-    } else if (unitTime >= 1e9) {
-        unit = " s";
-        unitScale = 1e9;
-    } else if (unitTime >= 1e6) {
-        unit = " ms";
-        unitScale = 1e6;
-    } else if (unitTime >= 1e3) {
-        unit = QString::fromUtf8(" Âµs");
-        unitScale = 1e3;
+    if (state.type == SelectionState::None) {
+        model->selectNone();
+    } else if (state.type == SelectionState::Horizontal) {
+        model->selectTime(state.start, state.end);
+    } else if (state.type == SelectionState::Vertical) {
+        model->selectProgram(state.start);
     }
 
-    /* 3 decimal places */
-    text += QString("%1").arg(time / unitScale, 0, 'f', 3);
-
-    /* Remove trailing 0 */
-    while(text.endsWith('0'))
-        text.truncate(text.length() - 1);
-
-    /* Remove trailing decimal point */
-    if (text.endsWith(QLocale::system().decimalPoint()))
-        text.truncate(text.length() - 1);
+    m_table->reset();
 
-    return text + unit;
+    if (state.type == SelectionState::Vertical) {
+        m_table->selectRow(model->getRowIndex(state.start));
+    }
 }
 
-
 #include "profiledialog.moc"
index 22ef066f05a25377a0c6fd9e2b2e650048f5f174..862d068bcd888bcae36ec890df9ccda29f47f26e 100644 (file)
@@ -15,16 +15,11 @@ public:
     ~ProfileDialog();
     
     void setProfile(trace::Profile* profile);
+    void showCall(int call);
 
 public slots:
-    void setVerticalScrollMax(int max);
-    void setHorizontalScrollMax(int max);
-
     void tableDoubleClicked(const QModelIndex& index);
-
-    void selectNone();
-    void selectProgram(unsigned program);
-    void selectTime(int64_t start, int64_t end);
+    void graphSelectionChanged(SelectionState state);
 
 signals:
     void jumpToCall(int call);
@@ -33,6 +28,4 @@ private:
     trace::Profile *m_profile;
 };
 
-QString getTimeString(int64_t time, int64_t unitTime = 0);
-
 #endif
diff --git a/gui/profileheatmap.h b/gui/profileheatmap.h
new file mode 100644 (file)
index 0000000..e50ea26
--- /dev/null
@@ -0,0 +1,470 @@
+#ifndef PROFILEHEATMAP_H
+#define PROFILEHEATMAP_H
+
+#include "graphing/heatmapview.h"
+#include "profiling.h"
+
+/**
+ * Data providers for a heatmap based off the trace::Profile call data
+ */
+
+class ProfileHeatmapRowIterator : public HeatmapRowIterator {
+public:
+    ProfileHeatmapRowIterator(const trace::Profile* profile, qint64 start, qint64 end, int steps, bool gpu, int program = -1) :
+        m_profile(profile),
+        m_step(-1),
+        m_stepWidth(1),
+        m_stepCount(steps),
+        m_index(0),
+        m_timeStart(start),
+        m_timeEnd(end),
+        m_useGpu(gpu),
+        m_program(program),
+        m_selected(false),
+        m_timeSelection(false),
+        m_programSelection(false)
+    {
+        m_timeWidth = m_timeEnd - m_timeStart;
+    }
+
+    virtual bool next()
+    {
+        unsigned maxIndex = m_program == -1 ? m_profile->calls.size() : m_profile->programs[m_program].calls.size();
+
+        if (m_index >= maxIndex) {
+            return false;
+        }
+
+        double dtds = m_timeWidth / (double)m_stepCount;
+
+        qint64 heatDuration = 0;
+        qint64 programHeatDuration = 0;
+        m_heat = 0.0f;
+        m_step += m_stepWidth;
+        m_stepWidth = 1;
+
+        m_selected = false;
+
+        /* Iterator through calls until step != lastStep */
+        for (; m_index < maxIndex; ++m_index)
+        {
+            const trace::Profile::Call* call;
+
+            if (m_program == -1) {
+                call = &m_profile->calls[m_index];
+            } else {
+                call = &m_profile->calls[ m_profile->programs[m_program].calls[m_index] ];
+            }
+
+            qint64 start, duration, end;
+
+            if (m_useGpu) {
+                start = call->gpuStart;
+                duration = call->gpuDuration;
+
+                if (call->pixels < 0) {
+                    continue;
+                }
+            } else {
+                start = call->cpuStart;
+                duration = call->cpuDuration;
+            }
+
+            end = start + duration;
+
+            if (end < m_timeStart) {
+                continue;
+            }
+
+            if (start > m_timeEnd) {
+                m_index = m_profile->calls.size();
+                break;
+            }
+
+            double left  = timeToStep(start);
+            double right = timeToStep(end);
+
+            int leftStep = left;
+            int rightStep = right;
+
+            if (leftStep > m_step) {
+                break;
+            }
+
+            if (m_programSelection && call->program == m_programSel) {
+                m_selected = true;
+            }
+
+            if (rightStep - leftStep > 1) {
+                m_label = QString::fromStdString(call->name);
+                m_step = left;
+                m_stepWidth = rightStep - leftStep;
+                heatDuration = dtds;
+                ++m_index;
+                break;
+            }
+
+            if (leftStep < m_step) {
+                qint64 rightTime = stepToTime(rightStep);
+                heatDuration += end - rightTime;
+
+                if (m_programSelection && call->program == m_programSel) {
+                    programHeatDuration += end - rightTime;
+                }
+            } else if (leftStep == rightStep) {
+                heatDuration += duration;
+
+                if (m_programSelection && call->program == m_programSel) {
+                    programHeatDuration += duration;
+                }
+            } else if (rightStep - leftStep == 1) {
+                qint64 rightTime = stepToTime(rightStep);
+                heatDuration += rightTime - start;
+
+                if (m_programSelection && call->program == m_programSel) {
+                    programHeatDuration += rightTime - start;
+                }
+
+                break;
+            }
+        }
+
+        m_heat = heatDuration / dtds;
+        m_programHeat = programHeatDuration / dtds;
+
+        if (m_timeSelection) {
+            qint64 time = stepToTime(m_step);
+
+            if (time >= m_timeSelStart && time <= m_timeSelEnd) {
+                m_programHeat = 1.0;
+            }
+        }
+
+        if (m_programSelection && (m_program == m_programSel || (m_selected && m_stepWidth > 1))) {
+            m_programHeat = 1.0;
+        }
+
+        if (m_programHeat > 0) {
+            m_selected = true;
+        }
+
+        return true;
+    }
+
+    virtual bool isGpu() const
+    {
+        return m_useGpu;
+    }
+
+    virtual float heat() const
+    {
+        return m_heat;
+    }
+
+    virtual float selectedHeat() const
+    {
+        return m_programHeat;
+    }
+
+    virtual int step() const
+    {
+        return m_step;
+    }
+
+    virtual int width() const
+    {
+        return m_stepWidth;
+    }
+
+    virtual QString label() const
+    {
+        return m_label;
+    }
+
+    void setProgramSelection(int program)
+    {
+        m_programSelection = true;
+        m_programSel = program;
+    }
+
+    void setTimeSelection(qint64 start, qint64 end)
+    {
+        m_timeSelection = true;
+        m_timeSelStart = start;
+        m_timeSelEnd = end;
+    }
+
+private:
+    double timeToStep(qint64 time) const
+    {
+        double pos = time;
+        pos -= m_timeStart;
+        pos /= m_timeWidth;
+        pos *= m_stepCount;
+        return pos;
+    }
+
+    qint64 stepToTime(int pos) const
+    {
+        double time = pos;
+        time /= m_stepCount;
+        time *= m_timeWidth;
+        time += m_timeStart;
+        return (qint64)time;
+    }
+
+private:
+    const trace::Profile* m_profile;
+
+    int m_step;
+    int m_stepWidth;
+    int m_stepCount;
+
+    unsigned m_index;
+
+    float m_heat;
+
+    qint64 m_timeStart;
+    qint64 m_timeEnd;
+    qint64 m_timeWidth;
+
+    bool m_useGpu;
+    int m_program;
+
+    QString m_label;
+
+    bool m_selected;
+
+    bool m_timeSelection;
+    qint64 m_timeSelStart;
+    qint64 m_timeSelEnd;
+
+    bool m_programSelection;
+    int m_programSel;
+
+    float m_programHeat;
+};
+
+class ProfileHeatmapDataProvider : public HeatmapDataProvider {
+protected:
+    enum SelectionType {
+        None,
+        Time,
+        Program
+    };
+
+public:
+    ProfileHeatmapDataProvider(trace::Profile* profile) :
+        m_profile(profile),
+        m_selectionState(NULL)
+    {
+        sortRows();
+    }
+
+    virtual qint64 start() const
+    {
+        return m_profile->frames.front().cpuStart;
+    }
+
+    virtual qint64 end() const
+    {
+        return m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
+    }
+
+    virtual unsigned dataRows() const
+    {
+        return m_rowPrograms.size();
+    }
+
+    virtual QString dataLabel(unsigned row) const
+    {
+        if (row >= m_rowPrograms.size()) {
+            return QString();
+        } else {
+            return QString("%1").arg(m_rowPrograms[row]);
+        }
+    }
+
+    virtual qint64 dataRowAt(unsigned row) const
+    {
+        if (row >= m_rowPrograms.size()) {
+            return 0;
+        } else {
+            return m_rowPrograms[row];
+        }
+    }
+
+    virtual HeatmapRowIterator* dataRowIterator(int row, qint64 start, qint64 end, int steps) const
+    {
+        ProfileHeatmapRowIterator* itr = new ProfileHeatmapRowIterator(m_profile, start, end, steps, true, m_rowPrograms[row]);
+
+        if (m_selectionState) {
+            if (m_selectionState->type == SelectionState::Horizontal) {
+                itr->setTimeSelection(m_selectionState->start, m_selectionState->end);
+            } else if (m_selectionState->type == SelectionState::Vertical) {
+                itr->setProgramSelection(m_selectionState->start);
+            }
+        }
+
+        return itr;
+    }
+
+    virtual unsigned headerRows() const
+    {
+        return 2;
+    }
+
+    virtual qint64 headerRowAt(unsigned row) const
+    {
+        return row;
+    }
+
+    virtual QString headerLabel(unsigned row) const
+    {
+        if (row == 0) {
+            return "CPU";
+        } else if (row == 1) {
+            return "GPU";
+        } else {
+            return QString();
+        }
+    }
+
+    virtual HeatmapRowIterator* headerRowIterator(int row, qint64 start, qint64 end, int steps) const
+    {
+        ProfileHeatmapRowIterator* itr = new ProfileHeatmapRowIterator(m_profile, start, end, steps, row != 0);
+
+        if (m_selectionState) {
+            if (m_selectionState->type == SelectionState::Horizontal) {
+                itr->setTimeSelection(m_selectionState->start, m_selectionState->end);
+            } else if (m_selectionState->type == SelectionState::Vertical) {
+                itr->setProgramSelection(m_selectionState->start);
+            }
+        }
+
+        return itr;
+    }
+
+    virtual qint64 dataItemAt(unsigned row, qint64 time) const
+    {
+        if (row >= m_rowPrograms.size()) {
+            return -1;
+        }
+
+        unsigned program = m_rowPrograms[row];
+
+        std::vector<unsigned>::const_iterator item =
+                Profiling::binarySearchTimespanIndexed
+                    (m_profile->calls, m_profile->programs[program].calls.begin(), m_profile->programs[program].calls.end(), time);
+
+        if (item == m_profile->programs[program].calls.end()) {
+            return -1;
+        }
+
+        return *item;
+    }
+
+    virtual qint64 headerItemAt(unsigned row, qint64 time) const
+    {
+        if (row >= m_rowPrograms.size()) {
+            return -1;
+        }
+
+        if (row == 0) {
+            /* CPU */
+            std::vector<trace::Profile::Call>::const_iterator item =
+                    Profiling::binarySearchTimespan<trace::Profile::Call,
+                                        &trace::Profile::Call::cpuStart,
+                                        &trace::Profile::Call::cpuDuration>
+                    (m_profile->calls.begin(), m_profile->calls.end(), time);
+
+            if (item != m_profile->calls.end()) {
+                return item - m_profile->calls.begin();
+            }
+        } else if (row == 1) {
+            /* GPU */
+            for (unsigned i = 0; i < m_rowPrograms.size(); ++i) {
+                qint64 index = dataItemAt(i, time);
+
+                if (index != -1) {
+                    return index;
+                }
+            }
+        }
+
+        return -1;
+    }
+
+    virtual void itemDoubleClicked(qint64 index) const
+    {
+        if (index < 0 || index >= m_profile->calls.size()) {
+            return;
+        }
+
+        const trace::Profile::Call& call = m_profile->calls[index];
+        Profiling::jumpToCall(call.no);
+    }
+
+    virtual QString itemTooltip(qint64 index) const
+    {
+        if (index >= m_profile->calls.size()) {
+            return QString();
+        }
+
+        const trace::Profile::Call& call = m_profile->calls[index];
+
+        QString text;
+        text  = QString::fromStdString(call.name);
+
+        text += QString("\nCall: %1").arg(call.no);
+        text += QString("\nCPU Start: %1").arg(Profiling::getTimeString(call.cpuStart));
+        text += QString("\nCPU Duration: %1").arg(Profiling::getTimeString(call.cpuDuration));
+
+        if (call.pixels >= 0) {
+            text += QString("\nGPU Start: %1").arg(Profiling::getTimeString(call.gpuStart));
+            text += QString("\nGPU Duration: %1").arg(Profiling::getTimeString(call.gpuDuration));
+            text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call.pixels));
+        }
+
+        return text;
+    }
+
+    virtual void setSelectionState(SelectionState* state)
+    {
+        m_selectionState = state;
+    }
+
+private:
+    void sortRows()
+    {
+        typedef QPair<quint64, unsigned> Pair;
+        std::vector<Pair> gpu;
+
+        /* Map shader to visible row */
+        for (std::vector<trace::Profile::Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
+            const trace::Profile::Program& program = *itr;
+            unsigned no = itr -  m_profile->programs.begin();
+
+            if (program.gpuTotal > 0) {
+                gpu.push_back(Pair(program.gpuTotal, no));
+            }
+        }
+
+        /* Sort the shaders by most used gpu */
+        qSort(gpu);
+
+        /* Create row order */
+        m_rowPrograms.clear();
+
+        for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
+            m_rowPrograms.push_back(itr->second);
+        }
+    }
+
+protected:
+    trace::Profile* m_profile;
+    std::vector<int> m_rowPrograms;
+    SelectionState* m_selectionState;
+};
+
+#endif
index cbe2eea94189eaeb6125843c9b8d47838a101435..11a0d0c10901e299b271dfabad75e4948fb0c5f7 100644 (file)
@@ -1,5 +1,6 @@
 #include "profiletablemodel.h"
 #include "profiledialog.h"
+#include "profiling.h"
 
 #include <QLocale>
 
@@ -235,15 +236,15 @@ QVariant ProfileTableModel::data(const QModelIndex &index, int role) const
         case COLUMN_USAGES:
             return QLocale::system().toString(row.uses);
         case COLUMN_GPU_TIME:
-            return getTimeString(row.gpuTime);
+            return Profiling::getTimeString(row.gpuTime);
         case COLUMN_CPU_TIME:
-            return getTimeString(row.cpuTime);
+            return Profiling::getTimeString(row.cpuTime);
         case COLUMN_PIXELS_DRAWN:
             return QLocale::system().toString((qlonglong)row.pixels);
         case COLUMN_GPU_AVERAGE:
-            return getTimeString((row.uses <= 0) ? 0 : (row.gpuTime / row.uses));
+            return Profiling::getTimeString((row.uses <= 0) ? 0 : (row.gpuTime / row.uses));
         case COLUMN_CPU_AVERAGE:
-            return getTimeString((row.uses <= 0) ? 0 : (row.cpuTime / row.uses));
+            return Profiling::getTimeString((row.uses <= 0) ? 0 : (row.cpuTime / row.uses));
         case COLUMN_PIXELS_AVERAGE:
             return QLocale::system().toString((row.uses <= 0) ? 0 : (row.pixels / row.uses));
         }
diff --git a/gui/profiling.h b/gui/profiling.h
new file mode 100644 (file)
index 0000000..a5b1266
--- /dev/null
@@ -0,0 +1,128 @@
+#ifndef PROFILING_H
+#define PROFILING_H
+
+#include <QString>
+#include <QLocale>
+#include "trace_profiler.hpp"
+
+class Profiling {
+public:
+    /**
+     * Select and show the call in main window.
+     */
+    static void jumpToCall(int index);
+
+    /**
+     * Convert a CPU / GPU time to a textual representation.
+     * This includes automatic unit selection.
+     */
+    static QString getTimeString(int64_t time, int64_t unitTime = 0)
+    {
+        QString text;
+        QString unit = " ns";
+        double unitScale = 1;
+
+        if (unitTime == 0) {
+            unitTime = time;
+        }
+
+        if (unitTime >= 60e9) {
+            int64_t mins = time / 60e9;
+            text += QString("%1 m ").arg(mins);
+
+            time -= mins * 60e9;
+            unit = " s";
+            unitScale = 1e9;
+        } else if (unitTime >= 1e9) {
+            unit = " s";
+            unitScale = 1e9;
+        } else if (unitTime >= 1e6) {
+            unit = " ms";
+            unitScale = 1e6;
+        } else if (unitTime >= 1e3) {
+            unit = QString::fromUtf8(" Âµs");
+            unitScale = 1e3;
+        }
+
+        /* 3 decimal places */
+        text += QString("%1").arg(time / unitScale, 0, 'f', 3);
+
+        /* Remove trailing 0 */
+        while(text.endsWith('0'))
+            text.truncate(text.length() - 1);
+
+        /* Remove trailing decimal point */
+        if (text.endsWith(QLocale::system().decimalPoint()))
+            text.truncate(text.length() - 1);
+
+        return text + unit;
+    }
+
+    template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
+    static typename std::vector<val_ty>::const_iterator binarySearchTimespan(
+            typename std::vector<val_ty>::const_iterator begin,
+            typename std::vector<val_ty>::const_iterator end,
+            int64_t time,
+            bool nearest = false)
+    {
+        int lower = 0;
+        int upper = end - begin;
+        int pos = (lower + upper) / 2;
+        typename std::vector<val_ty>::const_iterator itr = begin + pos;
+
+        while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
+            if ((*itr).*mem_ptr_start > time) {
+                upper = pos - 1;
+            } else {
+                lower = pos + 1;
+            }
+
+            pos = (lower + upper) / 2;
+            itr = begin + pos;
+        }
+
+        if (nearest || lower <= upper) {
+            return itr;
+        } else {
+            return end;
+        }
+    }
+
+    static std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
+            const std::vector<trace::Profile::Call>& calls,
+            std::vector<unsigned>::const_iterator begin,
+            std::vector<unsigned>::const_iterator end,
+            int64_t time,
+            bool nearest = false)
+    {
+        int lower = 0;
+        int upper = end - begin - 1;
+        int pos = (lower + upper) / 2;
+        std::vector<unsigned>::const_iterator itr = begin + pos;
+
+        while (lower <= upper) {
+            const trace::Profile::Call& call = calls[*itr];
+
+            if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) {
+                break;
+            }
+
+            if (call.gpuStart > time) {
+                upper = pos - 1;
+            } else {
+                lower = pos + 1;
+            }
+
+            pos = (lower + upper) / 2;
+            itr = begin + pos;
+        }
+
+        if (nearest || lower <= upper) {
+            return itr;
+        } else {
+            return end;
+        }
+    }
+};
+
+#endif
diff --git a/gui/timelinewidget.cpp b/gui/timelinewidget.cpp
deleted file mode 100644 (file)
index 0f30225..0000000
+++ /dev/null
@@ -1,1078 +0,0 @@
-#include "timelinewidget.h"
-#include "profiledialog.h"
-#include "trace_profiler.hpp"
-
-#include <qmath.h>
-#include <QColor>
-#include <QLocale>
-#include <QPainter>
-#include <QToolTip>
-#include <QMouseEvent>
-#include <QWheelEvent>
-#include <QApplication>
-
-typedef trace::Profile::Call Call;
-typedef trace::Profile::Frame Frame;
-typedef trace::Profile::Program Program;
-
-TimelineWidget::TimelineWidget(QWidget *parent)
-    : QWidget(parent),
-      m_profile(NULL),
-      m_rowHeight(20),
-      m_axisWidth(50),
-      m_axisHeight(30),
-      m_axisLine(QColor(240, 240, 240)),
-      m_axisBorder(Qt::black),
-      m_axisForeground(Qt::black),
-      m_axisBackground(QColor(210, 210, 210)),
-      m_itemBorder(Qt::red),
-      m_itemGpuForeground(Qt::cyan),
-      m_itemGpuBackground(Qt::red),
-      m_itemCpuForeground(QColor(255, 255, 0)),
-      m_itemCpuBackground(QColor(0, 0, 255)),
-      m_itemDeselectedForeground(Qt::white),
-      m_itemDeselectedBackground(QColor(155, 155, 155)),
-      m_selectionBorder(Qt::green),
-      m_selectionBackground(QColor(100, 255, 100, 8)),
-      m_zoomBorder(QColor(255, 0, 255)),
-      m_zoomBackground(QColor(255, 0, 255, 30))
-{
-    setBackgroundRole(QPalette::Base);
-    setAutoFillBackground(true);
-    setMouseTracking(true);
-
-    m_selection.type = SelectNone;
-}
-
-
-/**
- * Update horizontal view scroll based on scroll value
- */
-void TimelineWidget::setHorizontalScrollValue(int scrollValue)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    /* Calculate time from scroll value */
-    double time = scrollValue;
-    time /= m_maxScrollX;
-    time *= (m_timeMax - m_timeWidth) - m_timeMin;
-    time += m_timeMin;
-
-    setTimeScroll(time, false);
-}
-
-
-/**
- * Update vertical view scroll based on scroll value
- */
-void TimelineWidget::setVerticalScrollValue(int value)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    setRowScroll(value, false);
-}
-
-
-/**
- * Set selection to nothing
- */
-void TimelineWidget::selectNone(bool notify)
-{
-    m_selection.type = SelectNone;
-
-    if (notify) {
-        emit selectedNone();
-    }
-
-    update();
-}
-
-
-/**
- * Set selection to a program
- */
-void TimelineWidget::selectProgram(unsigned program, bool notify)
-{
-    m_selection.program = program;
-    m_selection.type = SelectProgram;
-
-    if (notify) {
-        emit selectedProgram(program);
-    }
-
-    update();
-}
-
-
-/**
- * Set selection to a period of time
- */
-void TimelineWidget::selectTime(int64_t start, int64_t end, bool notify)
-{
-    m_selection.timeStart = start;
-    m_selection.timeEnd = end;
-    m_selection.type = SelectTime;
-
-    if (notify) {
-        emit selectedTime(start, end);
-    }
-
-    update();
-}
-
-
-/**
- * Convert time to view position
- */
-double TimelineWidget::timeToPosition(int64_t time)
-{
-    double pos = time;
-    pos -= m_time;
-    pos /= m_timeWidth;
-    pos *= m_viewWidth;
-    return pos;
-}
-
-
-/**
- * Convert view position to time
- */
-int64_t TimelineWidget::positionToTime(int pos)
-{
-    double time = pos;
-    time /= m_viewWidth;
-    time *= m_timeWidth;
-    time += m_time;
-    return (int64_t)time;
-}
-
-
-/**
- * Binary Search for a time in start+durations
- */
-template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
-typename std::vector<val_ty>::const_iterator binarySearchTimespan(
-        typename std::vector<val_ty>::const_iterator begin,
-        typename std::vector<val_ty>::const_iterator end,
-        int64_t time)
-{
-    int lower = 0;
-    int upper = end - begin;
-    int pos = (lower + upper) / 2;
-    typename std::vector<val_ty>::const_iterator itr = begin + pos;
-
-    while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
-        if ((*itr).*mem_ptr_start > time) {
-            upper = pos - 1;
-        } else {
-            lower = pos + 1;
-        }
-
-        pos = (lower + upper) / 2;
-        itr = begin + pos;
-    }
-
-    if (lower <= upper) {
-        return itr;
-    } else {
-        return end;
-    }
-}
-
-
-/**
- * Binary Search for a time in start+durations on an array of indices
- */
-std::vector<unsigned>::const_iterator binarySearchTimespanIndexed(
-        const std::vector<Call>& calls,
-        std::vector<unsigned>::const_iterator begin,
-        std::vector<unsigned>::const_iterator end,
-        int64_t time)
-{
-    int lower = 0;
-    int upper = end - begin - 1;
-    int pos = (lower + upper) / 2;
-    std::vector<unsigned>::const_iterator itr = begin + pos;
-
-    while (lower <= upper) {
-        const Call& call = calls[*itr];
-
-        if (call.gpuStart <= time && call.gpuStart + call.gpuDuration > time) {
-            break;
-        }
-
-        if (call.gpuStart > time) {
-            upper = pos - 1;
-        } else {
-            lower = pos + 1;
-        }
-
-        pos = (lower + upper) / 2;
-        itr = begin + pos;
-    }
-
-    if (lower <= upper) {
-        return itr;
-    } else {
-        return end;
-    }
-}
-
-
-/**
- * Find the frame at time
- */
-const Frame* TimelineWidget::frameAtTime(int64_t time)
-{
-    if (!m_profile) {
-        return NULL;
-    }
-
-    std::vector<Frame>::const_iterator res
-            = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
-                m_profile->frames.begin(),
-                m_profile->frames.end(),
-                time);
-
-    if (res != m_profile->frames.end()) {
-        return &*res;
-    }
-
-    return NULL;
-}
-
-
-/**
- * Find the CPU call at time
- */
-const Call* TimelineWidget::cpuCallAtTime(int64_t time)
-{
-    if (!m_profile) {
-        return NULL;
-    }
-
-    std::vector<Call>::const_iterator res
-            = binarySearchTimespan<Call, &Call::cpuStart, &Call::cpuDuration>(
-                m_profile->calls.begin(),
-                m_profile->calls.end(),
-                time);
-
-    if (res != m_profile->calls.end()) {
-        return &*res;
-    }
-
-    return NULL;
-}
-
-
-/**
- * Find the draw call at time
- */
-const Call* TimelineWidget::drawCallAtTime(int64_t time)
-{
-    if (!m_profile) {
-        return NULL;
-    }
-
-    for (int i = 0; i < m_rowPrograms.size(); ++i) {
-        const Call* call = drawCallAtTime(time, m_rowPrograms[i]);
-
-        if (call) {
-            return call;
-        }
-    }
-
-    return NULL;
-}
-
-
-/**
- * Find the draw call at time for a selected program
- */
-const Call* TimelineWidget::drawCallAtTime(int64_t time, int program)
-{
-    if (!m_profile) {
-        return NULL;
-    }
-
-    std::vector<unsigned>::const_iterator res
-            = binarySearchTimespanIndexed(
-                m_profile->calls,
-                m_profile->programs[program].calls.begin(),
-                m_profile->programs[program].calls.end(),
-                time);
-
-    if (res != m_profile->programs[program].calls.end()) {
-        return &m_profile->calls[*res];
-    }
-
-    return NULL;
-}
-
-
-/**
- * Calculate the row order by total gpu time per shader
- */
-void TimelineWidget::calculateRows()
-{
-    typedef QPair<uint64_t, unsigned> Pair;
-    std::vector<Pair> gpu;
-
-    /* Map shader to visible row */
-    for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
-        const Program& program = *itr;
-        unsigned no = itr -  m_profile->programs.begin();
-
-        if (program.gpuTotal > 0) {
-            gpu.push_back(Pair(program.gpuTotal, no));
-        }
-    }
-
-    /* Sort the shaders by most used gpu */
-    qSort(gpu);
-
-    /* Create row order */
-    m_rowPrograms.clear();
-
-    for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
-        m_rowPrograms.push_back(itr->second);
-    }
-
-    m_rowCount = m_rowPrograms.size();
-}
-
-
-/**
- * Set the trace profile to use for the timeline
- */
-void TimelineWidget::setProfile(trace::Profile* profile)
-{
-    if (!profile->frames.size())
-        return;
-
-    m_profile = profile;
-    calculateRows();
-
-    m_timeMin = m_profile->frames.front().cpuStart;
-    m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
-
-    m_time = m_timeMin;
-    m_timeWidth = m_timeMax - m_timeMin;
-
-    m_timeWidthMin = 1000;
-    m_timeWidthMax = m_timeWidth;
-
-    m_maxScrollX = 0;
-    m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
-
-    setTimeScroll(m_time);
-    setRowScroll(0);
-    selectNone();
-    update();
-}
-
-
-/**
- * Set the horizontal scroll position to time
- */
-void TimelineWidget::setTimeScroll(int64_t time, bool notify)
-{
-    time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
-
-    m_time = time;
-
-    if (m_timeWidth == m_timeWidthMax) {
-        m_maxScrollX = 0;
-    } else {
-        m_maxScrollX = 10000;
-    }
-
-    if (notify) {
-        double value = time - m_timeMin;
-        value /= m_timeMax - m_timeWidth - m_timeMin;
-        value *= m_maxScrollX;
-        m_scrollX = value;
-
-        emit horizontalScrollMaxChanged(m_maxScrollX);
-        emit horizontalScrollValueChanged(m_scrollX);
-    }
-
-    update();
-}
-
-
-/**
- * Set the vertical scroll position to position
- */
-void TimelineWidget::setRowScroll(int position, bool notify)
-{
-    position = qBound(0, position, m_maxScrollY);
-
-    m_scrollY = position;
-    m_row  = m_scrollY / m_rowHeight;
-
-    if (notify) {
-        emit verticalScrollMaxChanged(m_maxScrollY);
-        emit verticalScrollValueChanged(m_scrollY);
-    }
-
-    update();
-}
-
-
-void TimelineWidget::resizeEvent(QResizeEvent *e)
-{
-    /* Update viewport size */
-    m_viewWidth = qMax(0, width() - m_axisWidth);
-    m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight * 2);
-
-    /* Update vertical scroll bar */
-    if (m_profile) {
-        m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
-        emit verticalScrollMaxChanged(m_maxScrollY);
-        setRowScroll(m_scrollY);
-    }
-}
-
-
-void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
-{
-    bool tooltip = false;
-    m_mousePosition = e->pos();
-
-    if (!m_profile) {
-        return;
-    }
-
-    /* Display tooltip if necessary */
-    if (e->buttons() == Qt::NoButton) {
-        if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
-            int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
-            int y = m_mousePosition.y() - m_axisHeight;
-
-            if (y < m_rowHeight) {
-                const Call* call = cpuCallAtTime(time);
-
-                if (call) {
-                    QString text;
-                    text  = QString::fromStdString(call->name);
-                    text += QString("\nCall: %1").arg(call->no);
-                    text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
-                    text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
-
-                    QToolTip::showText(e->globalPos(), text);
-                    tooltip = true;
-                }
-            } else {
-                const Call* call = NULL;
-
-                if (y < m_rowHeight * 2) {
-                    call = drawCallAtTime(time);
-                } else {
-                    int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
-
-                    if (row < m_rowPrograms.size()) {
-                        call = drawCallAtTime(time, m_rowPrograms[row]);
-                    }
-                }
-
-                if (call) {
-                    QString text;
-                    text  = QString::fromStdString(call->name);
-                    text += QString("\nCall: %1").arg(call->no);
-                    text += QString("\nCPU Start: %1").arg(getTimeString(call->cpuStart));
-                    text += QString("\nGPU Start: %1").arg(getTimeString(call->gpuStart));
-                    text += QString("\nCPU Duration: %1").arg(getTimeString(call->cpuDuration));
-                    text += QString("\nGPU Duration: %1").arg(getTimeString(call->gpuDuration));
-                    text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call->pixels));
-
-                    QToolTip::showText(e->globalPos(), text);
-                    tooltip = true;
-                }
-            }
-        } else if (m_mousePosition.x() < m_axisWidth && m_mousePosition.y() > m_axisHeight) {
-            int y = m_mousePosition.y() - m_axisHeight;
-
-            if (y < m_rowHeight) {
-                QToolTip::showText(e->globalPos(), "All CPU calls");
-                tooltip = true;
-            } else if (y < m_rowHeight * 2) {
-                QToolTip::showText(e->globalPos(), "All GPU calls");
-                tooltip = true;
-            } else {
-                int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
-
-                if (row < m_rowPrograms.size()) {
-                    QToolTip::showText(e->globalPos(), QString("All calls in Shader Program %1").arg(m_rowPrograms[row]));
-                    tooltip = true;
-                }
-            }
-        }
-    } else if (e->buttons().testFlag(Qt::LeftButton)) {
-        if (m_mousePressMode == DragView) {
-            /* Horizontal scroll */
-            double dt = m_timeWidth;
-            dt /= m_viewWidth;
-            dt *= m_mousePressPosition.x() - e->pos().x();
-            setTimeScroll(m_mousePressTime + dt);
-
-            /* Vertical scroll */
-            int dy = m_mousePressPosition.y() - e->pos().y();
-            setRowScroll(m_mousePressRow + dy);
-        } else if (m_mousePressMode == RulerSelect) {
-            /* Horizontal selection */
-            int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
-            int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
-
-            selectTime(qMin(down, up), qMax(down, up), true);
-        }
-
-        update();
-    }
-
-    if (!tooltip) {
-        QToolTip::hideText();
-    }
-}
-
-
-void TimelineWidget::mousePressEvent(QMouseEvent *e)
-{
-    if (e->buttons() & Qt::LeftButton) {
-        if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
-            if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
-                m_mousePressMode = RulerZoom;
-            } else {
-                m_mousePressMode = RulerSelect;
-            }
-        } else if (e->pos().x() >= m_axisWidth) {
-            m_mousePressMode = DragView;
-        } else {
-            m_mousePressMode = NoMousePress;
-        }
-
-        m_mousePressPosition  = e->pos();
-        m_mousePressTime = m_time;
-        m_mousePressRow  = m_scrollY;
-
-        update();
-    }
-}
-
-
-void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    /* Calculate new time view based on selected area */
-    int dxy = qAbs(m_mousePressPosition.x() - e->pos().x()) + qAbs(m_mousePressPosition.y() - e->pos().y());
-
-    int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
-    int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
-
-    int64_t left  = qMin(down, up);
-    int64_t right = qMax(down, up);
-
-    if (m_mousePressMode == RulerZoom) {
-        m_timeWidth = right - left;
-        m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
-
-        m_mousePressMode = NoMousePress;
-        setTimeScroll(left);
-    } else {
-        if (dxy <= 2) {
-            if (m_selection.type == SelectTime) {
-                if (left < m_selection.timeStart || right > m_selection.timeEnd || e->pos().x() < m_axisWidth) {
-                    selectNone(true);
-                }
-            } else if (m_selection.type == SelectProgram) {
-                int y = e->pos().y() - m_axisHeight;
-                int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
-
-                if (row < 0 || m_rowPrograms[row] != m_selection.program) {
-                    selectNone(true);
-                }
-            }
-        } else if (m_mousePressMode == RulerSelect) {
-            selectTime(left, right, true);
-        }
-    }
-}
-
-
-void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
-{
-    int64_t time = positionToTime(e->pos().x() - m_axisWidth);
-
-    if (e->pos().x() > m_axisWidth) {
-        int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
-
-        if (e->pos().y() < m_axisHeight) {
-            /* Horizontal axis */
-            const Frame* frame = frameAtTime(time);
-
-            if (frame) {
-                selectTime(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
-                return;
-            }
-        } else if (row == 0) {
-            /* CPU Calls */
-            const Call* call = cpuCallAtTime(time);
-
-            if (call) {
-                emit jumpToCall(call->no);
-                return;
-            }
-        } else if (row > 0) {
-            /* Draw Calls */
-            const Call* call = drawCallAtTime(time, 0);
-
-            if (call) {
-                emit jumpToCall(call->no);
-                return;
-            }
-        }
-    } else {
-        int y = e->pos().y() - m_axisHeight;
-        int row = (y - m_rowHeight * 2 + m_scrollY) / m_rowHeight;
-
-        if (row >= 0 && row < m_rowPrograms.size()) {
-            selectProgram(m_rowPrograms[row], true);
-        }
-    }
-}
-
-
-void TimelineWidget::wheelEvent(QWheelEvent *e)
-{
-    if (!m_profile) {
-        return;
-    }
-
-    if (e->pos().x() < m_axisWidth) {
-        return;
-    }
-
-    int zoomPercent = 10;
-
-    /* If holding Ctrl key then zoom 2x faster */
-    if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
-        zoomPercent = 20;
-    }
-
-    /* Zoom view by adjusting width */
-    double dt = m_timeWidth;
-    double size = m_timeWidth;
-    size *= -e->delta();
-
-    /* Zoom deltas normally come in increments of 120 */
-    size /= 120 * (100 / zoomPercent);
-
-    m_timeWidth += size;
-    m_timeWidth  = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
-
-    /* Scroll view to zoom around mouse */
-    dt -= m_timeWidth;
-    dt *= e->x() - m_axisWidth;
-    dt /= m_viewWidth;
-    setTimeScroll(dt + m_time);
-
-    update();
-}
-
-
-/**
- * Paints a single pixel column of the heat map
- */
-void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected)
-{
-    if (heat == 0) {
-        return;
-    }
-
-    if (m_selection.type == SelectTime) {
-        selected = x >= m_selectionLeft && x <= m_selectionRight;
-    }
-
-    double timePerPixel = m_timeWidth / (double)m_viewWidth;
-    double colour = heat / timePerPixel;
-
-    /* Gamma correction */
-    colour = qPow(colour, 1.0 / 2.0);
-
-    if (!selected) {
-        colour = qBound(0.0, colour * 100.0, 100.0);
-        painter.setPen(QColor(255 - colour, 255 - colour, 255 - colour));
-    } else if (gpu) {
-        colour = qBound(0.0, colour * 255.0, 255.0);
-        painter.setPen(QColor(255, 255 - colour, 255 - colour));
-    } else {
-        colour = qBound(0.0, colour * 255.0, 255.0);
-        painter.setPen(QColor(255 - colour, 255 - colour, 255));
-    }
-
-    painter.drawLine(x, 0, x, m_rowHeight - 1);
-}
-
-
-/**
- * Draws a call on the heatmap
- */
-bool TimelineWidget::drawCall(QPainter& painter, const trace::Profile::Call& call, int& lastX, int64_t& heat, bool gpu)
-{
-    int64_t start, duration, end;
-
-    if (gpu) {
-        start = call.gpuStart;
-        duration = call.gpuDuration;
-    } else {
-        start = call.cpuStart;
-        duration = call.cpuDuration;
-    }
-
-    end = start + duration;
-
-    if (start > m_timeEnd) {
-        return false;
-    }
-
-    if (end < m_time) {
-        return true;
-    }
-
-    double left  = timeToPosition(start);
-    double right = timeToPosition(end);
-
-    int leftX  = left;
-    int rightX = right;
-
-    bool selected = true;
-
-    if (m_selection.type == SelectProgram) {
-        selected = call.program == m_selection.program;
-    }
-
-    /* Draw last heat if needed */
-    if (leftX != lastX) {
-        drawHeat(painter, lastX, heat, gpu, selected);
-        lastX = leftX;
-        heat = 0;
-    }
-
-    if (rightX <= leftX + 1) {
-        if (rightX == lastX) {
-            /* Fully contained in this X */
-            heat += duration;
-        } else {
-            /* Split call time between the two pixels it occupies */
-            int64_t time = positionToTime(rightX);
-            heat += time - start;
-
-            drawHeat(painter, lastX, heat, gpu, selected);
-
-            heat = end - time;
-            lastX = rightX;
-        }
-    } else {
-        QRect rect;
-        rect.setLeft(left + 0.5);
-        rect.setWidth(right - left);
-        rect.setTop(0);
-        rect.setHeight(m_rowHeight);
-
-        if (m_selection.type == SelectTime) {
-            selected = (start >= m_selection.timeStart && start <= m_selection.timeEnd)
-                    || (end >= m_selection.timeStart && end <= m_selection.timeEnd);
-        }
-
-        /* Draw background rect */
-        if (selected) {
-            if (gpu) {
-                painter.fillRect(rect, m_itemGpuBackground);
-            } else {
-                painter.fillRect(rect, m_itemCpuBackground);
-            }
-        } else {
-            painter.fillRect(rect, m_itemDeselectedBackground);
-        }
-
-        /* If wide enough, draw text */
-        if (rect.width() > 6) {
-            rect.adjust(1, 0, -1, -2);
-
-            if (selected) {
-                if (gpu) {
-                    painter.setPen(m_itemGpuForeground);
-                } else {
-                    painter.setPen(m_itemCpuForeground);
-                }
-            } else {
-                painter.setPen(m_itemDeselectedForeground);
-            }
-
-            painter.drawText(rect,
-                             Qt::AlignLeft | Qt::AlignVCenter,
-                             painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
-        }
-    }
-
-    return true;
-}
-
-
-/**
- * Render the whole widget
- */
-void TimelineWidget::paintEvent(QPaintEvent *e)
-{
-    if (!m_profile)
-        return;
-
-    QPainter painter(this);
-
-    int rowEnd = qMin(m_row + qCeil(m_viewHeight / (double)m_rowHeight) + 1, m_rowCount);
-    int64_t heatGPU = 0, heatCPU = 0;
-    int lastCpuX = 0, lastGpuX = 0;
-    int widgetHeight = height();
-    int widgetWidth = width();
-
-    m_timeEnd = m_time + m_timeWidth;
-    m_selectionLeft  = timeToPosition(m_selection.timeStart);
-    m_selectionRight = (timeToPosition(m_selection.timeEnd) + 0.5);
-
-
-    /* Draw program rows */
-    painter.translate(m_axisWidth, m_axisHeight + m_rowHeight * 2 - (m_scrollY % m_rowHeight));
-
-    for (int row = m_row; row < rowEnd; ++row) {
-        Program& program = m_profile->programs[m_rowPrograms[row]];
-        lastGpuX = 0;
-        heatGPU = 0;
-
-        for (std::vector<unsigned>::const_iterator itr = program.calls.begin(); itr != program.calls.end(); ++itr) {
-            const Call& call = m_profile->calls[*itr];
-
-            if (!drawCall(painter, call, lastGpuX, heatGPU, true)) {
-                break;
-            }
-        }
-
-        painter.translate(0, m_rowHeight);
-    }
-
-
-    /* Draw CPU/GPU rows */
-    painter.resetTransform();
-    painter.translate(m_axisWidth, m_axisHeight);
-    painter.fillRect(0, 0, m_viewWidth, m_rowHeight * 2, Qt::white);
-
-    lastCpuX = lastGpuX = 0;
-    heatCPU = heatGPU = 0;
-
-    for (std::vector<Call>::const_iterator itr = m_profile->calls.begin(); itr != m_profile->calls.end(); ++itr) {
-        const Call& call = *itr;
-
-        /* Draw gpu row */
-        if (call.pixels >= 0) {
-            painter.translate(0, m_rowHeight);
-            drawCall(painter, call, lastGpuX, heatGPU, true);
-            painter.translate(0, -m_rowHeight);
-        }
-
-        /* Draw cpu row */
-        if (!drawCall(painter, call, lastCpuX, heatCPU, false)) {
-            break;
-        }
-    }
-
-
-    /* Draw axis */
-    painter.resetTransform();
-    painter.setPen(m_axisBorder);
-
-    /* Top Rect */
-    painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
-    painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
-
-    /* Left Rect */
-    painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
-    painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
-
-
-    /* Draw the program numbers */
-    painter.translate(0, m_axisHeight + m_rowHeight * 2);
-
-    for (int row = m_row; row < rowEnd; ++row) {
-        int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
-
-        painter.setPen(m_axisForeground);
-        painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
-
-        if (m_selection.type == SelectProgram && m_selection.program == m_rowPrograms[row]) {
-            painter.setPen(m_selectionBorder);
-            painter.drawLine(0, qMax(0, y - 1), widgetWidth, qMax(0, y - 1));
-            painter.drawLine(0, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
-            painter.drawLine(m_axisWidth - 1, y - 1, m_axisWidth - 1, y + m_rowHeight - 1);
-        } else {
-            painter.setPen(m_axisBorder);
-            painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
-
-            painter.setPen(m_axisLine);
-            painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
-        }
-    }
-
-
-    /* Draw the "CPU" axis label */
-    painter.resetTransform();
-    painter.translate(0, m_axisHeight);
-
-    painter.setPen(m_axisBorder);
-    painter.setBrush(m_axisBackground);
-    painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
-
-    painter.setPen(m_axisForeground);
-    painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
-
-    painter.setPen(m_axisBorder);
-    painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
-
-
-    /* Draw the "GPU" axis label */
-    painter.translate(0, m_rowHeight);
-
-    painter.setPen(m_axisBorder);
-    painter.setBrush(m_axisBackground);
-    painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
-
-    painter.setPen(m_axisForeground);
-    painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "GPU");
-
-    painter.setPen(m_axisBorder);
-    painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
-
-
-    /* Draw the frame numbers */
-    painter.resetTransform();
-
-    painter.setPen(m_axisForeground);
-    painter.translate(m_axisWidth, 0);
-
-    int lastLabel = -999; /* Ensure first label gets drawn */
-
-    double scroll = m_time;
-    scroll /= m_timeWidth;
-    scroll *= m_viewWidth;
-
-    for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
-        static const int padding = 4;
-        const Frame& frame = *itr;
-        bool draw = true;
-        int width;
-
-        if (frame.cpuStart > m_timeEnd) {
-            break;
-        }
-
-        if (frame.cpuStart + frame.cpuDuration < m_time) {
-            draw = false;
-        }
-
-        double left = frame.cpuStart;
-        left /= m_timeWidth;
-        left *= m_viewWidth;
-
-        double right = frame.cpuStart + frame.cpuDuration;
-        right /= m_timeWidth;
-        right *= m_viewWidth;
-
-        QString text = QString("%1").arg(frame.no);
-
-        width = painter.fontMetrics().width(text) + padding * 2;
-
-        if (left + width > scroll)
-            draw = true;
-
-        /* Draw a frame number if we have space since the last one */
-        if (left - lastLabel > width) {
-            lastLabel = left + width;
-
-            if (draw) {
-                int textX;
-                painter.setPen(m_axisForeground);
-
-                if (left < scroll && right - left > width) {
-                    if (right - scroll > width) {
-                        textX = 0;
-                    } else {
-                        textX = right - scroll - width;
-                    }
-                } else {
-                    textX = left - scroll;
-                }
-
-                /* Draw frame number and major ruler marking */
-                painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
-                painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
-            }
-        } else if (draw) {
-            /* Draw a minor ruler marking */
-            painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
-        }
-    }
-
-
-    /* Draw "Frame" axis label */
-    painter.resetTransform();
-
-    painter.setPen(m_axisBorder);
-    painter.setBrush(m_axisBackground);
-    painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
-
-    painter.setPen(m_axisForeground);
-    painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
-
-
-    /* Draw the active selection border */
-    if (m_selection.type == SelectTime) {
-        painter.setPen(m_selectionBorder);
-
-        m_selectionLeft += m_axisWidth;
-        m_selectionRight += m_axisWidth;
-
-        if (m_selectionLeft >= m_axisWidth && m_selectionLeft < widgetWidth) {
-            painter.drawLine(m_selectionLeft, 0, m_selectionLeft, widgetHeight);
-        }
-
-        if (m_selectionRight >= m_axisWidth && m_selectionRight < widgetWidth) {
-            painter.drawLine(m_selectionRight, 0, m_selectionRight, widgetHeight);
-        }
-
-        m_selectionLeft = qBound(m_axisWidth, m_selectionLeft, widgetWidth);
-        m_selectionRight = qBound(m_axisWidth, m_selectionRight, widgetWidth);
-
-        painter.drawLine(m_selectionLeft, m_axisHeight - 1, m_selectionRight, m_axisHeight - 1);
-        painter.fillRect(m_selectionLeft, 0, m_selectionRight - m_selectionLeft, widgetHeight, m_selectionBackground);
-    }
-
-
-    /* Draw the ruler zoom */
-    if (m_mousePressMode == RulerZoom) {
-        int x1 = m_mousePressPosition.x();
-        int x2 = qMax(m_mousePosition.x(), m_axisWidth);
-
-        painter.setPen(m_zoomBorder);
-        painter.drawLine(x1, 0, x1, widgetHeight);
-        painter.drawLine(x2, 0, x2, widgetHeight);
-        painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
-        painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
-    }
-}
-
-#include "timelinewidget.moc"
diff --git a/gui/timelinewidget.h b/gui/timelinewidget.h
deleted file mode 100644 (file)
index ba42a10..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-#ifndef TIMELINEWIDGET_H
-#define TIMELINEWIDGET_H
-
-#include <QWidget>
-#include <QList>
-#include <QPen>
-#include "trace_profiler.hpp"
-
-class TimelineWidget : public QWidget
-{
-    Q_OBJECT
-
-    enum MousePressMode {
-        NoMousePress,
-        DragView,
-        RulerZoom,
-        RulerSelect
-    };
-
-    enum SelectType {
-        SelectNone,
-        SelectTime,
-        SelectProgram
-    };
-
-public:
-    TimelineWidget(QWidget *parent = 0);
-
-    void setProfile(trace::Profile* profile);
-
-    void selectNone(bool notify = false);
-    void selectProgram(unsigned program, bool notify = false);
-    void selectTime(int64_t start, int64_t end, bool notify = false);
-
-protected:
-    virtual void wheelEvent(QWheelEvent *e);
-    virtual void mousePressEvent(QMouseEvent *e);
-    virtual void mouseMoveEvent(QMouseEvent *e);
-    virtual void mouseReleaseEvent(QMouseEvent *e);
-    virtual void mouseDoubleClickEvent(QMouseEvent *e);
-
-    virtual void paintEvent(QPaintEvent *e);
-    virtual void resizeEvent(QResizeEvent *e);
-
-public slots:
-    void setHorizontalScrollValue(int value);
-    void setVerticalScrollValue(int value);
-
-signals:
-    void verticalScrollMaxChanged(int max);
-    void verticalScrollValueChanged(int value);
-
-    void horizontalScrollMaxChanged(int max);
-    void horizontalScrollValueChanged(int value);
-
-    void jumpToCall(int call);
-
-    void selectedNone();
-    void selectedProgram(unsigned program);
-    void selectedTime(int64_t start, int64_t end);
-
-private:
-    void setRowScroll(int position, bool notify = true);
-    void setTimeScroll(int64_t time, bool notify = true);
-
-    bool drawCall(QPainter& painter, const trace::Profile::Call& call, int &lastX, int64_t &heat, bool gpu);
-    void drawHeat(QPainter& painter, int x, int64_t heat, bool gpu, bool selected);
-
-    double timeToPosition(int64_t time);
-    int64_t positionToTime(int pos);
-
-    void calculateRows();
-
-    const trace::Profile::Frame* frameAtTime(int64_t time);
-    const trace::Profile::Call* cpuCallAtTime(int64_t time);
-    const trace::Profile::Call* drawCallAtTime(int64_t time);
-    const trace::Profile::Call* drawCallAtTime(int64_t time, int program);
-
-private:
-    /* Data */
-    trace::Profile* m_profile;
-    std::vector<int> m_rowPrograms;
-
-    /* Scrollbars */
-    int m_scrollX;
-    int m_scrollY;
-    int m_maxScrollX;
-    int m_maxScrollY;
-
-    /* Viewport */
-    int m_viewWidth;
-    int m_viewHeight;
-
-    /* Visible Times */
-    int64_t m_time;
-    int64_t m_timeEnd;
-    int64_t m_timeMin;
-    int64_t m_timeMax;
-    int64_t m_timeWidth;
-    int64_t m_timeWidthMin;
-    int64_t m_timeWidthMax;
-
-    int m_selectionLeft;
-    int m_selectionRight;
-
-    /* Visible Rows */
-    int m_row;
-    int m_rowCount;
-
-    /* Mouse data */
-    int m_mousePressRow;
-    int64_t m_mousePressTime;
-    QPoint m_mousePosition;
-    QPoint m_mousePressPosition;
-    MousePressMode m_mousePressMode;
-
-    /* Style */
-    int m_rowHeight;
-    int m_axisWidth;
-    int m_axisHeight;
-    QPen m_axisLine;
-    QPen m_axisBorder;
-    QPen m_axisForeground;
-    QBrush m_axisBackground;
-    QPen m_itemBorder;
-    QPen m_itemGpuForeground;
-    QBrush m_itemGpuBackground;
-    QPen m_itemCpuForeground;
-    QBrush m_itemCpuBackground;
-    QPen m_itemDeselectedForeground;
-    QBrush m_itemDeselectedBackground;
-    QPen m_selectionBorder;
-    QBrush m_selectionBackground;
-    QPen m_zoomBorder;
-    QBrush m_zoomBackground;
-
-    /* Selection */
-    struct {
-        SelectType type;
-
-        unsigned program;
-
-        int64_t timeStart;
-        int64_t timeEnd;
-    } m_selection;
-};
-
-#endif // TIMELINEWIDGET_H
index 7a54ef72595e8c68b964c98b8e5de9636422c6c7..bbcfbe96cf84b243ec2e8d3d81712dc963072607 100644 (file)
@@ -31,7 +31,7 @@
      <property name="opaqueResize">
       <bool>false</bool>
      </property>
-     <widget class="QWidget" name="timelineContainer" native="true">
+     <widget class="GraphWidget" name="m_timeline" native="true">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
         <horstretch>0</horstretch>
        <property name="margin">
         <number>0</number>
        </property>
-       <item>
-        <layout class="QGridLayout" name="gridLayout">
-         <property name="spacing">
-          <number>2</number>
-         </property>
-         <item row="0" column="0">
-          <widget class="TimelineWidget" name="m_timeline" native="true">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="focusPolicy">
-            <enum>Qt::WheelFocus</enum>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="1">
-          <widget class="QScrollBar" name="m_verticalScrollBar">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="orientation">
-            <enum>Qt::Vertical</enum>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QScrollBar" name="m_horizontalScrollBar">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="maximum">
-            <number>10000</number>
-           </property>
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
       </layout>
      </widget>
      <widget class="QWidget" name="graphContainer" native="true">
         <number>0</number>
        </property>
        <item>
-        <widget class="GraphWidget" name="m_gpuGraph" native="true">
+        <widget class="CallDurationGraph" name="m_gpuGraph" native="true">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
            <horstretch>0</horstretch>
         </widget>
        </item>
        <item>
-        <widget class="GraphWidget" name="m_cpuGraph" native="true">
+        <widget class="CallDurationGraph" name="m_cpuGraph" native="true">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
            <horstretch>0</horstretch>
  </widget>
  <customwidgets>
   <customwidget>
-   <class>TimelineWidget</class>
+   <class>GraphWidget</class>
    <extends>QWidget</extends>
-   <header>timelinewidget.h</header>
+   <header>graphing/graphwidget.h</header>
    <container>1</container>
-   <slots>
-    <signal>horizontalScrollValueChanged(int)</signal>
-    <signal>verticalScrollValueChanged(int)</signal>
-    <signal>horizontalScrollMaxChanged(int)</signal>
-    <signal>verticalScrollMaxChanged(int)</signal>
-    <signal>jumpToCall(int)</signal>
-    <signal>selectedTime(int64_t,int64_t)</signal>
-    <signal>selectedProgram(unsigned)</signal>
-    <signal>selectedNone()</signal>
-    <slot>setHorizontalScrollValue(int)</slot>
-    <slot>setVerticalScrollValue(int)</slot>
-   </slots>
   </customwidget>
   <customwidget>
-   <class>GraphWidget</class>
+   <class>CallDurationGraph</class>
    <extends>QWidget</extends>
-   <header>graphwidget.h</header>
+   <header>calldurationgraph.h</header>
    <container>1</container>
-   <slots>
-    <signal>viewChanged(int,int)</signal>
-    <signal>selectedTime(int64_t,int64_t)</signal>
-    <signal>selectedProgram(unsigned)</signal>
-    <signal>selectedNone()</signal>
-    <slot>changeView(int,int)</slot>
-   </slots>
   </customwidget>
  </customwidgets>
  <resources/>
  <connections>
-  <connection>
-   <sender>m_horizontalScrollBar</sender>
-   <signal>sliderMoved(int)</signal>
-   <receiver>m_timeline</receiver>
-   <slot>setHorizontalScrollValue(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>373</x>
-     <y>434</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_verticalScrollBar</sender>
-   <signal>sliderMoved(int)</signal>
-   <receiver>m_timeline</receiver>
-   <slot>setVerticalScrollValue(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>754</x>
-     <y>213</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>horizontalScrollValueChanged(int)</signal>
-   <receiver>m_horizontalScrollBar</receiver>
-   <slot>setValue(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>373</x>
-     <y>434</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>verticalScrollValueChanged(int)</signal>
-   <receiver>m_verticalScrollBar</receiver>
-   <slot>setValue(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>754</x>
-     <y>213</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>horizontalScrollMaxChanged(int)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>setHorizontalScrollMax(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>511</x>
-     <y>383</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>verticalScrollMaxChanged(int)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>setVerticalScrollMax(int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>373</x>
-     <y>213</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>511</x>
-     <y>383</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_cpuGraph</sender>
-   <signal>viewChanged(int,int)</signal>
-   <receiver>m_gpuGraph</receiver>
-   <slot>changeView(int,int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>511</x>
-     <y>687</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>511</x>
-     <y>527</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_gpuGraph</sender>
-   <signal>viewChanged(int,int)</signal>
-   <receiver>m_cpuGraph</receiver>
-   <slot>changeView(int,int)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>511</x>
-     <y>527</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>511</x>
-     <y>687</y>
-    </hint>
-   </hints>
-  </connection>
   <connection>
    <sender>m_table</sender>
    <signal>doubleClicked(QModelIndex)</signal>
     </hint>
    </hints>
   </connection>
-  <connection>
-   <sender>m_cpuGraph</sender>
-   <signal>selectedTime(int64_t,int64_t)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectTime(int64_t,int64_t)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>555</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_gpuGraph</sender>
-   <signal>selectedTime(int64_t,int64_t)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectTime(int64_t,int64_t)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>455</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>selectedTime(int64_t,int64_t)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectTime(int64_t,int64_t)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>544</x>
-     <y>192</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_cpuGraph</sender>
-   <signal>selectedProgram(unsigned)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectProgram(unsigned)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>555</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_gpuGraph</sender>
-   <signal>selectedProgram(unsigned)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectProgram(unsigned)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>455</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>selectedProgram(unsigned)</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectProgram(unsigned)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>544</x>
-     <y>192</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_cpuGraph</sender>
-   <signal>selectedNone()</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectNone()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>555</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_gpuGraph</sender>
-   <signal>selectedNone()</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectNone()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>552</x>
-     <y>455</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>m_timeline</sender>
-   <signal>selectedNone()</signal>
-   <receiver>ProfileDialog</receiver>
-   <slot>selectNone()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>544</x>
-     <y>192</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>552</x>
-     <y>401</y>
-    </hint>
-   </hints>
-  </connection>
  </connections>
  <slots>
   <signal>jumpToCall(int)</signal>