mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 19:02:51 +00:00
938 lines
36 KiB
C++
938 lines
36 KiB
C++
/*
|
|
KSysGuard, the KDE System Guard
|
|
|
|
Copyright (c) 1999 - 2002 Chris Schlaeger <cs@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License version 2 or at your option version 3 as published by
|
|
the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include <QtXml/qdom.h>
|
|
#include <QtGui/QImage>
|
|
#include <QtGui/QToolTip>
|
|
#include <QtGui/QVBoxLayout>
|
|
#include <QtGui/QHBoxLayout>
|
|
#include <QtGui/QLabel>
|
|
#include <QtGui/QFontInfo>
|
|
#include <QtGui/qevent.h>
|
|
|
|
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kapplication.h>
|
|
#include <ksignalplotter.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <ksgrd/SensorManager.h>
|
|
#include "StyleEngine.h"
|
|
|
|
#include "FancyPlotterSettings.h"
|
|
|
|
#include "FancyPlotter.h"
|
|
|
|
class SensorToAdd {
|
|
public:
|
|
QRegExp name;
|
|
QString hostname;
|
|
QString type;
|
|
QList<QColor> colors;
|
|
QString summationName;
|
|
};
|
|
|
|
class FancyPlotterLabel : public QLabel {
|
|
public:
|
|
FancyPlotterLabel(QWidget *parent) : QLabel(parent) {
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
longHeadingWidth = 0;
|
|
shortHeadingWidth = 0;
|
|
textMargin = 0;
|
|
setLayoutDirection(Qt::LeftToRight); //We do this because we organise the strings ourselves.. is this going to muck it up though for RTL languages?
|
|
}
|
|
~FancyPlotterLabel() {
|
|
}
|
|
void setLabel(const QString &name, const QColor &color) {
|
|
labelName = name;
|
|
|
|
if(indicatorSymbol.isNull()) {
|
|
if(fontMetrics().inFont(QChar(0x25CF)))
|
|
indicatorSymbol = QChar(0x25CF);
|
|
else
|
|
indicatorSymbol = '#';
|
|
}
|
|
changeLabel(color);
|
|
|
|
}
|
|
void setValueText(const QString &value) {
|
|
//value can have multiple strings, separated with the 0x9c character
|
|
valueText = value.split(QChar(0x9c));
|
|
resizeEvent(NULL);
|
|
update();
|
|
}
|
|
virtual void resizeEvent( QResizeEvent * ) {
|
|
QFontMetrics fm = fontMetrics();
|
|
|
|
if(valueText.isEmpty()) {
|
|
if(longHeadingWidth < width())
|
|
setText(longHeadingText);
|
|
else
|
|
setText(shortHeadingText);
|
|
return;
|
|
}
|
|
QString value = valueText.first();
|
|
|
|
int textWidth = fm.boundingRect(value).width();
|
|
if(textWidth + longHeadingWidth < width())
|
|
setBothText(longHeadingText, value);
|
|
else if(textWidth + shortHeadingWidth < width())
|
|
setBothText(shortHeadingText, value);
|
|
else {
|
|
int valueTextCount = valueText.count();
|
|
int i;
|
|
for(i = 1; i < valueTextCount; ++i) {
|
|
textWidth = fm.boundingRect(valueText.at(i)).width();
|
|
if(textWidth + shortHeadingWidth <= width()) {
|
|
break;
|
|
}
|
|
}
|
|
if(i < valueTextCount)
|
|
setBothText(shortHeadingText, valueText.at(i));
|
|
else
|
|
setText(noHeadingText + valueText.last()); //This just sets the color of the text
|
|
}
|
|
}
|
|
void changeLabel(const QColor &_color) {
|
|
color = _color;
|
|
|
|
if ( kapp->layoutDirection() == Qt::RightToLeft )
|
|
longHeadingText = QString(": ") + labelName + " <font color=\"" + color.name() + "\">" + indicatorSymbol + "</font>";
|
|
else
|
|
longHeadingText = QString("<qt><font color=\"") + color.name() + "\">" + indicatorSymbol + "</font> " + labelName + " :";
|
|
shortHeadingText = QString("<qt><font color=\"") + color.name() + "\">" + indicatorSymbol + "</font>";
|
|
noHeadingText = QString("<qt><font color=\"") + color.name() + "\">";
|
|
|
|
textMargin = fontMetrics().width('x') + margin()*2 + frameWidth()*2;
|
|
longHeadingWidth = fontMetrics().boundingRect(labelName + " :" + indicatorSymbol + " x").width() + textMargin;
|
|
shortHeadingWidth = fontMetrics().boundingRect(indicatorSymbol).width() + textMargin;
|
|
setMinimumWidth(shortHeadingWidth);
|
|
update();
|
|
}
|
|
private:
|
|
void setBothText(const QString &heading, const QString & value) {
|
|
if(QApplication::layoutDirection() == Qt::LeftToRight)
|
|
setText(heading + ' ' + value);
|
|
else
|
|
setText("<qt>" + value + ' ' + heading);
|
|
}
|
|
int textMargin;
|
|
QString longHeadingText;
|
|
QString shortHeadingText;
|
|
QString noHeadingText;
|
|
int longHeadingWidth;
|
|
int shortHeadingWidth;
|
|
QList<QString> valueText;
|
|
QString labelName;
|
|
QColor color;
|
|
static QChar indicatorSymbol;
|
|
};
|
|
|
|
QChar FancyPlotterLabel::indicatorSymbol;
|
|
|
|
FancyPlotter::FancyPlotter( QWidget* parent,
|
|
const QString &title,
|
|
SharedSettings *workSheetSettings)
|
|
: KSGRD::SensorDisplay( parent, title, workSheetSettings )
|
|
{
|
|
mBeams = 0;
|
|
mSettingsDialog = 0;
|
|
mSensorReportedMax = mSensorReportedMin = 0;
|
|
mSensorManualMax = mSensorManualMin = 0;
|
|
mUseManualRange = false;
|
|
mNumAnswers = 0;
|
|
mLabelsWidget = NULL;
|
|
|
|
//The unicode character 0x25CF is a big filled in circle. We would prefer to use this in the tooltip.
|
|
//However it's maybe possible that the font used to draw the tooltip won't have it. So we fall back to a
|
|
//"#" instead.
|
|
QFontMetrics fm(QToolTip::font());
|
|
if(fm.inFont(QChar(0x25CF)))
|
|
mIndicatorSymbol = QChar(0x25CF);
|
|
else
|
|
mIndicatorSymbol = '#';
|
|
|
|
QBoxLayout *layout = new QVBoxLayout(this);
|
|
layout->setSpacing(0);
|
|
mPlotter = new KSignalPlotter( this );
|
|
int axisTextWidth = fontMetrics().width(i18nc("Largest axis title", "99999 XXXX"));
|
|
mPlotter->setMaxAxisTextWidth( axisTextWidth );
|
|
mPlotter->setUseAutoRange( true );
|
|
mHeading = new QLabel(translatedTitle(), this);
|
|
QFont headingFont;
|
|
headingFont.setWeight(QFont::Bold);
|
|
headingFont.setPointSizeF(headingFont.pointSizeF() * 1.19);
|
|
mHeading->setFont(headingFont);
|
|
layout->addWidget(mHeading);
|
|
layout->addWidget(mPlotter);
|
|
|
|
/* Create a set of labels underneath the graph. */
|
|
mLabelsWidget = new QWidget;
|
|
layout->addWidget(mLabelsWidget);
|
|
QBoxLayout *outerLabelLayout = new QHBoxLayout(mLabelsWidget);
|
|
outerLabelLayout->setSpacing(0);
|
|
outerLabelLayout->setContentsMargins(0,0,0,0);
|
|
|
|
/* create a spacer to fill up the space up to the start of the graph */
|
|
outerLabelLayout->addItem(new QSpacerItem(axisTextWidth + 10, 0, QSizePolicy::Preferred));
|
|
|
|
mLabelLayout = new QHBoxLayout;
|
|
outerLabelLayout->addLayout(mLabelLayout);
|
|
mLabelLayout->setContentsMargins(0,0,0,0);
|
|
QFont font;
|
|
font.setPointSize( KSGRD::Style->fontSize() );
|
|
mPlotter->setFont( font );
|
|
|
|
/* All RMB clicks to the mPlotter widget will be handled by
|
|
* SensorDisplay::eventFilter. */
|
|
mPlotter->installEventFilter( this );
|
|
|
|
setPlotterWidget( mPlotter );
|
|
connect(mPlotter, SIGNAL(axisScaleChanged()), this, SLOT(plotterAxisScaleChanged()));
|
|
QDomElement emptyElement;
|
|
restoreSettings(emptyElement);
|
|
}
|
|
|
|
FancyPlotter::~FancyPlotter()
|
|
{
|
|
}
|
|
|
|
void FancyPlotter::setTitle( const QString &title ) { //virtual
|
|
KSGRD::SensorDisplay::setTitle( title );
|
|
if(mHeading)
|
|
mHeading->setText(translatedTitle());
|
|
}
|
|
|
|
bool FancyPlotter::eventFilter( QObject* object, QEvent* event ) { //virtual
|
|
if(event->type() == QEvent::ToolTip)
|
|
setTooltip();
|
|
return SensorDisplay::eventFilter(object, event);
|
|
}
|
|
|
|
void FancyPlotter::configureSettings()
|
|
{
|
|
if(mSettingsDialog)
|
|
return;
|
|
mSettingsDialog = new FancyPlotterSettings( this, mSharedSettings->locked );
|
|
|
|
mSettingsDialog->setTitle( title() );
|
|
mSettingsDialog->setUseManualRange( mUseManualRange );
|
|
if(mUseManualRange) {
|
|
mSettingsDialog->setMinValue( mSensorManualMin );
|
|
mSettingsDialog->setMaxValue( mSensorManualMax );
|
|
} else {
|
|
mSettingsDialog->setMinValue( mSensorReportedMin );
|
|
mSettingsDialog->setMaxValue( mSensorReportedMax );
|
|
}
|
|
|
|
mSettingsDialog->setHorizontalScale( mPlotter->horizontalScale() );
|
|
|
|
mSettingsDialog->setShowVerticalLines( mPlotter->showVerticalLines() );
|
|
mSettingsDialog->setVerticalLinesDistance( mPlotter->verticalLinesDistance() );
|
|
mSettingsDialog->setVerticalLinesScroll( mPlotter->verticalLinesScroll() );
|
|
|
|
mSettingsDialog->setShowHorizontalLines( mPlotter->showHorizontalLines() );
|
|
|
|
mSettingsDialog->setShowAxis( mPlotter->showAxis() );
|
|
|
|
mSettingsDialog->setFontSize( mPlotter->font().pointSize() );
|
|
|
|
mSettingsDialog->setRangeUnits( mUnit );
|
|
mSettingsDialog->setRangeUnits( mUnit );
|
|
|
|
mSettingsDialog->setStackBeams( mPlotter->stackGraph() );
|
|
|
|
bool hasIntegerRange = true;
|
|
SensorModelEntry::List list;
|
|
for ( int i = 0; i < (int)mBeams; ++i ) {
|
|
FPSensorProperties *sensor = NULL;
|
|
//find the first sensor for this beam, since one beam can have many sensors
|
|
for ( int j = 0; j < sensors().count(); ++j ) {
|
|
FPSensorProperties *sensor2 = static_cast<FPSensorProperties *>(sensors().at(j));
|
|
if(sensor2->beamId == i)
|
|
sensor = sensor2;
|
|
}
|
|
if(!sensor)
|
|
return;
|
|
SensorModelEntry entry;
|
|
entry.setId( i );
|
|
entry.setHostName( sensor->hostName() );
|
|
entry.setSensorName( sensor->regExpName().isEmpty()?sensor->name():sensor->regExpName() );
|
|
entry.setUnit( sensor->unit() );
|
|
entry.setStatus( sensor->isOk() ? i18n( "OK" ) : i18n( "Error" ) );
|
|
entry.setColor( mPlotter->beamColor( i ) );
|
|
if(!sensor->isInteger)
|
|
hasIntegerRange = false;
|
|
list.append( entry );
|
|
}
|
|
mSettingsDialog->setSensors( list );
|
|
mSettingsDialog->setHasIntegerRange( hasIntegerRange );
|
|
|
|
connect( mSettingsDialog, SIGNAL(applyClicked()), this, SLOT(applySettings()) );
|
|
connect( mSettingsDialog, SIGNAL(okClicked()), this, SLOT(applySettings()) );
|
|
connect( mSettingsDialog, SIGNAL(finished()), this, SLOT(settingsFinished()) );
|
|
|
|
mSettingsDialog->show();
|
|
}
|
|
|
|
void FancyPlotter::settingsFinished()
|
|
{
|
|
mSettingsDialog->delayedDestruct();
|
|
mSettingsDialog = 0;
|
|
}
|
|
|
|
void FancyPlotter::applySettings() {
|
|
setTitle( mSettingsDialog->title() );
|
|
|
|
mUseManualRange = mSettingsDialog->useManualRange();
|
|
if(mUseManualRange) {
|
|
mSensorManualMin = mSettingsDialog->minValue();
|
|
mSensorManualMax = mSettingsDialog->maxValue();
|
|
mPlotter->changeRange( mSettingsDialog->minValue(), mSettingsDialog->maxValue() );
|
|
}
|
|
else {
|
|
mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
|
|
}
|
|
|
|
if ( mPlotter->horizontalScale() != mSettingsDialog->horizontalScale() ) {
|
|
mPlotter->setHorizontalScale( mSettingsDialog->horizontalScale() );
|
|
}
|
|
|
|
mPlotter->setShowVerticalLines( mSettingsDialog->showVerticalLines() );
|
|
mPlotter->setVerticalLinesDistance( mSettingsDialog->verticalLinesDistance() );
|
|
mPlotter->setVerticalLinesScroll( mSettingsDialog->verticalLinesScroll() );
|
|
|
|
mPlotter->setShowHorizontalLines( mSettingsDialog->showHorizontalLines() );
|
|
|
|
mPlotter->setShowAxis( mSettingsDialog->showAxis() );
|
|
mPlotter->setStackGraph( mSettingsDialog->stackBeams() );
|
|
|
|
QFont font;
|
|
font.setPointSize( mSettingsDialog->fontSize() );
|
|
mPlotter->setFont( font );
|
|
|
|
QList<int> deletedBeams = mSettingsDialog->deleted();
|
|
for ( int i =0; i < deletedBeams.count(); ++i) {
|
|
removeBeam(deletedBeams[i]);
|
|
}
|
|
mSettingsDialog->clearDeleted(); //We have deleted them, so clear the deleted
|
|
|
|
reorderBeams(mSettingsDialog->order());
|
|
mSettingsDialog->resetOrder(); //We have now reordered the sensors, so reset the order
|
|
|
|
SensorModelEntry::List list = mSettingsDialog->sensors();
|
|
|
|
for( int i = 0; i < list.count(); i++)
|
|
setBeamColor(i, list[i].color());
|
|
mPlotter->update();
|
|
}
|
|
|
|
void FancyPlotter::resizeEvent( QResizeEvent* )
|
|
{
|
|
bool showHeading = true;;
|
|
bool showLabels = true;;
|
|
|
|
if( height() < mLabelsWidget->sizeHint().height() + mHeading->sizeHint().height() + mPlotter->minimumHeight() )
|
|
showHeading = false;
|
|
if( height() < mLabelsWidget->sizeHint().height() + mPlotter->minimumHeight() )
|
|
showLabels = false;
|
|
mHeading->setVisible(showHeading);
|
|
mLabelsWidget->setVisible(showLabels);
|
|
|
|
}
|
|
|
|
void FancyPlotter::reorderBeams(const QList<int> & orderOfBeams)
|
|
{
|
|
//Q_ASSERT(orderOfBeams.size() == mLabelLayout.size()); Commented out because it cause compile problems in some cases??
|
|
//Reorder the graph
|
|
mPlotter->reorderBeams(orderOfBeams);
|
|
//Reorder the labels underneath the graph
|
|
QList<QLayoutItem *> labelsInOldOrder;
|
|
while(!mLabelLayout->isEmpty())
|
|
labelsInOldOrder.append(mLabelLayout->takeAt(0));
|
|
|
|
for(int newIndex = 0; newIndex < orderOfBeams.count(); newIndex++) {
|
|
int oldIndex = orderOfBeams.at(newIndex);
|
|
mLabelLayout->addItem(labelsInOldOrder.at(oldIndex));
|
|
}
|
|
|
|
for ( int i = 0; i < sensors().count(); ++i ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
for(int newIndex = 0; newIndex < orderOfBeams.count(); newIndex++) {
|
|
int oldIndex = orderOfBeams.at(newIndex);
|
|
if(oldIndex == sensor->beamId) {
|
|
sensor->beamId = newIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void FancyPlotter::applyStyle()
|
|
{
|
|
QFont font = mPlotter->font();
|
|
font.setPointSize(KSGRD::Style->fontSize() );
|
|
mPlotter->setFont( font );
|
|
for ( int i = 0; i < mPlotter->numBeams() &&
|
|
(unsigned int)i < KSGRD::Style->numSensorColors(); ++i ) {
|
|
setBeamColor(i, KSGRD::Style->sensorColor(i));
|
|
}
|
|
|
|
mPlotter->update();
|
|
}
|
|
void FancyPlotter::setBeamColor(int i, const QColor &color)
|
|
{
|
|
mPlotter->setBeamColor( i, color );
|
|
static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(i)))->widget())->changeLabel(color);
|
|
}
|
|
bool FancyPlotter::addSensor( const QString &hostName, const QString &name,
|
|
const QString &type, const QString &title )
|
|
{
|
|
return addSensor( hostName, name, type, title,
|
|
KSGRD::Style->sensorColor( mBeams ), QString(), mBeams );
|
|
}
|
|
|
|
bool FancyPlotter::addSensor( const QString &hostName, const QString &name,
|
|
const QString &type, const QString &title,
|
|
const QColor &color, const QString ®expName,
|
|
int beamId, const QString & summationName)
|
|
{
|
|
if ( type != "integer" && type != "float" )
|
|
return false;
|
|
|
|
|
|
registerSensor( new FPSensorProperties( hostName, name, type, title, color, regexpName, beamId, summationName ) );
|
|
|
|
/* To differentiate between answers from value requests and info
|
|
* requests we add 100 to the beam index for info requests. */
|
|
sendRequest( hostName, name + '?', sensors().size() - 1 + 100 );
|
|
|
|
if((int)mBeams == beamId) {
|
|
mPlotter->addBeam( color );
|
|
/* Add a label for this beam */
|
|
FancyPlotterLabel *label = new FancyPlotterLabel(this);
|
|
mLabelLayout->addWidget(label);
|
|
if(!summationName.isEmpty()) {
|
|
label->setLabel(summationName, mPlotter->beamColor(mBeams));
|
|
}
|
|
++mBeams;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FancyPlotter::removeBeam( uint beamId )
|
|
{
|
|
if ( beamId >= mBeams ) {
|
|
kDebug(1215) << "FancyPlotter::removeBeam: beamId out of range ("
|
|
<< beamId << ")" << endl;
|
|
return false;
|
|
}
|
|
|
|
mPlotter->removeBeam( beamId );
|
|
--mBeams;
|
|
QWidget *label = (static_cast<QWidgetItem *>(mLabelLayout->takeAt( beamId )))->widget();
|
|
mLabelLayout->removeWidget(label);
|
|
delete label;
|
|
|
|
mSensorReportedMax = 0;
|
|
mSensorReportedMin = 0;
|
|
for ( int i = sensors().count()-1; i >= 0; --i ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
|
|
if(sensor->beamId == (int)beamId)
|
|
removeSensor( i );
|
|
else {
|
|
if(sensor->beamId > (int)beamId)
|
|
sensor->beamId--; //sensor pointer is no longer valid after removing the sensor
|
|
mSensorReportedMax = qMax(mSensorReportedMax, sensor->maxValue);
|
|
mSensorReportedMin = qMin(mSensorReportedMin, sensor->minValue);
|
|
}
|
|
}
|
|
//change the plotter's range to the new maximum
|
|
if ( !mUseManualRange )
|
|
mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
|
|
else
|
|
mPlotter->changeRange( mSensorManualMin, mSensorManualMax );
|
|
|
|
//loop through the new sensors to find the new unit
|
|
for ( int i = 0; i < sensors().count(); i++ ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
if(i == 0)
|
|
mUnit = sensor->unit();
|
|
else if(mUnit != sensor->unit()) {
|
|
mUnit = "";
|
|
break;
|
|
}
|
|
}
|
|
//adjust the scale to take into account the removed sensor
|
|
plotterAxisScaleChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FancyPlotter::setTooltip()
|
|
{
|
|
QString tooltip = "<qt><p style='white-space:pre'>";
|
|
|
|
QString description;
|
|
QString lastValue;
|
|
bool neednewline = false;
|
|
bool showingSummationGroup = false;
|
|
int beamId = -1;
|
|
//Note that the number of beams can be less than the number of sensors, since some sensors
|
|
//get added together for a beam.
|
|
//For the tooltip, we show all the sensors
|
|
for ( int i = 0; i < sensors().count(); ++i ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
description = sensor->description();
|
|
if(description.isEmpty())
|
|
description = sensor->name();
|
|
|
|
if(sensor->isOk()) {
|
|
lastValue = KGlobal::locale()->formatNumber( sensor->lastValue, (sensor->isInteger)?0:-1 );
|
|
if (sensor->unit() == "%")
|
|
lastValue = i18nc("units", "%1%", lastValue);
|
|
else if( !sensor->unit().isEmpty() )
|
|
lastValue = i18nc("units", QString(QString("%1 ") + sensor->unit()).toUtf8(), lastValue);
|
|
} else {
|
|
lastValue = i18n("Error");
|
|
}
|
|
if (beamId != sensor->beamId) {
|
|
if (!sensor->summationName.isEmpty()) {
|
|
tooltip += i18nc("%1 is what is being shown statistics for, like 'Memory', 'Swap', etc.", "<p><b>%1:</b><br>", i18n(sensor->summationName.toUtf8().constData()));
|
|
showingSummationGroup = true;
|
|
neednewline = false;
|
|
} else if (showingSummationGroup) {
|
|
//When a summation group has finished, seperate the next sensor with a newline
|
|
showingSummationGroup = false;
|
|
tooltip += "<br>";
|
|
}
|
|
|
|
}
|
|
beamId = sensor->beamId;
|
|
|
|
if(sensor->isLocalhost()) {
|
|
tooltip += QString( "%1%2 %3 (%4)" ).arg( neednewline ? "<br>" : "")
|
|
.arg("<font color=\"" + mPlotter->beamColor( beamId ).name() + "\">"+mIndicatorSymbol+"</font>")
|
|
.arg( i18n(description.toUtf8()) )
|
|
.arg( lastValue );
|
|
|
|
} else {
|
|
tooltip += QString( "%1%2 %3:%4 (%5)" ).arg( neednewline ? "<br>" : "" )
|
|
.arg("<font color=\"" + mPlotter->beamColor( beamId ).name() + "\">"+mIndicatorSymbol+"</font>")
|
|
.arg( sensor->hostName() )
|
|
.arg( i18n(description.toUtf8()) )
|
|
.arg( lastValue );
|
|
}
|
|
neednewline = true;
|
|
}
|
|
// tooltip += "</td></tr></table>";
|
|
mPlotter->setToolTip( tooltip );
|
|
}
|
|
|
|
void FancyPlotter::sendDataToPlotter( )
|
|
{
|
|
if(!mSampleBuf.isEmpty() && mBeams != 0) {
|
|
if((uint)mSampleBuf.count() > mBeams) {
|
|
mSampleBuf.clear();
|
|
return; //ignore invalid results - can happen if a sensor is deleted
|
|
}
|
|
while((uint)mSampleBuf.count() < mBeams)
|
|
mSampleBuf.append(mPlotter->lastValue(mSampleBuf.count())); //we might have sensors missing so set their values to the previously known value
|
|
mPlotter->addSample( mSampleBuf );
|
|
if(isVisible()) {
|
|
if(QToolTip::isVisible() && (qApp->topLevelAt(QCursor::pos()) == window()) && mPlotter->geometry().contains(mPlotter->mapFromGlobal( QCursor::pos() ))) {
|
|
setTooltip();
|
|
QToolTip::showText(QCursor::pos(), mPlotter->toolTip(), mPlotter);
|
|
}
|
|
QString lastValue;
|
|
int beamId = -1;
|
|
for ( int i = 0; i < sensors().size(); ++i ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
if(sensor->beamId == beamId)
|
|
continue;
|
|
beamId = sensor->beamId;
|
|
if(sensor->isOk() && mPlotter->numBeams() > beamId) {
|
|
|
|
int precision;
|
|
if(sensor->unit() == mUnit) {
|
|
precision = (sensor->isInteger && mPlotter->scaleDownBy() == 1)?0:-1;
|
|
lastValue = mPlotter->lastValueAsString(beamId, precision);
|
|
} else {
|
|
precision = (sensor->isInteger)?0:-1;
|
|
lastValue = KGlobal::locale()->formatNumber( mPlotter->lastValue(beamId), precision );
|
|
if (sensor->unit() == "%")
|
|
lastValue = i18nc("units", "%1%", lastValue);
|
|
else if( !sensor->unit().isEmpty() ) {
|
|
lastValue = i18nc("units", QString("%1 " + sensor->unit()).toUtf8(), lastValue);
|
|
}
|
|
}
|
|
|
|
if(sensor->maxValue != 0 && sensor->unit() != "%") {
|
|
//Use a multi length string incase we do not have enough room
|
|
lastValue = i18n("%1 of %2" "\xc2\x9c" "%1", lastValue, mPlotter->valueAsString(sensor->maxValue, precision) );
|
|
}
|
|
} else {
|
|
lastValue = i18n("Error");
|
|
}
|
|
static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(beamId)))->widget())->setValueText(lastValue);
|
|
}
|
|
}
|
|
|
|
}
|
|
mSampleBuf.clear();
|
|
}
|
|
void FancyPlotter::timerTick() //virtual
|
|
{
|
|
if(mNumAnswers < sensors().count())
|
|
sendDataToPlotter(); //we haven't received enough answers yet, but plot what we do have
|
|
mNumAnswers = 0;
|
|
SensorDisplay::timerTick();
|
|
}
|
|
void FancyPlotter::plotterAxisScaleChanged()
|
|
{
|
|
//Prevent this being called recursively
|
|
disconnect(mPlotter, SIGNAL(axisScaleChanged()), this, SLOT(plotterAxisScaleChanged()));
|
|
KLocalizedString unit;
|
|
double value = mPlotter->currentMaximumRangeValue();
|
|
if(mUnit == "KiB") {
|
|
if(value >= 1024*1024*1024*0.7) { //If it's over 0.7TiB, then set the scale to terabytes
|
|
mPlotter->setScaleDownBy(1024*1024*1024);
|
|
unit = ki18nc("units", "%1 TiB"); // the unit - terabytes
|
|
} else if(value >= 1024*1024*0.7) { //If it's over 0.7GiB, then set the scale to gigabytes
|
|
mPlotter->setScaleDownBy(1024*1024);
|
|
unit = ki18nc("units", "%1 GiB"); // the unit - gigabytes
|
|
} else if(value > 1024) {
|
|
mPlotter->setScaleDownBy(1024);
|
|
unit = ki18nc("units", "%1 MiB"); // the unit - megabytes
|
|
} else {
|
|
mPlotter->setScaleDownBy(1);
|
|
unit = ki18nc("units", "%1 KiB"); // the unit - kilobytes
|
|
}
|
|
} else if(mUnit == "KiB/s") {
|
|
if(value >= 1024*1024*1024*0.7) { //If it's over 0.7TiB, then set the scale to terabytes
|
|
mPlotter->setScaleDownBy(1024*1024*1024);
|
|
unit = ki18nc("units", "%1 TiB/s"); // the unit - terabytes per second
|
|
} else if(value >= 1024*1024*0.7) { //If it's over 0.7GiB, then set the scale to gigabytes
|
|
mPlotter->setScaleDownBy(1024*1024);
|
|
unit = ki18nc("units", "%1 GiB/s"); // the unit - gigabytes per second
|
|
} else if(value > 1024) {
|
|
mPlotter->setScaleDownBy(1024);
|
|
unit = ki18nc("units", "%1 MiB/s"); // the unit - megabytes per second
|
|
} else {
|
|
mPlotter->setScaleDownBy(1);
|
|
unit = ki18nc("units", "%1 KiB/s"); // the unit - kilobytes per second
|
|
}
|
|
} else if(mUnit == "%") {
|
|
mPlotter->setScaleDownBy(1);
|
|
unit = ki18nc("units", "%1%"); //the unit - percentage
|
|
} else if(mUnit.isEmpty()) {
|
|
unit = ki18nc("unitless - just a number", "%1");
|
|
} else {
|
|
#if 0 // the strings are here purely for translation
|
|
NOOP_I18NC("units", "%1 1/s"); // the unit - 1 per second
|
|
NOOP_I18NC("units", "%1 s"); // the unit - seconds
|
|
NOOP_I18NC("units", "%1 MHz"); // the unit - frequency megahertz
|
|
#endif
|
|
mPlotter->setScaleDownBy(1);
|
|
//translate any others
|
|
unit = ki18nc("units", QString("%1 " + mUnit).toUtf8());
|
|
}
|
|
mPlotter->setUnit(unit);
|
|
//reconnect
|
|
connect(mPlotter, SIGNAL(axisScaleChanged()), this, SLOT(plotterAxisScaleChanged()));
|
|
}
|
|
void FancyPlotter::answerReceived( int id, const QList<QByteArray> &answerlist )
|
|
{
|
|
QByteArray answer;
|
|
|
|
if(!answerlist.isEmpty()) answer = answerlist[0];
|
|
if ( (uint)id < 100 ) {
|
|
//Make sure that we put the answer in the correct place. Its index in the list should be equal to the sensor index. This in turn will contain the beamId
|
|
if(id >= sensors().count())
|
|
return; //just ignore if we get a result for an invalid sensor
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(id));
|
|
int beamId = sensor->beamId;
|
|
double value = answer.toDouble();
|
|
while(beamId > mSampleBuf.count())
|
|
mSampleBuf.append(0); //we might have sensors missing so set their values to zero
|
|
|
|
if(beamId == mSampleBuf.count()) {
|
|
mSampleBuf.append( value );
|
|
} else {
|
|
mSampleBuf[beamId] += value; //If we get two answers for the same beamid, we should add them together. That's how the summation works
|
|
}
|
|
sensor->lastValue = value;
|
|
/* We received something, so the sensor is probably ok. */
|
|
sensorError( id, false );
|
|
|
|
if(++mNumAnswers == sensors().count())
|
|
sendDataToPlotter(); //we have received all the answers so start plotting
|
|
} else if ( id >= 100 && id < 200 ) {
|
|
if( (id - 100) >= sensors().count())
|
|
return; //just ignore if we get a result for an invalid sensor
|
|
KSGRD::SensorFloatInfo info( answer );
|
|
QString unit = info.unit();
|
|
if(unit.toUpper() == "KB" || unit.toUpper() == "KIB")
|
|
unit = "KiB";
|
|
if(unit.toUpper() == "KB/S" || unit.toUpper() == "KIB/S")
|
|
unit = "KiB/s";
|
|
|
|
if(id == 100) //if we are the first sensor, just use that sensors units as the global unit
|
|
mUnit = unit;
|
|
else if(unit != mUnit)
|
|
mUnit = ""; //if the units don't match, then set the units on the scale to empty, to avoid any confusion
|
|
|
|
mSensorReportedMax = qMax(mSensorReportedMax, info.max());
|
|
mSensorReportedMin = qMin(mSensorReportedMin, info.min());
|
|
|
|
if ( !mUseManualRange )
|
|
mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
|
|
plotterAxisScaleChanged();
|
|
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(id - 100));
|
|
sensor->maxValue = info.max();
|
|
sensor->minValue = info.min();
|
|
sensor->setUnit( unit );
|
|
sensor->setDescription( info.name() );
|
|
|
|
QString summationName = sensor->summationName;
|
|
int beamId = sensor->beamId;
|
|
|
|
Q_ASSERT(beamId < mPlotter->numBeams());
|
|
Q_ASSERT(beamId < mLabelLayout->count());
|
|
|
|
if(summationName.isEmpty())
|
|
static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(beamId)))->widget())->setLabel(info.name(), mPlotter->beamColor(beamId));
|
|
|
|
} else if( id == 200) {
|
|
/* FIXME This doesn't check the host! */
|
|
if(!mSensorsToAdd.isEmpty()) {
|
|
foreach(SensorToAdd *sensor, mSensorsToAdd) {
|
|
int beamId = mBeams; //Assign the next sensor to the next available beamId
|
|
for ( int i = 0; i < answerlist.count(); ++i ) {
|
|
if ( answerlist[ i ].isEmpty() )
|
|
continue;
|
|
QString sensorName = QString::fromUtf8(answerlist[ i ].split('\t')[0]);
|
|
if(sensor->name.exactMatch(sensorName)) {
|
|
if(sensor->summationName.isEmpty())
|
|
beamId = mBeams; //If summationName is not empty then reuse the previous beamId. In this way we can have multiple sensors with the same beamId, which can then be summed together
|
|
QColor color;
|
|
if(!sensor->colors.isEmpty() )
|
|
color = sensor->colors.takeFirst();
|
|
else if(KSGRD::Style->numSensorColors() != 0)
|
|
color = KSGRD::Style->sensorColor( beamId % KSGRD::Style->numSensorColors());
|
|
addSensor( sensor->hostname, sensorName,
|
|
(sensor->type.isEmpty()) ? "float" : sensor->type
|
|
, "", color, sensor->name.pattern(), beamId, sensor->summationName);
|
|
}
|
|
}
|
|
}
|
|
qDeleteAll(mSensorsToAdd);
|
|
mSensorsToAdd.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FancyPlotter::restoreSettings( QDomElement &element )
|
|
{
|
|
mUseManualRange = element.attribute( "manualRange", "0" ).toInt();
|
|
|
|
if(mUseManualRange) {
|
|
mSensorManualMax = element.attribute( "max" ).toDouble();
|
|
mSensorManualMin = element.attribute( "min" ).toDouble();
|
|
mPlotter->changeRange( mSensorManualMin, mSensorManualMax );
|
|
} else {
|
|
mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
|
|
}
|
|
|
|
mPlotter->setUseAutoRange(element.attribute( "autoRange", "1" ).toInt());
|
|
|
|
// Do not restore the color settings from a previous version
|
|
int version = element.attribute("version", "0").toInt();
|
|
|
|
mPlotter->setShowVerticalLines( element.attribute( "vLines", "0" ).toUInt() );
|
|
mPlotter->setVerticalLinesDistance( element.attribute( "vDistance", "30" ).toUInt() );
|
|
mPlotter->setVerticalLinesScroll( element.attribute( "vScroll", "0" ).toUInt() );
|
|
mPlotter->setHorizontalScale( element.attribute( "hScale", "6" ).toUInt() );
|
|
|
|
mPlotter->setShowHorizontalLines( element.attribute( "hLines", "1" ).toUInt() );
|
|
mPlotter->setStackGraph( element.attribute("stacked", "0").toInt());
|
|
|
|
QString filename = element.attribute( "svgBackground");
|
|
if (!filename.isEmpty() && filename[0] == '/') {
|
|
KStandardDirs* kstd = KGlobal::dirs();
|
|
filename = kstd->findResource( "data", "ksysguard/" + filename);
|
|
}
|
|
mPlotter->setSvgBackground( filename );
|
|
if(version >= 1) {
|
|
mPlotter->setShowAxis( element.attribute( "labels", "1" ).toUInt() );
|
|
uint fontsize = element.attribute( "fontSize", "0").toUInt();
|
|
if(fontsize == 0) fontsize = KSGRD::Style->fontSize();
|
|
QFont font;
|
|
font.setPointSize( fontsize );
|
|
mPlotter->setFont( font );
|
|
}
|
|
QDomNodeList dnList = element.elementsByTagName( "beam" );
|
|
for ( int i = 0; i < dnList.count(); ++i ) {
|
|
QDomElement el = dnList.item( i ).toElement();
|
|
if(el.hasAttribute("regexpSensorName")) {
|
|
SensorToAdd *sensor = new SensorToAdd();
|
|
sensor->name = QRegExp(el.attribute("regexpSensorName"));
|
|
sensor->hostname = el.attribute( "hostName" );
|
|
sensor->type = el.attribute( "sensorType" );
|
|
sensor->summationName = el.attribute("summationName");
|
|
QStringList colors = el.attribute("color").split(',');
|
|
bool ok;
|
|
foreach(const QString &color, colors) {
|
|
int c = color.toUInt( &ok, 0 );
|
|
if(ok) {
|
|
QColor col( (c & 0xff0000) >> 16, (c & 0xff00) >> 8, (c & 0xff), (c & 0xff000000) >> 24);
|
|
if(col.isValid()) {
|
|
if(col.alpha() == 0) col.setAlpha(255);
|
|
sensor->colors << col;
|
|
}
|
|
else
|
|
sensor->colors << KSGRD::Style->sensorColor( i );
|
|
}
|
|
else
|
|
sensor->colors << KSGRD::Style->sensorColor( i );
|
|
}
|
|
mSensorsToAdd.append(sensor);
|
|
sendRequest( sensor->hostname, "monitors", 200 );
|
|
} else
|
|
addSensor( el.attribute( "hostName" ), el.attribute( "sensorName" ),
|
|
( el.attribute( "sensorType" ).isEmpty() ? "float" :
|
|
el.attribute( "sensorType" ) ), "", restoreColor( el, "color",
|
|
KSGRD::Style->sensorColor( i ) ), QString(), mBeams, el.attribute("summationName") );
|
|
}
|
|
|
|
SensorDisplay::restoreSettings( element );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FancyPlotter::saveSettings( QDomDocument &doc, QDomElement &element)
|
|
{
|
|
element.setAttribute( "autoRange", mPlotter->useAutoRange() );
|
|
|
|
element.setAttribute( "manualRange", mUseManualRange );
|
|
if(mUseManualRange) {
|
|
element.setAttribute( "min", mSensorManualMin );
|
|
element.setAttribute( "max", mSensorManualMax );
|
|
}
|
|
|
|
|
|
element.setAttribute( "vLines", mPlotter->showVerticalLines() );
|
|
element.setAttribute( "vDistance", mPlotter->verticalLinesDistance() );
|
|
element.setAttribute( "vScroll", mPlotter->verticalLinesScroll() );
|
|
|
|
element.setAttribute( "hScale", mPlotter->horizontalScale() );
|
|
|
|
element.setAttribute( "hLines", mPlotter->showHorizontalLines() );
|
|
|
|
element.setAttribute( "svgBackground", mPlotter->svgBackground() );
|
|
element.setAttribute( "stacked", mPlotter->stackGraph() );
|
|
|
|
element.setAttribute( "version", 1 );
|
|
element.setAttribute( "labels", mPlotter->showAxis() );
|
|
element.setAttribute( "fontSize", mPlotter->font().pointSize() );
|
|
|
|
QHash<QString,QDomElement> hash;
|
|
int beamId = -1;
|
|
for ( int i = 0; i < sensors().size(); ++i ) {
|
|
FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
|
|
if(sensor->beamId == beamId)
|
|
continue;
|
|
beamId = sensor->beamId;
|
|
|
|
QString regExpName = sensor->regExpName();
|
|
if(!regExpName.isEmpty() && hash.contains( regExpName )) {
|
|
QDomElement oldBeam = hash.value(regExpName);
|
|
saveColorAppend( oldBeam, "color", mPlotter->beamColor( beamId ) );
|
|
} else {
|
|
QDomElement beam = doc.createElement( "beam" );
|
|
element.appendChild( beam );
|
|
beam.setAttribute( "hostName", sensor->hostName() );
|
|
if(regExpName.isEmpty())
|
|
beam.setAttribute( "sensorName", sensor->name() );
|
|
else {
|
|
beam.setAttribute( "regexpSensorName", sensor->regExpName() );
|
|
hash[regExpName] = beam;
|
|
}
|
|
if(!sensor->summationName.isEmpty())
|
|
beam.setAttribute( "summationName", sensor->summationName);
|
|
beam.setAttribute( "sensorType", sensor->type() );
|
|
saveColor( beam, "color", mPlotter->beamColor( beamId ) );
|
|
}
|
|
}
|
|
SensorDisplay::saveSettings( doc, element );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FancyPlotter::hasSettingsDialog() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FPSensorProperties::FPSensorProperties()
|
|
{
|
|
}
|
|
|
|
FPSensorProperties::FPSensorProperties( const QString &hostName,
|
|
const QString &name,
|
|
const QString &type,
|
|
const QString &description,
|
|
const QColor &color,
|
|
const QString ®expName,
|
|
int beamId_,
|
|
const QString &summationName_ )
|
|
: KSGRD::SensorProperties( hostName, name, type, description),
|
|
mColor( color )
|
|
{
|
|
setRegExpName(regexpName);
|
|
beamId = beamId_;
|
|
summationName = summationName_;
|
|
maxValue = 0;
|
|
minValue = 0;
|
|
lastValue = 0;
|
|
isInteger = (type == "integer");
|
|
}
|
|
|
|
FPSensorProperties::~FPSensorProperties()
|
|
{
|
|
}
|
|
|
|
void FPSensorProperties::setColor( const QColor &color )
|
|
{
|
|
mColor = color;
|
|
}
|
|
|
|
QColor FPSensorProperties::color() const
|
|
{
|
|
return mColor;
|
|
}
|
|
|
|
|
|
#include "moc_FancyPlotter.cpp"
|