]> git.cworth.org Git - apitrace/blob - gui/timelinewidget.cpp
d9f1b5809ebf63246dba4c4c7f7cb2aeb53c9bca
[apitrace] / gui / timelinewidget.cpp
1 #include "timelinewidget.h"
2 #include "trace_profiler.hpp"
3
4 #include <math.h>
5 #include <QColor>
6 #include <QPainter>
7 #include <QToolTip>
8 #include <QMouseEvent>
9 #include <QWheelEvent>
10 #include <QApplication>
11
12 typedef trace::Profile::Frame Frame;
13 typedef trace::Profile::Program Program;
14 typedef trace::Profile::CpuCall CpuCall;
15 typedef trace::Profile::DrawCall DrawCall;
16
17 TimelineWidget::TimelineWidget(QWidget *parent)
18     : QWidget(parent),
19       m_profile(NULL),
20       m_timeSelectionStart(0),
21       m_timeSelectionEnd(0),
22       m_rowHeight(20),
23       m_axisWidth(50),
24       m_axisHeight(30),
25       m_axisLine(QColor(240, 240, 240)),
26       m_axisBorder(Qt::black),
27       m_axisForeground(Qt::black),
28       m_axisBackground(QColor(210, 210, 210)),
29       m_itemBorder(Qt::red),
30       m_itemForeground(Qt::cyan),
31       m_itemBackground(Qt::red),
32       m_selectionBorder(Qt::green),
33       m_selectionBackground(QColor(100, 255, 100, 8)),
34       m_zoomBorder(QColor(255, 0, 255)),
35       m_zoomBackground(QColor(255, 0, 255, 30))
36 {
37     setBackgroundRole(QPalette::Base);
38     setAutoFillBackground(true);
39     setMouseTracking(true);
40 }
41
42
43 /**
44  * Update horizontal view scroll based on scroll value
45  */
46 void TimelineWidget::setHorizontalScrollValue(int scrollValue)
47 {
48     if (!m_profile) {
49         return;
50     }
51
52     /* Calculate time from scroll value */
53     double time = scrollValue;
54     time /= m_maxScrollX;
55     time *= (m_timeMax - m_timeWidth) - m_timeMin;
56     time += m_timeMin;
57
58     setTimeScroll(time, false);
59 }
60
61
62 /**
63  * Update vertical view scroll based on scroll value
64  */
65 void TimelineWidget::setVerticalScrollValue(int value)
66 {
67     if (!m_profile) {
68         return;
69     }
70
71     setRowScroll(value, false);
72 }
73
74
75 /**
76  * Update the time selection
77  */
78 void TimelineWidget::setSelection(int64_t start, int64_t end, bool notify)
79 {
80     m_timeSelectionStart = start;
81     m_timeSelectionEnd = end;
82
83     if (notify) {
84         emit selectionChanged(m_timeSelectionStart, m_timeSelectionEnd);
85     }
86
87     update();
88 }
89
90
91 /**
92  * Convert time to view position
93  */
94 double TimelineWidget::timeToPosition(int64_t time)
95 {
96     double pos = time;
97     pos -= m_time;
98     pos /= m_timeWidth;
99     pos *= m_viewWidth;
100     return pos;
101 }
102
103
104 /**
105  * Convert view position to time
106  */
107 int64_t TimelineWidget::positionToTime(int pos)
108 {
109     double time = pos;
110     time /= m_viewWidth;
111     time *= m_timeWidth;
112     time += m_time;
113     return (int64_t)time;
114 }
115
116
117 /**
118  * Binary Search for a time in start+durations
119  */
120 template<typename val_ty, int64_t val_ty::* mem_ptr_start, int64_t val_ty::* mem_ptr_dura>
121 typename std::vector<val_ty>::const_iterator binarySearchTimespan(
122         typename std::vector<val_ty>::const_iterator begin,
123         typename std::vector<val_ty>::const_iterator end,
124         int64_t time)
125 {
126     int lower = 0;
127     int upper = end - begin;
128     int pos = (lower + upper) / 2;
129     typename std::vector<val_ty>::const_iterator itr = begin + pos;
130
131     while (!((*itr).*mem_ptr_start <= time && (*itr).*mem_ptr_start + (*itr).*mem_ptr_dura > time) && (lower <= upper)) {
132         if ((*itr).*mem_ptr_start > time) {
133             upper = pos - 1;
134         } else {
135             lower = pos + 1;
136         }
137
138         pos = (lower + upper) / 2;
139         itr = begin + pos;
140     }
141
142     if (lower <= upper)
143         return itr;
144     else
145         return end;
146 }
147
148
149 /**
150  * Find the frame at time
151  */
152 const Frame* TimelineWidget::frameAtTime(int64_t time) {
153     if (!m_profile) {
154         return NULL;
155     }
156
157     std::vector<Frame>::const_iterator res
158             = binarySearchTimespan<Frame, &Frame::cpuStart, &Frame::cpuDuration>(
159                 m_profile->frames.begin(),
160                 m_profile->frames.end(),
161                 time);
162
163     if (res != m_profile->frames.end()) {
164         const Frame& frame = *res;
165         return &frame;
166     }
167
168     return NULL;
169 }
170
171
172 /**
173  * Find the CPU call at time
174  */
175 const CpuCall* TimelineWidget::cpuCallAtTime(int64_t time) {
176     if (!m_profile) {
177         return NULL;
178     }
179
180     std::vector<CpuCall>::const_iterator res
181             = binarySearchTimespan<CpuCall, &CpuCall::cpuStart, &CpuCall::cpuDuration>(
182                 m_profile->cpuCalls.begin(),
183                 m_profile->cpuCalls.end(),
184                 time);
185
186     if (res != m_profile->cpuCalls.end()) {
187         const CpuCall& call = *res;
188         return &call;
189     }
190
191     return NULL;
192 }
193
194
195 /**
196  * Find the draw call at time
197  */
198 const DrawCall* TimelineWidget::drawCallAtTime(int program, int64_t time) {
199     if (!m_profile) {
200         return NULL;
201     }
202
203     std::vector<DrawCall>& drawCalls = m_profile->programs[program].drawCalls;
204
205     std::vector<DrawCall>::const_iterator res
206             = binarySearchTimespan<DrawCall, &DrawCall::gpuStart, &DrawCall::gpuDuration>(
207                 drawCalls.begin(),
208                 drawCalls.end(),
209                 time);
210
211     if (res != drawCalls.end()) {
212         const DrawCall& call = *res;
213         return &call;
214     }
215
216     return NULL;
217 }
218
219
220 /**
221  * Calculate the row order by total gpu time per shader
222  */
223 void TimelineWidget::calculateRows()
224 {
225     typedef QPair<uint64_t, unsigned> Pair;
226     std::vector<Pair> gpu;
227
228     /* Map shader to visible row */
229     for (std::vector<Program>::const_iterator itr = m_profile->programs.begin(); itr != m_profile->programs.end(); ++itr) {
230         const Program& program = *itr;
231         unsigned no = itr -  m_profile->programs.begin();
232
233         if (program.gpuTotal > 0) {
234             gpu.push_back(Pair(program.gpuTotal, no));
235         }
236     }
237
238     /* Sort the shaders by most used gpu */
239     qSort(gpu);
240
241     /* Create row order */
242     m_rowPrograms.clear();
243
244     for (std::vector<Pair>::const_reverse_iterator itr = gpu.rbegin(); itr != gpu.rend(); ++itr) {
245         m_rowPrograms.push_back(itr->second);
246     }
247
248     m_rowCount = m_rowPrograms.size();
249 }
250
251
252 /**
253  * Set the trace profile to use for the timeline
254  */
255 void TimelineWidget::setProfile(trace::Profile* profile)
256 {
257     if (!profile->frames.size())
258         return;
259
260     m_profile = profile;
261     calculateRows();
262
263     m_timeMin = m_profile->frames.front().cpuStart;
264     m_timeMax = m_profile->frames.back().cpuStart + m_profile->frames.back().cpuDuration;
265
266     m_time = m_timeMin;
267     m_timeWidth = m_timeMax - m_timeMin;
268
269     m_timeWidthMin = 1000;
270     m_timeWidthMax = m_timeWidth;
271
272     m_maxScrollX = 0;
273     m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
274
275     setTimeScroll(m_time);
276     setRowScroll(0);
277
278     update();
279 }
280
281
282 /**
283  * Set the horizontal scroll position to time
284  */
285 void TimelineWidget::setTimeScroll(int64_t time, bool notify)
286 {
287     time = qBound(m_timeMin, time, m_timeMax - m_timeWidth);
288
289     m_time = time;
290
291     if (m_timeWidth == m_timeWidthMax) {
292         m_maxScrollX = 0;
293     } else {
294         m_maxScrollX = 10000;
295     }
296
297     if (notify) {
298         double value = time - m_timeMin;
299         value /= m_timeMax - m_timeWidth - m_timeMin;
300         value *= m_maxScrollX;
301         m_scrollX = value;
302
303         emit horizontalScrollMaxChanged(m_maxScrollX);
304         emit horizontalScrollValueChanged(m_scrollX);
305     }
306
307     update();
308 }
309
310
311 /**
312  * Set the vertical scroll position to position
313  */
314 void TimelineWidget::setRowScroll(int position, bool notify)
315 {
316     position = qBound(0, position, m_maxScrollY);
317
318     m_scrollY = position;
319     m_row  = m_scrollY / m_rowHeight;
320
321     if (notify) {
322         emit verticalScrollMaxChanged(m_maxScrollY);
323         emit verticalScrollValueChanged(m_scrollY);
324     }
325
326     update();
327 }
328
329
330 void TimelineWidget::resizeEvent(QResizeEvent *e)
331 {
332     /* Update viewport size */
333     m_viewWidth = qMax(0, width() - m_axisWidth);
334     m_viewHeight = qMax(0, height() - m_axisHeight - m_rowHeight);
335
336     /* Update vertical scroll bar */
337     if (m_profile) {
338         m_maxScrollY = qMax(0, (m_rowCount * m_rowHeight) - m_viewHeight);
339         emit verticalScrollMaxChanged(m_maxScrollY);
340         setRowScroll(m_scrollY);
341     }
342 }
343
344
345 void TimelineWidget::mouseMoveEvent(QMouseEvent *e)
346 {
347     bool tooltip = false;
348     m_mousePosition = e->pos();
349
350     if (!m_profile) {
351         return;
352     }
353
354     /* Display tooltip if necessary */
355     if (e->buttons() == Qt::NoButton) {
356         if (m_mousePosition.x() > m_axisWidth && m_mousePosition.y() > m_axisHeight) {
357             int64_t time = positionToTime(m_mousePosition.x() - m_axisWidth);
358             int y = m_mousePosition.y() - m_axisHeight;
359
360             if (y < m_rowHeight) {
361                 const CpuCall* call = cpuCallAtTime(time);
362
363                 if (call) {
364                     QString text;
365                     text  = QString::fromStdString(call->name);
366                     text += QString("\nCall: %1").arg(call->no);
367                     text += QString("\nCPU Start: %1").arg(call->cpuStart);
368                     text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
369
370                     QToolTip::showText(e->globalPos(), text);
371                     tooltip = true;
372                 }
373             } else {
374                 int row = (y - m_rowHeight + m_scrollY) / m_rowHeight;
375
376                 if (row < m_rowPrograms.size()) {
377                     int program = m_rowPrograms[row];
378                     const DrawCall* call = drawCallAtTime(program, time);
379
380                     if (call) {
381                         QString text;
382                         text  = QString::fromStdString(call->name);
383                         text += QString("\nCall: %1").arg(call->no);
384                         text += QString("\nGPU Start: %1").arg(call->gpuStart);
385                         text += QString("\nCPU Start: %1").arg(call->cpuStart);
386                         text += QString("\nGPU Duration: %1").arg(call->gpuDuration);
387                         text += QString("\nCPU Duration: %1").arg(call->cpuDuration);
388                         text += QString("\nPixels Drawn: %1").arg(call->pixels);
389
390                         QToolTip::showText(e->globalPos(), text);
391                         tooltip = true;
392                     }
393                 }
394             }
395         }
396     } else if (e->buttons().testFlag(Qt::LeftButton)) {
397         if (m_mousePressMode == DragView) {
398             /* Horizontal scroll */
399             double dt = m_timeWidth;
400             dt /= m_viewWidth;
401             dt *= m_mousePressPosition.x() - e->pos().x();
402             setTimeScroll(m_mousePressTime + dt);
403
404             /* Vertical scroll */
405             int dy = m_mousePressPosition.y() - e->pos().y();
406             setRowScroll(m_mousePressRow + dy);
407         } else if (m_mousePressMode == RulerSelect) {
408             /* Horizontal selection */
409             int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
410             int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
411
412             setSelection(qMin(down, up), qMax(down, up));
413         }
414
415         update();
416     }
417
418     if (!tooltip) {
419         QToolTip::hideText();
420     }
421 }
422
423
424 void TimelineWidget::mousePressEvent(QMouseEvent *e)
425 {
426     if (e->buttons() & Qt::LeftButton) {
427         if (e->pos().y() < m_axisHeight && e->pos().x() >= m_axisWidth) {
428             if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
429                 m_mousePressMode = RulerZoom;
430             } else {
431                 m_mousePressMode = RulerSelect;
432
433                 int64_t time = positionToTime(e->pos().x() - m_axisWidth);
434                 m_timeSelectionStart = time;
435                 m_timeSelectionEnd = time;
436             }
437         } else {
438             m_mousePressMode = DragView;
439         }
440
441         m_mousePressPosition  = e->pos();
442         m_mousePressTime = m_time;
443         m_mousePressRow   = m_scrollY;
444
445         update();
446     }
447 }
448
449
450 void TimelineWidget::mouseReleaseEvent(QMouseEvent *e)
451 {
452     if (!m_profile) {
453         return;
454     }
455
456     /* Calculate new time view based on selected area */
457     int64_t down  = positionToTime(m_mousePressPosition.x() - m_axisWidth);
458     int64_t up    = positionToTime(qMax(e->pos().x() - m_axisWidth, 0));
459
460     int64_t left  = qMin(down, up);
461     int64_t right = qMax(down, up);
462
463     if (m_mousePressMode == RulerZoom) {
464         m_timeWidth = right - left;
465         m_timeWidth = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
466
467         m_mousePressMode = NoMousePress;
468         setTimeScroll(left);
469     } else if (m_mousePressMode == RulerSelect) {
470         setSelection(m_timeSelectionStart, m_timeSelectionEnd, true);
471     }
472 }
473
474
475 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *e)
476 {
477     int64_t time = positionToTime(e->pos().x() - m_axisWidth);
478
479     if (e->pos().x() > m_axisWidth) {
480         int row = (e->pos().y() - m_axisHeight) / m_rowHeight;
481
482         if (e->pos().y() < m_axisHeight) {
483             /* Horizontal axis */
484             const Frame* frame = frameAtTime(time);
485
486             if (frame) {
487                 setSelection(frame->cpuStart, frame->cpuStart + frame->cpuDuration, true);
488                 return;
489             }
490         } else if (row == 0) {
491             /* CPU Calls */
492             const CpuCall* call = cpuCallAtTime(time);
493
494             if (call) {
495                 emit jumpToCall(call->no);
496                 return;
497             }
498         } else if (row > 0) {
499             /* Draw Calls */
500             int program = m_rowPrograms[row - 1 + m_row];
501             const DrawCall* call = drawCallAtTime(program, time);
502
503             if (call) {
504                 emit jumpToCall(call->no);
505                 return;
506             }
507         }
508     }
509
510     if (time < m_timeSelectionStart || time > m_timeSelectionEnd) {
511         setSelection(0, 0, true);
512     }
513 }
514
515
516 void TimelineWidget::wheelEvent(QWheelEvent *e)
517 {
518     if (!m_profile) {
519         return;
520     }
521
522     int zoomPercent = 10;
523
524     /* If holding Ctrl key then zoom 2x faster */
525     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
526         zoomPercent = 20;
527     }
528
529     /* Zoom view by adjusting width */
530     double dt = m_timeWidth;
531     double size = m_timeWidth;
532     size *= -e->delta();
533
534     /* Zoom deltas normally come in increments of 120 */
535     size /= 120 * (100 / zoomPercent);
536
537     m_timeWidth += size;
538     m_timeWidth  = qBound(m_timeWidthMin, m_timeWidth, m_timeWidthMax);
539
540     /* Scroll view to zoom around mouse */
541     dt -= m_timeWidth;
542     dt *= e->x() - m_axisWidth;
543     dt /= m_viewWidth;
544     setTimeScroll(dt + m_time);
545
546     update();
547 }
548
549
550 /**
551  * Paints a single pixel column of the heat map
552  */
553 void TimelineWidget::drawHeat(QPainter& painter, int x, int64_t heat, bool isCpu)
554 {
555     if (heat == 0) {
556         return;
557     }
558
559     double timePerPixel = m_timeWidth;
560     timePerPixel /= m_viewWidth;
561
562     double colour = heat / timePerPixel;
563     colour = qBound(0.0, colour * 255.0, 255.0);
564
565     if (isCpu) {
566         painter.setPen(QColor(255 - colour, 255 - colour, 255));
567     } else {
568         painter.setPen(QColor(255, 255 - colour, 255 - colour));
569     }
570
571     painter.drawLine(x, 0, x, m_rowHeight - 1);
572 }
573
574
575 /**
576  * Render the whole widget
577  */
578 void TimelineWidget::paintEvent(QPaintEvent *e)
579 {
580     if (!m_profile)
581         return;
582
583     QPainter painter(this);
584
585     int rowEnd = qMin(m_row + (m_viewHeight / m_rowHeight) + 1, m_rowCount);
586     int64_t timeEnd = m_time + m_timeWidth;
587     int64_t heat = 0;
588     int lastX = 0;
589     int widgetHeight = height();
590     int widgetWidth = width();
591
592     /* Draw GPU rows */
593     painter.translate(m_axisWidth, m_axisHeight + m_rowHeight - (m_scrollY % m_rowHeight));
594
595     for (int row = m_row; row < rowEnd; ++row) {
596         Program& program = m_profile->programs[m_rowPrograms[row]];
597         lastX = 0;
598         heat = 0;
599
600         for (std::vector<DrawCall>::const_iterator itr = program.drawCalls.begin(); itr != program.drawCalls.end(); ++itr) {
601             const DrawCall& call = *itr;
602             int64_t gpuEnd = call.gpuStart + call.gpuDuration;
603
604             if (call.gpuStart > timeEnd) {
605                 break;
606             }
607
608             if (gpuEnd < m_time) {
609                 continue;
610             }
611
612             double left  = timeToPosition(call.gpuStart);
613             double right = timeToPosition(gpuEnd);
614
615             int leftX  = left;
616             int rightX = right;
617
618             /* Draw last heat if needed */
619             if (leftX != lastX) {
620                 drawHeat(painter, lastX, heat, false);
621                 lastX = leftX;
622                 heat = 0;
623             }
624
625             if (rightX <= leftX + 1) {
626                 if (rightX == lastX) {
627                     /* Fully contained in this X */
628                     heat += call.gpuDuration;
629                 } else {
630                     /* Split call time between the two pixels it occupies */
631                     int64_t time = positionToTime(rightX);
632
633                     heat += time - call.gpuStart;
634                     drawHeat(painter, lastX, heat, false);
635
636                     heat = gpuEnd - time;
637                     lastX = rightX;
638                 }
639             } else {
640                 QRect rect;
641                 rect.setLeft(left + 0.5);
642                 rect.setWidth(right - left);
643                 rect.setTop(0);
644                 rect.setHeight(m_rowHeight);
645
646                 painter.fillRect(rect, m_itemBackground);
647
648                 if (rect.width() > 6) {
649                     rect.adjust(1, 0, -1, -2);
650                     painter.setPen(m_itemForeground);
651
652                     painter.drawText(rect,
653                                      Qt::AlignLeft | Qt::AlignVCenter,
654                                      painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
655                 }
656             }
657         }
658
659         painter.translate(0, m_rowHeight);
660     }
661
662     /* Draw CPU row */
663     painter.resetTransform();
664     painter.translate(m_axisWidth, m_axisHeight);
665     painter.fillRect(0, 0, m_viewWidth, m_rowHeight, Qt::white);
666
667     for (std::vector<CpuCall>::const_iterator itr = m_profile->cpuCalls.begin(); itr != m_profile->cpuCalls.end(); ++itr) {
668         const CpuCall& call = *itr;
669         int64_t cpuEnd = call.cpuStart + call.cpuDuration;
670
671         if (call.cpuStart > timeEnd) {
672             continue;
673         }
674
675         if (cpuEnd < m_time) {
676             continue;
677         }
678
679         double left = timeToPosition(call.cpuStart);
680         double right = timeToPosition(cpuEnd);
681
682         int leftX = left;
683         int rightX = right;
684
685         /* Draw last heat if needed */
686         if (leftX != lastX) {
687             drawHeat(painter, lastX, heat, true);
688             lastX = leftX;
689             heat = 0;
690         }
691
692         if (rightX <= leftX + 1) {
693             if (rightX == lastX) {
694                 /* Fully contained in this X */
695                 heat += call.cpuDuration;
696             } else {
697                 /* Split call time between the two pixels it occupies */
698                 int64_t time = positionToTime(rightX);
699
700                 heat += time - call.cpuStart;
701                 drawHeat(painter, lastX, heat, true);
702
703                 heat = cpuEnd - time;
704                 lastX = rightX;
705             }
706         } else {
707             QRect rect;
708             rect.setLeft(left + 0.5);
709             rect.setWidth(right - left);
710             rect.setTop(0);
711             rect.setHeight(m_rowHeight);
712
713             painter.fillRect(rect, QColor(0, 0, 255));
714
715             if (rect.width() > 6) {
716                 rect.adjust(1, 0, -1, -2);
717                 painter.setPen(QColor(255, 255, 0));
718
719                 painter.drawText(rect,
720                                  Qt::AlignLeft | Qt::AlignVCenter,
721                                  painter.fontMetrics().elidedText(QString::fromStdString(call.name), Qt::ElideRight, rect.width()));
722             }
723         }
724     }
725
726     /* Draw axis */
727     painter.resetTransform();
728     painter.setPen(m_axisBorder);
729
730     /* Top Rect */
731     painter.fillRect(m_axisWidth - 1, 0, widgetWidth, m_axisHeight - 1, m_axisBackground);
732     painter.drawLine(0, m_axisHeight - 1, widgetWidth, m_axisHeight - 1);
733
734     /* Left Rect */
735     painter.fillRect(0, m_axisHeight - 1, m_axisWidth - 1, widgetHeight, m_axisBackground);
736     painter.drawLine(m_axisWidth - 1, 0, m_axisWidth - 1, widgetHeight);
737
738     /* Draw the program numbers */
739     painter.translate(0, m_axisHeight + m_rowHeight);
740
741     for (int row = m_row; row < rowEnd; ++row) {
742         int y = (row - m_row) * m_rowHeight - (m_scrollY % m_rowHeight);
743
744         painter.setPen(m_axisForeground);
745         painter.drawText(0, y, m_axisWidth, m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(m_rowPrograms[row]));
746
747         painter.setPen(m_axisBorder);
748         painter.drawLine(0, y + m_rowHeight - 1, m_axisWidth - 1, y + m_rowHeight - 1);
749
750         painter.setPen(m_axisLine);
751         painter.drawLine(m_axisWidth, y + m_rowHeight - 1, widgetWidth, y + m_rowHeight - 1);
752     }
753
754     /* Draw the "CPU" axis label */
755     painter.resetTransform();
756     painter.translate(0, m_axisHeight);
757
758     painter.setPen(m_axisBorder);
759     painter.setBrush(m_axisBackground);
760     painter.drawRect(-1, -1, m_axisWidth, m_rowHeight);
761
762     painter.setPen(m_axisForeground);
763     painter.drawText(0, 0, m_axisWidth - 1, m_rowHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "CPU");
764
765     painter.setPen(m_axisBorder);
766     painter.drawLine(m_axisWidth, m_rowHeight - 1, widgetWidth, m_rowHeight - 1);
767
768
769     /* Draw the frame numbers */
770     painter.resetTransform();
771
772     painter.setPen(m_axisForeground);
773     painter.translate(m_axisWidth, 0);
774
775     int lastLabel = -9999;
776
777     double scroll = m_time;
778     scroll /= m_timeWidth;
779     scroll *= m_viewWidth;
780
781     for (std::vector<Frame>::const_iterator itr = m_profile->frames.begin(); itr != m_profile->frames.end(); ++itr) {
782         static const int padding = 4;
783         const Frame& frame = *itr;
784         bool draw = true;
785         int width;
786
787         if (frame.cpuStart > timeEnd) {
788             break;
789         }
790
791         if (frame.cpuStart + frame.cpuDuration < m_time) {
792             draw = false;
793         }
794
795         double left = frame.cpuStart;
796         left /= m_timeWidth;
797         left *= m_viewWidth;
798
799         double right = frame.cpuStart + frame.cpuDuration;
800         right /= m_timeWidth;
801         right *= m_viewWidth;
802
803         QString text = QString("%1").arg(frame.no);
804
805         width = painter.fontMetrics().width(text) + padding * 2;
806
807         if (left + width > scroll)
808             draw = true;
809
810         /* Draw a frame number if we have space since the last one */
811         if (left - lastLabel > width) {
812             lastLabel = left + width;
813
814             if (draw) {
815                 int textX;
816                 painter.setPen(m_axisForeground);
817
818                 if (left < scroll && right - left > width) {
819                     if (right - scroll > width) {
820                         textX = 0;
821                     } else {
822                         textX = right - scroll - width;
823                     }
824                 } else {
825                     textX = left - scroll;
826                 }
827
828                 /* Draw frame number and major ruler marking */
829                 painter.drawText(textX + padding, 0, width - padding, m_axisHeight - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
830                 painter.drawLine(left - scroll, m_axisHeight / 2, left - scroll, m_axisHeight - 1);
831             }
832         } else if (draw) {
833             /* Draw a minor ruler marking */
834             painter.drawLine(left - scroll, m_axisHeight - (m_axisHeight / 4), left - scroll, m_axisHeight - 1);
835         }
836     }
837
838     /* Draw "Frame" axis label */
839     painter.resetTransform();
840
841     painter.setPen(m_axisBorder);
842     painter.setBrush(m_axisBackground);
843     painter.drawRect(-1, -1, m_axisWidth, m_axisHeight);
844
845     painter.setPen(m_axisForeground);
846     painter.drawText(0, 0, m_axisWidth - 1, m_axisHeight - 1, Qt::AlignHCenter | Qt::AlignVCenter, "Frame");
847
848     /* Draw the active selection border */
849     if (m_timeSelectionStart != m_timeSelectionEnd) {
850         int selectionLeft  = timeToPosition(m_timeSelectionStart) + m_axisWidth;
851         int selectionRight = (timeToPosition(m_timeSelectionEnd) + 0.5) + m_axisWidth;
852
853         painter.setPen(m_selectionBorder);
854
855         if (selectionLeft >= m_axisWidth && selectionLeft < widgetWidth) {
856             painter.drawLine(selectionLeft, 0, selectionLeft, widgetHeight);
857         }
858
859         if (selectionRight >= m_axisWidth && selectionRight < widgetWidth) {
860             painter.drawLine(selectionRight, 0, selectionRight, widgetHeight);
861         }
862
863         selectionLeft = qBound(m_axisWidth, selectionLeft, widgetWidth);
864         selectionRight = qBound(m_axisWidth, selectionRight, widgetWidth);
865
866         painter.drawLine(selectionLeft, m_axisHeight - 1, selectionRight, m_axisHeight - 1);
867         painter.fillRect(selectionLeft, 0, selectionRight - selectionLeft, widgetHeight, m_selectionBackground);
868     }
869
870     /* Draw the ruler zoom */
871     if (m_mousePressMode == RulerZoom) {
872         int x1 = m_mousePressPosition.x();
873         int x2 = qMax(m_mousePosition.x(), m_axisWidth);
874
875         painter.setPen(m_zoomBorder);
876         painter.drawLine(x1, 0, x1, widgetHeight);
877         painter.drawLine(x2, 0, x2, widgetHeight);
878         painter.drawLine(x1, m_axisHeight - 1, x2, m_axisHeight - 1);
879         painter.fillRect(x1, m_axisHeight, x2 - x1, widgetHeight, m_zoomBackground);
880     }
881 }
882
883 #include "timelinewidget.moc"