Vidalia  0.2.15
GraphFrame.cpp
Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file GraphFrame.cpp
00013 ** \brief Graphs a series of send and receive data points
00014 */
00015 
00016 #include "GraphFrame.h"
00017 
00018 #include <QtGui>
00019 
00020 
00021 /** Default contructor */
00022 GraphFrame::GraphFrame(QWidget *parent)
00023 : QFrame(parent)
00024 {
00025   /* Create Graph Frame related objects */
00026   _recvData = new QList<qreal>();
00027   _sendData = new QList<qreal>();
00028   _painter = new QPainter();
00029   _graphStyle = SolidLine;
00030   
00031   /* Initialize graph values */
00032   _recvData->prepend(0);
00033   _sendData->prepend(0);
00034   _maxPoints = getNumPoints();
00035   _maxPosition = 0;
00036   _showRecv = true;
00037   _showSend = true;
00038   _maxValue = MIN_SCALE;
00039   _scaleWidth = 0;
00040 }
00041 
00042 /** Default destructor */
00043 GraphFrame::~GraphFrame()
00044 {
00045   delete _painter;
00046   delete _recvData;
00047   delete _sendData;
00048 }
00049 
00050 /** Gets the width of the desktop, which is the maximum number of points 
00051  * we can plot in the graph. */
00052 int
00053 GraphFrame::getNumPoints()
00054 {
00055   return size().width() - _scaleWidth;
00056 }
00057 
00058 /** Adds new data points to the graph. */
00059 void
00060 GraphFrame::addPoints(qreal recv, qreal send)
00061 {
00062   /* If maximum number of points plotted, remove oldest */
00063   if (_sendData->size() == _maxPoints) {
00064     _sendData->removeLast();
00065     _recvData->removeLast();
00066   }
00067 
00068   /* Update the displayed maximum */
00069   if (_maxPosition >= _maxPoints) {
00070     _maxValue = MIN_SCALE;
00071     foreach(qreal send, *_sendData)
00072       if(send > _maxValue)
00073         _maxValue = send;
00074     foreach(qreal recv, *_recvData)
00075       if(recv > _maxValue)
00076         _maxValue = recv;
00077     _maxPosition = 0;
00078   }
00079 
00080   /* Add the points to their respective lists */
00081   _sendData->prepend(send);
00082   _recvData->prepend(recv);
00083 
00084   /* Add to the total counters */
00085   _totalSend += send;
00086   _totalRecv += recv;
00087   
00088   bool maxUpdated = false;
00089   /* Check for a new maximum value */
00090   if (send > _maxValue) {
00091     _maxValue = send;
00092     maxUpdated = true;
00093   }
00094 
00095   if (recv > _maxValue) {
00096     _maxValue = recv;
00097     maxUpdated = true;
00098   }
00099 
00100   if (maxUpdated) {
00101     _maxPosition = 0;
00102   } else {
00103     _maxPosition++;
00104   }
00105 
00106   this->update();
00107 }
00108 
00109 /** Clears the graph. */
00110 void
00111 GraphFrame::resetGraph()
00112 {
00113   _recvData->clear();
00114   _sendData->clear();
00115   _recvData->prepend(0);
00116   _sendData->prepend(0);
00117   _maxValue = MIN_SCALE;
00118   _totalSend = 0;
00119   _totalRecv = 0;
00120   this->update();
00121 }
00122 
00123 /** Toggles display of respective graph lines and counters. */
00124 void
00125 GraphFrame::setShowCounters(bool showRecv, bool showSend)
00126 {
00127   _showRecv = showRecv;
00128   _showSend = showSend;
00129   this->update();
00130 }
00131 
00132 /** Overloads default QWidget::paintEvent. Draws the actual 
00133  * bandwidth graph. */
00134 void
00135 GraphFrame::paintEvent(QPaintEvent *event)
00136 {
00137   Q_UNUSED(event);
00138 
00139   /* Set current graph dimensions */
00140   _rec = this->frameRect();
00141   
00142   /* Start the painter */
00143   _painter->begin(this);
00144   
00145   /* We want antialiased lines and text */
00146   _painter->setRenderHint(QPainter::Antialiasing);
00147   _painter->setRenderHint(QPainter::TextAntialiasing);
00148   
00149   /* Fill in the background */
00150   _painter->fillRect(_rec, QBrush(BACK_COLOR));
00151   _painter->drawRect(_rec);
00152 
00153   /* Paint the scale */
00154   paintScale();
00155   /* Plot the send/receive data */
00156   paintData();
00157   /* Paint the send/recv totals */
00158   paintTotals();
00159 
00160   /* Stop the painter */
00161   _painter->end();
00162 }
00163 
00164 /** Paints an integral and an outline of that integral for each data set (send
00165  * and/or receive) that is to be displayed. The integrals will be drawn first,
00166  * followed by the outlines, since we want the area of overlapping integrals
00167  * to blend, but not the outlines of those integrals. */
00168 void
00169 GraphFrame::paintData()
00170 {
00171   QVector<QPointF> recvPoints, sendPoints;
00172 
00173   /* Convert the bandwidth data points to graph points */
00174   recvPoints = pointsFromData(_recvData);
00175   sendPoints = pointsFromData(_sendData);
00176   
00177   if (_graphStyle == AreaGraph) {
00178     /* Plot the bandwidth data as area graphs */
00179     if (_showRecv)
00180       paintIntegral(recvPoints, RECV_COLOR, 0.6);
00181     if (_showSend)
00182       paintIntegral(sendPoints, SEND_COLOR, 0.4);
00183   }
00184   
00185   /* Plot the bandwidth as solid lines. If the graph style is currently an
00186    * area graph, we end up outlining the integrals. */
00187   if (_showRecv)
00188     paintLine(recvPoints, RECV_COLOR);
00189   if (_showSend)
00190     paintLine(sendPoints, SEND_COLOR);
00191 }
00192 
00193 /** Returns a list of points on the bandwidth graph based on the supplied set
00194  * of send or receive values. */
00195 QVector<QPointF>
00196 GraphFrame::pointsFromData(QList<qreal>* list)
00197 {
00198   QVector<QPointF> points;
00199   int x = _rec.width();
00200   int y = _rec.height();
00201   qreal scale = (y - (y/10)) / _maxValue;
00202   qreal currValue;
00203   
00204   /* Translate all data points to points on the graph frame */
00205   points << QPointF(x, y);
00206   for (int i = 0; i < list->size(); i++) {
00207     currValue = y - (list->at(i) * scale);
00208     if (x - SCROLL_STEP < _scaleWidth) {
00209       points << QPointF(_scaleWidth, currValue);
00210       break;
00211     }
00212     points << QPointF(x, currValue);
00213     x -= SCROLL_STEP;
00214   }
00215   points << QPointF(_scaleWidth, y);
00216   return points; 
00217 }
00218 
00219 /** Plots an integral using the data points in <b>points</b>. The area will be
00220  * filled in using <b>color</b> and an alpha-blending level of <b>alpha</b>
00221  * (default is opaque). */
00222 void
00223 GraphFrame::paintIntegral(QVector<QPointF> points, QColor color, qreal alpha)
00224 {
00225   /* Save the current brush, plot the integral, and restore the old brush */
00226   QBrush oldBrush = _painter->brush();
00227   color.setAlphaF(alpha);
00228   _painter->setBrush(QBrush(color));
00229   _painter->drawPolygon(points.data(), points.size());
00230   _painter->setBrush(oldBrush);
00231 }
00232 
00233 /** Iterates the input list and draws a line on the graph in the appropriate
00234  * color. */
00235 void
00236 GraphFrame::paintLine(QVector<QPointF> points, QColor color, Qt::PenStyle lineStyle) 
00237 {
00238   /* Save the current brush, plot the line, and restore the old brush */
00239   QPen oldPen = _painter->pen();
00240   _painter->setPen(QPen(color, lineStyle));
00241   _painter->drawPolyline(points.data(), points.size());
00242   _painter->setPen(oldPen);
00243 }
00244 
00245 /** Paints selected total indicators on the graph. */
00246 void
00247 GraphFrame::paintTotals()
00248 {
00249   int x = _scaleWidth + FONT_SIZE, y = 0;
00250   int rowHeight = FONT_SIZE;
00251 
00252 #if !defined(Q_WS_MAC)
00253   /* On Mac, we don't need vertical spacing between the text rows. */
00254   rowHeight += 5;
00255 #endif
00256 
00257   /* If total received is selected */
00258   if (_showRecv) {
00259     y = rowHeight;
00260     _painter->setPen(RECV_COLOR);
00261     _painter->drawText(x, y,
00262         tr("Recv: ") + totalToStr(_totalRecv) + 
00263         " ("+tr("%1 KB/s").arg(_recvData->first(), 0, 'f', 2)+")");
00264   }
00265 
00266   /* If total sent is selected */
00267   if (_showSend) {
00268     y += rowHeight;
00269     _painter->setPen(SEND_COLOR);
00270     _painter->drawText(x, y,
00271         tr("Sent: ") + totalToStr(_totalSend) +
00272         " ("+tr("%1 KB/s").arg(_sendData->first(), 0, 'f', 2)+")");
00273   }
00274 }
00275 
00276 /** Returns a formatted string with the correct size suffix. */
00277 QString
00278 GraphFrame::totalToStr(qreal total)
00279 {
00280   /* Determine the correct size suffix */
00281   if (total < 1024) {
00282     /* Use KB suffix */
00283     return tr("%1 KB").arg(total, 0, 'f', 2);
00284   } else if (total < 1048576) {
00285     /* Use MB suffix */
00286     return tr("%1 MB").arg(total/1024.0, 0, 'f', 2);
00287   } else {
00288     /* Use GB suffix */
00289     return tr("%1 GB").arg(total/1048576.0, 0, 'f', 2);
00290   }
00291 }
00292 
00293 /** Returns the width in pixels of <b>label</b> using the current painter's
00294  * font. */
00295 int
00296 GraphFrame::labelWidth(const QString &label)
00297 {
00298   int width = 0;
00299   QFontMetrics fm = fontMetrics();
00300 
00301   for (int i = 0; i < label.length(); i++)
00302     width += fm.charWidth(label, i);
00303   return width;
00304 }
00305 
00306 /** Paints the scale on the graph. */
00307 void
00308 GraphFrame::paintScale()
00309 {
00310   QString label[4];
00311   int width[4];
00312   int top = _rec.y();
00313   int bottom = _rec.height();
00314   int scaleWidth = 0;
00315   qreal pos;
00316   qreal markStep = _maxValue * .25;
00317   qreal paintStep = (bottom - (bottom/8)) / 4;
00318 
00319   /* Compute each of the y-axis labels */
00320   for (int i = 0; i < 4; i++) {
00321     pos = bottom - ((i+1) * paintStep);
00322     label[i] = tr("%1 KB/s").arg(markStep*(i+1), 0, 'f', 2);
00323     width[i] = labelWidth(label[i]);
00324     scaleWidth = qMax(scaleWidth, 2+width[i]);
00325   }
00326 
00327   /* Include a 5px margin between the y-axis and its labels */
00328   _scaleWidth = scaleWidth + 5;
00329 
00330   /* Draw the y-axis labels and horizontal marks in their correctly scaled
00331    * locations */
00332   for (int i = 0; i < 4; i++) {
00333     pos = bottom - ((i+1) * paintStep);
00334     _painter->setPen(SCALE_COLOR);
00335     _painter->drawText(QPointF(_scaleWidth-width[i]-5, pos), label[i]);
00336 
00337     _painter->setPen(GRID_COLOR);
00338     _painter->drawLine(QPointF(_scaleWidth, pos),
00339                        QPointF(_rec.width(), pos));
00340   }
00341 
00342   /* Draw the y-axis */
00343   _painter->drawLine(_scaleWidth, top, _scaleWidth, bottom);
00344 }
00345 
00346 void
00347 GraphFrame::resizeEvent(QResizeEvent *ev)
00348 {
00349   _maxPoints = ev->size().width() - _scaleWidth;
00350   _maxPoints /= SCROLL_STEP;
00351 }