summaryrefslogtreecommitdiffstats
path: root/kftpgrabber/src/widgets/trafficgraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kftpgrabber/src/widgets/trafficgraph.cpp')
-rw-r--r--kftpgrabber/src/widgets/trafficgraph.cpp628
1 files changed, 628 insertions, 0 deletions
diff --git a/kftpgrabber/src/widgets/trafficgraph.cpp b/kftpgrabber/src/widgets/trafficgraph.cpp
new file mode 100644
index 0000000..ddd339f
--- /dev/null
+++ b/kftpgrabber/src/widgets/trafficgraph.cpp
@@ -0,0 +1,628 @@
+/*
+ * This file is part of the KFTPGrabber project
+ *
+ * Copyright (C) 2003-2004 by the KFTPGrabber developers
+ * Copyright (C) 2003-2004 Jernej Kos <kostko@jweb-network.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ *
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you
+ * do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+
+#include <math.h>
+#include <string.h>
+
+#include <qpainter.h>
+#include <qpixmap.h>
+
+#include <klocale.h>
+
+#include "trafficgraph.h"
+
+namespace KFTPWidgets {
+
+static inline int min(int a, int b)
+{
+ return (a < b ? a : b);
+}
+
+TrafficGraph::TrafficGraph(QWidget *parent, const char *name)
+ : QWidget(parent, name)
+{
+ // Auto deletion does not work for pointer to arrays.
+ m_beamData.setAutoDelete(false);
+
+ setBackgroundMode(NoBackground);
+
+ m_samples = 0;
+ m_minValue = m_maxValue = 0.0;
+ m_useAutoRange = true;
+
+ m_graphStyle = GRAPH_POLYGON;
+
+ // Anything smaller than this does not make sense.
+ setMinimumSize(16, 100);
+ setSizePolicy(QSizePolicy(QSizePolicy::Expanding,
+ QSizePolicy::Expanding, false));
+
+ m_showVerticalLines = true;
+ m_verticalLinesColor = QColor(0x04FB1D);
+ m_verticalLinesDistance = 30;
+ m_verticalLinesScroll = true;
+ m_verticalLinesOffset = 0;
+ m_horizontalScale = 1;
+
+ m_showHorizontalLines = true;
+ m_horizontalLinesColor = QColor(0x04FB1D);
+ m_horizontalLinesCount = 5;
+
+ m_showLabels = true;
+ m_showTopBar = false;
+ m_fontSize = 8;
+
+ m_backgroundColor = QColor(0x313031);
+}
+
+
+TrafficGraph::~TrafficGraph()
+{
+ for (double* p = m_beamData.first(); p; p = m_beamData.next())
+ delete [] p;
+}
+
+bool TrafficGraph::addBeam(const QColor &color)
+{
+ double* d = new double[m_samples];
+ memset(d, 0, sizeof(double) * m_samples);
+ m_beamData.append(d);
+ m_beamColor.append(color);
+
+ return true;
+}
+
+void TrafficGraph::addSample(const QValueList<double>& sampleBuf)
+{
+ if (m_beamData.count() != sampleBuf.count())
+ return;
+
+ double* d;
+ if (m_useAutoRange) {
+ double sum = 0;
+ for (d = m_beamData.first(); d; d = m_beamData.next()) {
+ sum += d[0];
+ if (sum < m_minValue)
+ m_minValue = sum;
+ if (sum > m_maxValue)
+ m_maxValue = sum;
+ }
+ }
+
+ /* If the vertical lines are scrolling, increment the offset
+ * so they move with the data. The vOffset / hScale confusion
+ * is because v refers to Vertical Lines, and h to the horizontal
+ * distance between the vertical lines. */
+ if (m_verticalLinesScroll) {
+ m_verticalLinesOffset = (m_verticalLinesOffset + m_horizontalScale)
+ % m_verticalLinesDistance;
+ }
+
+ // Shift data buffers one sample down and insert new samples.
+ QValueList<double>::ConstIterator s;
+ for (d = m_beamData.first(), s = sampleBuf.begin(); d; d = m_beamData.next(), ++s) {
+ memmove(d, d + 1, (m_samples - 1) * sizeof(double));
+ d[m_samples - 1] = *s;
+ }
+
+ update();
+}
+
+void TrafficGraph::changeRange(int beam, double min, double max)
+{
+ // Only the first beam affects range calculation.
+ if (beam > 1)
+ return;
+
+ m_minValue = min;
+ m_maxValue = max;
+}
+
+QValueList<QColor> &TrafficGraph::beamColors()
+{
+ return m_beamColor;
+}
+
+void TrafficGraph::removeBeam(uint pos)
+{
+ m_beamColor.remove(m_beamColor.at(pos));
+ m_beamData.remove(pos);
+}
+
+void TrafficGraph::setUseAutoRange(bool value)
+{
+ m_useAutoRange = value;
+}
+
+bool TrafficGraph::useAutoRange() const
+{
+ return m_useAutoRange;
+}
+
+void TrafficGraph::setMinValue(double min)
+{
+ m_minValue = min;
+}
+
+double TrafficGraph::minValue() const
+{
+ return (m_useAutoRange ? 0 : m_minValue);
+}
+
+void TrafficGraph::setMaxValue(double max)
+{
+ m_maxValue = max;
+}
+
+double TrafficGraph::maxValue() const
+{
+ return (m_useAutoRange ? 0 : m_maxValue);
+}
+
+void TrafficGraph::setGraphStyle(uint style)
+{
+ m_graphStyle = style;
+}
+
+uint TrafficGraph::graphStyle() const
+{
+ return m_graphStyle;
+}
+
+void TrafficGraph::setHorizontalScale(uint scale)
+{
+ if (scale == m_horizontalScale)
+ return;
+
+ m_horizontalScale = scale;
+ if (isVisible())
+ updateDataBuffers();
+}
+
+uint TrafficGraph::horizontalScale() const
+{
+ return m_horizontalScale;
+}
+
+void TrafficGraph::setShowVerticalLines(bool value)
+{
+ m_showVerticalLines = value;
+}
+
+bool TrafficGraph::showVerticalLines() const
+{
+ return m_showVerticalLines;
+}
+
+void TrafficGraph::setVerticalLinesColor(const QColor &color)
+{
+ m_verticalLinesColor = color;
+}
+
+QColor TrafficGraph::verticalLinesColor() const
+{
+ return m_verticalLinesColor;
+}
+
+void TrafficGraph::setVerticalLinesDistance(int distance)
+{
+ m_verticalLinesDistance = distance;
+}
+
+int TrafficGraph::verticalLinesDistance() const
+{
+ return m_verticalLinesDistance;
+}
+
+void TrafficGraph::setVerticalLinesScroll(bool value)
+{
+ m_verticalLinesScroll = value;
+}
+
+bool TrafficGraph::verticalLinesScroll() const
+{
+ return m_verticalLinesScroll;
+}
+
+void TrafficGraph::setShowHorizontalLines(bool value)
+{
+ m_showHorizontalLines = value;
+}
+
+bool TrafficGraph::showHorizontalLines() const
+{
+ return m_showHorizontalLines;
+}
+
+void TrafficGraph::setHorizontalLinesColor(const QColor &color)
+{
+ m_horizontalLinesColor = color;
+}
+
+QColor TrafficGraph::horizontalLinesColor() const
+{
+ return m_horizontalLinesColor;
+}
+
+void TrafficGraph::setHorizontalLinesCount(int count)
+{
+ m_horizontalLinesCount = count;
+}
+
+int TrafficGraph::horizontalLinesCount() const
+{
+ return m_horizontalLinesCount;
+}
+
+void TrafficGraph::setShowLabels(bool value)
+{
+ m_showLabels = value;
+}
+
+bool TrafficGraph::showLabels() const
+{
+ return m_showLabels;
+}
+
+void TrafficGraph::setShowTopBar(bool value)
+{
+ m_showTopBar = value;
+}
+
+bool TrafficGraph::showTopBar() const
+{
+ return m_showTopBar;
+}
+
+void TrafficGraph::setFontSize(int size)
+{
+ m_fontSize = size;
+}
+
+int TrafficGraph::fontSize() const
+{
+ return m_fontSize;
+}
+
+void TrafficGraph::setBackgroundColor(const QColor &color)
+{
+ m_backgroundColor = color;
+}
+
+QColor TrafficGraph::backgroundColor() const
+{
+ return m_backgroundColor;
+}
+
+void TrafficGraph::resizeEvent(QResizeEvent*)
+{
+ updateDataBuffers();
+}
+
+void TrafficGraph::updateDataBuffers()
+{
+ /* Since the data buffers for the beams are equal in size to the
+ * width of the widget minus 2 we have to enlarge or shrink the
+ * buffers accordingly when a resize occures. To have a nicer
+ * display we try to keep as much data as possible. Data that is
+ * lost due to shrinking the buffers cannot be recovered on
+ * enlarging though. */
+
+ /* Determine new number of samples first.
+ * +0.5 to ensure rounding up
+ * +2 for extra data points so there is
+ * 1) no wasted space and
+ * 2) no loss of precision when drawing the first data point. */
+ uint newSampleNum = static_cast<uint>(((width() - 2 ) / m_horizontalScale) + 2.5);
+
+ // overlap between the old and the new buffers.
+ int overlap = min(m_samples, newSampleNum);
+
+ for (uint i = 0; i < m_beamData.count(); ++i) {
+ double* nd = new double[newSampleNum];
+
+ // initialize new part of the new buffer
+ if (newSampleNum > (uint) overlap)
+ memset(nd, 0, sizeof(double) * (newSampleNum - overlap));
+
+ // copy overlap from old buffer to new buffer
+ memcpy(nd + (newSampleNum - overlap), m_beamData.at(i) + (m_samples - overlap), overlap * sizeof(double));
+
+ m_beamData.remove(i);
+ m_beamData.insert(i, nd);
+ }
+
+ m_samples = newSampleNum;
+}
+
+void TrafficGraph::paintEvent(QPaintEvent*)
+{
+ uint w = width();
+ uint h = height();
+
+ /* Do not do repaints when the widget is not yet setup properly. */
+ if (w <= 2)
+ return;
+
+ QPixmap pm(w, h);
+ QPainter p;
+ p.begin(&pm, this);
+
+ pm.fill(m_backgroundColor);
+ /* Draw white line along the bottom and the right side of the
+ * widget to create a 3D like look. */
+ p.setPen(QColor(colorGroup().light()));
+ p.drawLine(0, h - 1, w - 1, h - 1);
+ p.drawLine(w - 1, 0, w - 1, h - 1);
+
+ p.setClipRect(1, 1, w - 2, h - 2);
+ double range = m_maxValue - m_minValue;
+
+ /* If the range is too small we will force it to 1.0 since it
+ * looks a lot nicer. */
+ if (range < 0.000001)
+ range = 1.0;
+
+ double minValue = m_minValue;
+ if (m_useAutoRange) {
+ if (m_minValue != 0.0) {
+ double dim = pow(10, floor(log10(fabs(m_minValue )))) / 2;
+ if (m_minValue < 0.0)
+ minValue = dim * floor(m_minValue / dim);
+ else
+ minValue = dim * ceil(m_minValue / dim);
+ range = m_maxValue - minValue;
+ if (range < 0.000001)
+ range = 1.0;
+ }
+
+ // Massage the range so that the grid shows some nice values.
+ double step = range / m_horizontalLinesCount;
+ double dim = pow(10, floor(log10(step))) / 2;
+ range = dim * ceil(step / dim) * m_horizontalLinesCount;
+ }
+
+ double maxValue = minValue + range;
+
+ int top = 0;
+ if (m_showTopBar && h > (m_fontSize + 2 + m_horizontalLinesCount * 10)) {
+ /* Draw horizontal bar with current sensor values at top of display. */
+ p.setPen(m_horizontalLinesColor);
+ int x0 = w / 2;
+ p.setFont(QFont(p.font().family(), m_fontSize));
+ top = p.fontMetrics().height();
+ h -= top;
+ int h0 = top - 2;
+ p.drawText(0, 0, x0, top - 2, Qt::AlignCenter, i18n("Bandwidth usage"));
+
+ p.drawLine(x0 - 1, 1, x0 - 1, h0);
+ p.drawLine(0, top - 1, w - 2, top - 1);
+
+ double bias = -minValue;
+ double scaleFac = ( w - x0 - 2 ) / range;
+ QValueList<QColor>::Iterator col;
+ col = m_beamColor.begin();
+
+ for (double *d = m_beamData.first(); d; d = m_beamData.next(), ++col) {
+ int start = x0 + (int) (bias * scaleFac);
+ int end = x0 + (int) ((bias += d[ w - 3 ]) * scaleFac);
+
+ /* If the rect is wider than 2 pixels we draw only the last
+ * pixels with the bright color. The rest is painted with
+ * a 50% darker color. */
+ if (end - start > 1) {
+ p.setPen((*col).dark(150));
+ p.setBrush((*col).dark(150));
+ p.drawRect(start, 1, end - start, h0);
+ p.setPen(*col);
+ p.drawLine(end, 1, end, h0);
+ } else if (start - end > 1) {
+ p.setPen((*col).dark(150));
+ p.setBrush((*col).dark(150));
+ p.drawRect(end, 1, start - end, h0);
+ p.setPen(*col);
+ p.drawLine(end, 1, end, h0);
+ } else {
+ p.setPen(*col);
+ p.drawLine(start, 1, start, h0);
+ }
+ }
+ }
+
+ /* Draw scope-like grid vertical lines */
+ if (m_showVerticalLines && w > 60) {
+ p.setPen(m_verticalLinesColor);
+ for (uint x = m_verticalLinesOffset; x < (w - 2); x += m_verticalLinesDistance)
+ p.drawLine(w - x, top, w - x, h + top - 2);
+ }
+
+ /* In autoRange mode we determine the range and plot the values in
+ * one go. This is more efficiently than running through the
+ * buffers twice but we do react on recently discarded samples as
+ * well as new samples one plot too late. So the range is not
+ * correct if the recently discarded samples are larger or smaller
+ * than the current extreme values. But we can probably live with
+ * this. */
+ if (m_useAutoRange)
+ m_minValue = m_maxValue = 0.0;
+
+ /* Plot stacked values */
+ double scaleFac = (h - 2) / range;
+ if (m_graphStyle == GRAPH_ORIGINAL) {
+ int xPos = 0;
+
+ for (int i = 0; i < m_samples; i++, xPos += m_horizontalScale) {
+ double bias = -minValue;
+ QValueList<QColor>::Iterator col;
+ col = m_beamColor.begin();
+ double sum = 0.0;
+
+ for (double *d = m_beamData.first(); d; d = m_beamData.next(), ++col) {
+ if (m_useAutoRange) {
+ sum += d[i];
+ if (sum < m_minValue)
+ m_minValue = sum;
+ if (sum > m_maxValue)
+ m_maxValue = sum;
+ }
+
+ int start = top + h - 2 - (int) (bias * scaleFac);
+ int end = top + h - 2 - (int) ((bias + d[ i ] ) * scaleFac);
+ bias += d[i];
+
+ /* If the line is longer than 2 pixels we draw only the last
+ * 2 pixels with the bright color. The rest is painted with
+ * a 50% darker color. */
+ if (end - start > 2) {
+ p.fillRect(xPos, start, m_horizontalScale, end - start - 1, (*col).dark(150));
+ p.fillRect(xPos, end - 1, m_horizontalScale, 2, *col);
+ } else if (start - end > 2) {
+ p.fillRect(xPos, start, m_horizontalScale, end - start + 1, (*col).dark(150));
+ p.fillRect(xPos, end + 1, m_horizontalScale, 2, *col);
+ } else
+ p.fillRect(xPos, start, m_horizontalScale, end - start, *col);
+
+ }
+ }
+ } else if (m_graphStyle == GRAPH_POLYGON) {
+ int *prevVals = new int[m_beamData.count()];
+ int hack[4];
+ int x1 = w - ((m_samples + 1) * m_horizontalScale);
+
+ for (int i = 0; i < m_samples; i++) {
+ QValueList<QColor>::Iterator col;
+ col = m_beamColor.begin();
+ double sum = 0.0;
+ int y = top + h - 2;
+ int oldY = top + h;
+ int oldPrevY = oldY;
+ int height = 0;
+ int j = 0;
+ int jMax = m_beamData.count() - 1;
+ x1 += m_horizontalScale;
+ int x2 = x1 + m_horizontalScale;
+
+ for (double *d = m_beamData.first(); d; d = m_beamData.next(), ++col, j++) {
+ if (m_useAutoRange) {
+ sum += d[i];
+
+ if ( sum < m_minValue )
+ m_minValue = sum;
+ if ( sum > m_maxValue )
+ m_maxValue = sum;
+ }
+
+ height = (int) ((d[i] - minValue) * scaleFac);
+ y -= height;
+
+ /* If the line is longer than 2 pixels we draw only the last
+ * 2 pixels with the bright color. The rest is painted with
+ * a 50% darker color. */
+ QPen lastPen = QPen(p.pen());
+ p.setPen((*col).dark(150));
+ p.setBrush((*col).dark(150));
+ QPointArray pa(4);
+ int prevY = (i == 0) ? y : prevVals[j];
+ pa.putPoints(0, 1, x1, prevY);
+ pa.putPoints(1, 1, x2, y);
+ pa.putPoints(2, 1, x2, oldY);
+ pa.putPoints(3, 1, x1, oldPrevY);
+ p.drawPolygon(pa);
+ p.setPen(lastPen);
+ if (jMax == 0) {
+ // draw as normal, no deferred drawing req'd.
+ p.setPen(*col);
+ p.drawLine(x1, prevY, x2, y);
+ } else if (j == jMax) {
+ // draw previous values and current values
+ p.drawLine(hack[0], hack[1], hack[2], hack[3]);
+ p.setPen(*col);
+ p.drawLine(x1, prevY, x2, y);
+ } else if (j == 0) {
+ // save values only
+ hack[0] = x1;
+ hack[1] = prevY;
+ hack[2] = x2;
+ hack[3] = y;
+ p.setPen(*col);
+ } else {
+ p.drawLine(hack[0], hack[1], hack[2], hack[3]);
+ hack[0] = x1;
+ hack[1] = prevY;
+ hack[2] = x2;
+ hack[3] = y;
+ p.setPen(*col);
+ }
+
+ prevVals[j] = y;
+ oldY = y;
+ oldPrevY = prevY;
+ }
+ }
+
+ delete[] prevVals;
+ }
+
+ /* Draw horizontal lines and values. Lines are drawn when the
+ * height is greater than 10 times hCount + 1, values are shown
+ * when width is greater than 60 */
+ if (m_showHorizontalLines && h > (10 * (m_horizontalLinesCount + 1))) {
+ p.setPen(m_horizontalLinesColor);
+ p.setFont(QFont(p.font().family(), m_fontSize));
+ QString val;
+
+ for (uint y = 1; y < m_horizontalLinesCount; y++) {
+ p.drawLine(0, top + y * (h / m_horizontalLinesCount), w - 2, top + y * (h / m_horizontalLinesCount));
+
+ if (m_showLabels && h > (m_fontSize + 1) * (m_horizontalLinesCount + 1) && w > 60 ) {
+ val = QString("%1").arg(maxValue - y * (range / m_horizontalLinesCount));
+ p.drawText(6, top + y * (h / m_horizontalLinesCount) - 1, val);
+ }
+ }
+
+ if (m_showLabels && h > (m_fontSize + 1) * (m_horizontalLinesCount + 1) && w > 60) {
+ val = QString("%1").arg(minValue);
+ p.drawText(6, top + h - 2, val);
+ }
+ }
+
+ p.end();
+ bitBlt(this, 0, 0, &pm);
+}
+
+}
+
+#include "trafficgraph.moc"