Vidalia  0.3.1
GraphFrame.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file GraphFrame.cpp
13 ** \brief Graphs a series of send and receive data points
14 */
15 
16 #include "GraphFrame.h"
17 
18 #include <QtGui>
19 
20 
21 /** Default contructor */
22 GraphFrame::GraphFrame(QWidget *parent)
23 : QFrame(parent)
24 {
25  /* Create Graph Frame related objects */
26  _recvData = new QList<qreal>();
27  _sendData = new QList<qreal>();
28  _painter = new QPainter();
30 
31  /* Initialize graph values */
32  _recvData->prepend(0);
33  _sendData->prepend(0);
35  _maxPosition = 0;
36  _showRecv = true;
37  _showSend = true;
39  _scaleWidth = 0;
40 }
41 
42 /** Default destructor */
44 {
45  delete _painter;
46  delete _recvData;
47  delete _sendData;
48 }
49 
50 /** Gets the width of the desktop, which is the maximum number of points
51  * we can plot in the graph. */
52 int
54 {
55  return size().width() - _scaleWidth;
56 }
57 
58 /** Adds new data points to the graph. */
59 void
60 GraphFrame::addPoints(qreal recv, qreal send)
61 {
62  /* If maximum number of points plotted, remove oldest */
63  if (_sendData->size() == _maxPoints) {
64  _sendData->removeLast();
65  _recvData->removeLast();
66  }
67 
68  /* Update the displayed maximum */
69  if (_maxPosition >= _maxPoints) {
71  foreach(qreal send, *_sendData)
72  if(send > _maxValue)
73  _maxValue = send;
74  foreach(qreal recv, *_recvData)
75  if(recv > _maxValue)
76  _maxValue = recv;
77  _maxPosition = 0;
78  }
79 
80  /* Add the points to their respective lists */
81  _sendData->prepend(send);
82  _recvData->prepend(recv);
83 
84  /* Add to the total counters */
85  _totalSend += send;
86  _totalRecv += recv;
87 
88  bool maxUpdated = false;
89  /* Check for a new maximum value */
90  if (send > _maxValue) {
91  _maxValue = send;
92  maxUpdated = true;
93  }
94 
95  if (recv > _maxValue) {
96  _maxValue = recv;
97  maxUpdated = true;
98  }
99 
100  if (maxUpdated) {
101  _maxPosition = 0;
102  } else {
103  _maxPosition++;
104  }
105 
106  this->update();
107 }
108 
109 /** Clears the graph. */
110 void
112 {
113  _recvData->clear();
114  _sendData->clear();
115  _recvData->prepend(0);
116  _sendData->prepend(0);
118  _totalSend = 0;
119  _totalRecv = 0;
120  this->update();
121 }
122 
123 /** Toggles display of respective graph lines and counters. */
124 void
125 GraphFrame::setShowCounters(bool showRecv, bool showSend)
126 {
127  _showRecv = showRecv;
128  _showSend = showSend;
129  this->update();
130 }
131 
132 /** Overloads default QWidget::paintEvent. Draws the actual
133  * bandwidth graph. */
134 void
135 GraphFrame::paintEvent(QPaintEvent *event)
136 {
137  Q_UNUSED(event);
138 
139  /* Set current graph dimensions */
140  _rec = this->frameRect();
141 
142  /* Start the painter */
143  _painter->begin(this);
144 
145  /* We want antialiased lines and text */
146  _painter->setRenderHint(QPainter::Antialiasing);
147  _painter->setRenderHint(QPainter::TextAntialiasing);
148 
149  /* Fill in the background */
150  _painter->fillRect(_rec, QBrush(BACK_COLOR));
151  _painter->drawRect(_rec);
152 
153  /* Paint the scale */
154  paintScale();
155  /* Plot the send/receive data */
156  paintData();
157  /* Paint the send/recv totals */
158  paintTotals();
159 
160  /* Stop the painter */
161  _painter->end();
162 }
163 
164 /** Paints an integral and an outline of that integral for each data set (send
165  * and/or receive) that is to be displayed. The integrals will be drawn first,
166  * followed by the outlines, since we want the area of overlapping integrals
167  * to blend, but not the outlines of those integrals. */
168 void
170 {
171  QVector<QPointF> recvPoints, sendPoints;
172 
173  /* Convert the bandwidth data points to graph points */
174  recvPoints = pointsFromData(_recvData);
175  sendPoints = pointsFromData(_sendData);
176 
177  if (_graphStyle == AreaGraph) {
178  /* Plot the bandwidth data as area graphs */
179  if (_showRecv)
180  paintIntegral(recvPoints, RECV_COLOR, 0.6);
181  if (_showSend)
182  paintIntegral(sendPoints, SEND_COLOR, 0.4);
183  }
184 
185  /* Plot the bandwidth as solid lines. If the graph style is currently an
186  * area graph, we end up outlining the integrals. */
187  if (_showRecv)
188  paintLine(recvPoints, RECV_COLOR);
189  if (_showSend)
190  paintLine(sendPoints, SEND_COLOR);
191 }
192 
193 /** Returns a list of points on the bandwidth graph based on the supplied set
194  * of send or receive values. */
195 QVector<QPointF>
196 GraphFrame::pointsFromData(QList<qreal>* list)
197 {
198  QVector<QPointF> points;
199  int x = _rec.width();
200  int y = _rec.height();
201  qreal scale = (y - (y/10)) / _maxValue;
202  qreal currValue;
203 
204  /* Translate all data points to points on the graph frame */
205  points << QPointF(x, y);
206  for (int i = 0; i < list->size(); i++) {
207  currValue = y - (list->at(i) * scale);
208  if (x - SCROLL_STEP < _scaleWidth) {
209  points << QPointF(_scaleWidth, currValue);
210  break;
211  }
212  points << QPointF(x, currValue);
213  x -= SCROLL_STEP;
214  }
215  points << QPointF(_scaleWidth, y);
216  return points;
217 }
218 
219 /** Plots an integral using the data points in <b>points</b>. The area will be
220  * filled in using <b>color</b> and an alpha-blending level of <b>alpha</b>
221  * (default is opaque). */
222 void
223 GraphFrame::paintIntegral(QVector<QPointF> points, QColor color, qreal alpha)
224 {
225  /* Save the current brush, plot the integral, and restore the old brush */
226  QBrush oldBrush = _painter->brush();
227  color.setAlphaF(alpha);
228  _painter->setBrush(QBrush(color));
229  _painter->drawPolygon(points.data(), points.size());
230  _painter->setBrush(oldBrush);
231 }
232 
233 /** Iterates the input list and draws a line on the graph in the appropriate
234  * color. */
235 void
236 GraphFrame::paintLine(QVector<QPointF> points, QColor color, Qt::PenStyle lineStyle)
237 {
238  /* Save the current brush, plot the line, and restore the old brush */
239  QPen oldPen = _painter->pen();
240  _painter->setPen(QPen(color, lineStyle));
241  _painter->drawPolyline(points.data(), points.size());
242  _painter->setPen(oldPen);
243 }
244 
245 /** Paints selected total indicators on the graph. */
246 void
248 {
249  int x = _scaleWidth + FONT_SIZE, y = 0;
250  int rowHeight = FONT_SIZE;
251 
252 #if !defined(Q_WS_MAC)
253  /* On Mac, we don't need vertical spacing between the text rows. */
254  rowHeight += 5;
255 #endif
256 
257  /* If total received is selected */
258  if (_showRecv) {
259  y = rowHeight;
260  _painter->setPen(RECV_COLOR);
261  _painter->drawText(x, y,
262  tr("Recv: ") + totalToStr(_totalRecv) +
263  " ("+tr("%1 KB/s").arg(_recvData->first(), 0, 'f', 2)+")");
264  }
265 
266  /* If total sent is selected */
267  if (_showSend) {
268  y += rowHeight;
269  _painter->setPen(SEND_COLOR);
270  _painter->drawText(x, y,
271  tr("Sent: ") + totalToStr(_totalSend) +
272  " ("+tr("%1 KB/s").arg(_sendData->first(), 0, 'f', 2)+")");
273  }
274 }
275 
276 /** Returns a formatted string with the correct size suffix. */
277 QString
279 {
280  /* Determine the correct size suffix */
281  if (total < 1024) {
282  /* Use KB suffix */
283  return tr("%1 KB").arg(total, 0, 'f', 2);
284  } else if (total < 1048576) {
285  /* Use MB suffix */
286  return tr("%1 MB").arg(total/1024.0, 0, 'f', 2);
287  } else {
288  /* Use GB suffix */
289  return tr("%1 GB").arg(total/1048576.0, 0, 'f', 2);
290  }
291 }
292 
293 /** Returns the width in pixels of <b>label</b> using the current painter's
294  * font. */
295 int
296 GraphFrame::labelWidth(const QString &label)
297 {
298  int width = 0;
299  QFontMetrics fm = fontMetrics();
300 
301  for (int i = 0; i < label.length(); i++)
302  width += fm.charWidth(label, i);
303  return width;
304 }
305 
306 /** Paints the scale on the graph. */
307 void
309 {
310  QString label[4];
311  int width[4];
312  int top = _rec.y();
313  int bottom = _rec.height();
314  int scaleWidth = 0;
315  qreal pos;
316  qreal markStep = _maxValue * .25;
317  qreal paintStep = (bottom - (bottom/8)) / 4;
318 
319  /* Compute each of the y-axis labels */
320  for (int i = 0; i < 4; i++) {
321  pos = bottom - ((i+1) * paintStep);
322  label[i] = tr("%1 KB/s").arg(markStep*(i+1), 0, 'f', 2);
323  width[i] = labelWidth(label[i]);
324  scaleWidth = qMax(scaleWidth, 2+width[i]);
325  }
326 
327  /* Include a 5px margin between the y-axis and its labels */
328  _scaleWidth = scaleWidth + 5;
329 
330  /* Draw the y-axis labels and horizontal marks in their correctly scaled
331  * locations */
332  for (int i = 0; i < 4; i++) {
333  pos = bottom - ((i+1) * paintStep);
334  _painter->setPen(SCALE_COLOR);
335  _painter->drawText(QPointF(_scaleWidth-width[i]-5, pos), label[i]);
336 
337  _painter->setPen(GRID_COLOR);
338  _painter->drawLine(QPointF(_scaleWidth, pos),
339  QPointF(_rec.width(), pos));
340  }
341 
342  /* Draw the y-axis */
343  _painter->drawLine(_scaleWidth, top, _scaleWidth, bottom);
344 }
345 
346 void
347 GraphFrame::resizeEvent(QResizeEvent *ev)
348 {
349  _maxPoints = ev->size().width() - _scaleWidth;
351 }