diff options
Diffstat (limited to 'kftpgrabber/src/widgets/trafficgraph.cpp')
-rw-r--r-- | kftpgrabber/src/widgets/trafficgraph.cpp | 628 |
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" |