/* * This file is part of the KFTPGrabber project * * Copyright (C) 2003-2004 by the KFTPGrabber developers * Copyright (C) 2003-2004 Jernej Kos * * 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 #include #include #include #include #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& 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::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 &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(((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::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::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::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"