1 /*
2 KSysGuard, the KDE System Guard
3
4 Copyright (c) 1999 - 2002 Chris Schlaeger <cs@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <QApplication>
24 #include <QDebug>
25 #include <QDomElement>
26 #include <QToolTip>
27 #include <QVBoxLayout>
28 #include <QHBoxLayout>
29 #include <QLabel>
30 #include <QLocale>
31 #include <QResizeEvent>
32 #include <QStandardPaths>
33
34 #include <KMessageBox>
35 #include <ksignalplotter.h>
36
37 #include <ksgrd/SensorManager.h>
38 #include "StyleEngine.h"
39
40 #include "FancyPlotterSettings.h"
41
42 #include "FancyPlotter.h"
43
44 // The unicode character 0x25CF is a big filled in circle. We would prefer
45 // to use this in the tooltip (or other messages). However it's possible
46 // that the font used to draw the tooltip won't have it. So we fall back to a
47 // "#" instead.
48 static constexpr const int BLACK_CIRCLE = 0x25CF;
circleCharacter(const QFontMetrics & fm)49 static inline QChar circleCharacter(const QFontMetrics& fm)
50 {
51 return fm.inFont(QChar(BLACK_CIRCLE)) ? QChar(BLACK_CIRCLE) : QLatin1Char('#');
52 }
53
54 class SensorToAdd {
55 public:
56 QRegExp name;
57 QString hostname;
58 QString type;
59 QList<QColor> colors;
60 QString summationName;
61 };
62
63 class FancyPlotterLabel : public QLabel {
64 public:
FancyPlotterLabel(QWidget * parent)65 FancyPlotterLabel(QWidget *parent) : QLabel(parent) {
66 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
67 longHeadingWidth = 0;
68 shortHeadingWidth = 0;
69 textMargin = 0;
70 setLayoutDirection(Qt::LeftToRight); //We do this because we organise the strings ourselves.. is this going to muck it up though for RTL languages?
71 }
~FancyPlotterLabel()72 ~FancyPlotterLabel() override {
73 }
setLabel(const QString & name,const QColor & color)74 void setLabel(const QString &name, const QColor &color) {
75 labelName = name;
76
77 if(indicatorSymbol.isNull()) {
78 indicatorSymbol = circleCharacter(fontMetrics());
79 }
80 changeLabel(color);
81
82 }
setValueText(const QString & value)83 void setValueText(const QString &value) {
84 //value can have multiple strings, separated with the 0x9c character
85 valueText = value.split(QChar(0x9c));
86 resizeEvent(nullptr);
87 update();
88 }
resizeEvent(QResizeEvent *)89 void resizeEvent( QResizeEvent * ) override {
90 QFontMetrics fm = fontMetrics();
91
92 if(valueText.isEmpty()) {
93 if(longHeadingWidth < width())
94 setText(longHeadingText);
95 else
96 setText(shortHeadingText);
97 return;
98 }
99 QString value = valueText.first();
100
101 int textWidth = fm.boundingRect(value).width();
102 if(textWidth + longHeadingWidth < width())
103 setBothText(longHeadingText, value);
104 else if(textWidth + shortHeadingWidth < width())
105 setBothText(shortHeadingText, value);
106 else {
107 int valueTextCount = valueText.count();
108 int i;
109 for(i = 1; i < valueTextCount; ++i) {
110 textWidth = fm.boundingRect(valueText.at(i)).width();
111 if(textWidth + shortHeadingWidth <= width()) {
112 break;
113 }
114 }
115 if(i < valueTextCount)
116 setBothText(shortHeadingText, valueText.at(i));
117 else
118 setText(noHeadingText + valueText.last()); //This just sets the color of the text
119 }
120 }
changeLabel(const QColor & _color)121 void changeLabel(const QColor &_color) {
122 color = _color;
123
124 if ( QApplication::layoutDirection() == Qt::RightToLeft )
125 longHeadingText = QLatin1String(": ") + labelName + QLatin1String(" <font color=\"") + color.name() + QLatin1String("\">") + indicatorSymbol + QLatin1String("</font>");
126 else
127 longHeadingText = QLatin1String("<qt><font color=\"") + color.name() + QLatin1String("\">") + indicatorSymbol + QLatin1String("</font> ") + labelName + QLatin1String(" :");
128 shortHeadingText = QLatin1String("<qt><font color=\"") + color.name() + QLatin1String("\">") + indicatorSymbol + QLatin1String("</font>");
129 noHeadingText = QLatin1String("<qt><font color=\"") + color.name() + QLatin1String("\">");
130
131 textMargin = fontMetrics().boundingRect(QLatin1Char('x')).width() + margin()*2 + frameWidth()*2;
132 longHeadingWidth = fontMetrics().boundingRect(labelName + QLatin1String(" :") + indicatorSymbol + QLatin1String(" x")).width() + textMargin;
133 shortHeadingWidth = fontMetrics().boundingRect(indicatorSymbol).width() + textMargin;
134 setMinimumWidth(shortHeadingWidth);
135 update();
136 }
137 private:
setBothText(const QString & heading,const QString & value)138 void setBothText(const QString &heading, const QString & value) {
139 if(QApplication::layoutDirection() == Qt::LeftToRight)
140 setText(heading + QLatin1Char(' ') + value);
141 else
142 setText(QStringLiteral("<qt>") + value + QLatin1Char(' ') + heading);
143 }
144 int textMargin;
145 QString longHeadingText;
146 QString shortHeadingText;
147 QString noHeadingText;
148 int longHeadingWidth;
149 int shortHeadingWidth;
150 QList<QString> valueText;
151 QString labelName;
152 QColor color;
153 static QChar indicatorSymbol;
154 };
155
156 QChar FancyPlotterLabel::indicatorSymbol;
157
FancyPlotter(QWidget * parent,const QString & title,SharedSettings * workSheetSettings)158 FancyPlotter::FancyPlotter( QWidget* parent,
159 const QString &title,
160 SharedSettings *workSheetSettings)
161 : KSGRD::SensorDisplay( parent, title, workSheetSettings )
162 {
163 mBeams = 0;
164 mSettingsDialog = 0;
165 mSensorReportedMax = mSensorReportedMin = 0;
166 mSensorManualMax = mSensorManualMin = 0;
167 mUseManualRange = false;
168 mNumAnswers = 0;
169 mLabelsWidget = nullptr;
170
171 mIndicatorSymbol = circleCharacter(QFontMetrics(QToolTip::font()));
172
173 QBoxLayout *layout = new QVBoxLayout(this);
174 layout->setSpacing(0);
175 mPlotter = new KSignalPlotter( this );
176 int axisTextWidth = fontMetrics().boundingRect(i18nc("Largest axis title", "99999 XXXX")).width();
177 mPlotter->setMaxAxisTextWidth( axisTextWidth );
178 mPlotter->setUseAutoRange( true );
179 mHeading = new QLabel(translatedTitle(), this);
180 QFont headingFont;
181 headingFont.setWeight(QFont::Bold);
182 headingFont.setPointSizeF(headingFont.pointSizeF() * 1.19);
183 mHeading->setFont(headingFont);
184 layout->addWidget(mHeading);
185 layout->addWidget(mPlotter);
186
187 /* Create a set of labels underneath the graph. */
188 mLabelsWidget = new QWidget;
189 layout->addWidget(mLabelsWidget);
190 QBoxLayout *outerLabelLayout = new QHBoxLayout(mLabelsWidget);
191 outerLabelLayout->setSpacing(0);
192 outerLabelLayout->setContentsMargins(0,0,0,0);
193
194 /* create a spacer to fill up the space up to the start of the graph */
195 outerLabelLayout->addItem(new QSpacerItem(axisTextWidth + 10, 0, QSizePolicy::Preferred));
196
197 mLabelLayout = new QHBoxLayout;
198 outerLabelLayout->addLayout(mLabelLayout);
199 mLabelLayout->setContentsMargins(0,0,0,0);
200 QFont font;
201 font.setPointSize( KSGRD::Style->fontSize() );
202 mPlotter->setFont( font );
203
204 /* All RMB clicks to the mPlotter widget will be handled by
205 * SensorDisplay::eventFilter. */
206 mPlotter->installEventFilter( this );
207
208 setPlotterWidget( mPlotter );
209 connect(mPlotter, &KSignalPlotter::axisScaleChanged, this, &FancyPlotter::plotterAxisScaleChanged);
210 QDomElement emptyElement;
211 restoreSettings(emptyElement);
212 }
213
~FancyPlotter()214 FancyPlotter::~FancyPlotter()
215 {
216 }
217
setTitle(const QString & title)218 void FancyPlotter::setTitle( const QString &title ) { //virtual
219 KSGRD::SensorDisplay::setTitle( title );
220 if(mHeading)
221 mHeading->setText(translatedTitle());
222 }
223
eventFilter(QObject * object,QEvent * event)224 bool FancyPlotter::eventFilter( QObject* object, QEvent* event ) { //virtual
225 if(event->type() == QEvent::ToolTip)
226 setTooltip();
227 return SensorDisplay::eventFilter(object, event);
228 }
229
configureSettings()230 void FancyPlotter::configureSettings()
231 {
232 if(mSettingsDialog)
233 return;
234 mSettingsDialog = new FancyPlotterSettings( this, mSharedSettings->locked );
235
236 mSettingsDialog->setTitle( title() );
237 mSettingsDialog->setUseManualRange( mUseManualRange );
238 if(mUseManualRange) {
239 mSettingsDialog->setMinValue( mSensorManualMin );
240 mSettingsDialog->setMaxValue( mSensorManualMax );
241 } else {
242 mSettingsDialog->setMinValue( mSensorReportedMin );
243 mSettingsDialog->setMaxValue( mSensorReportedMax );
244 }
245
246 mSettingsDialog->setHorizontalScale( mPlotter->horizontalScale() );
247
248 mSettingsDialog->setShowVerticalLines( mPlotter->showVerticalLines() );
249 mSettingsDialog->setVerticalLinesDistance( mPlotter->verticalLinesDistance() );
250 mSettingsDialog->setVerticalLinesScroll( mPlotter->verticalLinesScroll() );
251
252 mSettingsDialog->setShowHorizontalLines( mPlotter->showHorizontalLines() );
253
254 mSettingsDialog->setShowAxis( mPlotter->showAxis() );
255
256 mSettingsDialog->setFontSize( mPlotter->font().pointSize() );
257
258 mSettingsDialog->setRangeUnits( mUnit );
259 mSettingsDialog->setRangeUnits( mUnit );
260
261 mSettingsDialog->setStackBeams( mPlotter->stackGraph() );
262
263 bool hasIntegerRange = true;
264 SensorModelEntry::List list;
265 for ( int i = 0; i < (int)mBeams; ++i ) {
266 FPSensorProperties *sensor = nullptr;
267 //find the first sensor for this beam, since one beam can have many sensors
268 for ( int j = 0; j < sensors().count(); ++j ) {
269 FPSensorProperties *sensor2 = static_cast<FPSensorProperties *>(sensors().at(j));
270 if(sensor2->beamId == i)
271 sensor = sensor2;
272 }
273 if(!sensor)
274 return;
275 SensorModelEntry entry;
276 entry.setId( i );
277 entry.setHostName( sensor->hostName() );
278 entry.setSensorName( sensor->regExpName().isEmpty()?sensor->name():sensor->regExpName() );
279 entry.setUnit( sensor->unit() );
280 entry.setStatus( sensor->isOk() ? i18n( "OK" ) : i18n( "Error" ) );
281 entry.setColor( mPlotter->beamColor( i ) );
282 if(!sensor->isInteger)
283 hasIntegerRange = false;
284 list.append( entry );
285 }
286 mSettingsDialog->setSensors( list );
287 mSettingsDialog->setHasIntegerRange( hasIntegerRange );
288
289 connect(mSettingsDialog, &FancyPlotterSettings::applyClicked, this, &FancyPlotter::applySettings);
290 connect(mSettingsDialog, &FancyPlotterSettings::okClicked, this, &FancyPlotter::applySettings);
291 connect(mSettingsDialog, &FancyPlotterSettings::finished, this, &FancyPlotter::settingsFinished);
292
293 mSettingsDialog->open(); // open() opens the dialog modally (ie. blocks the parent window)
294 }
295
settingsFinished()296 void FancyPlotter::settingsFinished()
297 {
298 if(mSettingsDialog->result() == QDialog::Accepted) {
299 // Apply and OK also connect to applySettings, but it's
300 // racy which slot is called first when OK is clicked:
301 // so do it here, too, and worst case we'll apply twice while OKing.
302 applySettings();
303 }
304 mSettingsDialog->hide();
305 mSettingsDialog->deleteLater();
306 mSettingsDialog = nullptr;
307 }
308
applySettings()309 void FancyPlotter::applySettings() {
310 if (!mSettingsDialog) {
311 return;
312 }
313 setTitle( mSettingsDialog->title() );
314
315 mUseManualRange = mSettingsDialog->useManualRange();
316 if(mUseManualRange) {
317 mSensorManualMin = mSettingsDialog->minValue();
318 mSensorManualMax = mSettingsDialog->maxValue();
319 mPlotter->changeRange( mSettingsDialog->minValue(), mSettingsDialog->maxValue() );
320 }
321 else {
322 mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
323 }
324
325 if ( mPlotter->horizontalScale() != mSettingsDialog->horizontalScale() ) {
326 mPlotter->setHorizontalScale( mSettingsDialog->horizontalScale() );
327 }
328
329 mPlotter->setShowVerticalLines( mSettingsDialog->showVerticalLines() );
330 mPlotter->setVerticalLinesDistance( mSettingsDialog->verticalLinesDistance() );
331 mPlotter->setVerticalLinesScroll( mSettingsDialog->verticalLinesScroll() );
332
333 mPlotter->setShowHorizontalLines( mSettingsDialog->showHorizontalLines() );
334
335 mPlotter->setShowAxis( mSettingsDialog->showAxis() );
336 mPlotter->setStackGraph( mSettingsDialog->stackBeams() );
337
338 QFont font;
339 font.setPointSize( mSettingsDialog->fontSize() );
340 mPlotter->setFont( font );
341
342 QList<int> deletedBeams = mSettingsDialog->deleted();
343 for ( int i =0; i < deletedBeams.count(); ++i) {
344 removeBeam(deletedBeams[i]);
345 }
346 mSettingsDialog->clearDeleted(); //We have deleted them, so clear the deleted
347
348 reorderBeams(mSettingsDialog->order());
349 mSettingsDialog->resetOrder(); //We have now reordered the sensors, so reset the order
350
351 SensorModelEntry::List list = mSettingsDialog->sensors();
352
353 for( int i = 0; i < list.count(); i++)
354 setBeamColor(i, list[i].color());
355 mPlotter->update();
356 }
357
resizeEvent(QResizeEvent *)358 void FancyPlotter::resizeEvent( QResizeEvent* )
359 {
360 bool showHeading = true;
361 bool showLabels = true;
362
363 if( height() < mLabelsWidget->sizeHint().height() + mHeading->sizeHint().height() + mPlotter->minimumHeight() )
364 showHeading = false;
365 if( height() < mLabelsWidget->sizeHint().height() + mPlotter->minimumHeight() )
366 showLabels = false;
367 mHeading->setVisible(showHeading);
368 mLabelsWidget->setVisible(showLabels);
369
370 }
371
reorderBeams(const QList<int> & orderOfBeams)372 void FancyPlotter::reorderBeams(const QList<int> & orderOfBeams)
373 {
374 //Q_ASSERT(orderOfBeams.size() == mLabelLayout.size()); Commented out because it cause compile problems in some cases??
375 //Reorder the graph
376 mPlotter->reorderBeams(orderOfBeams);
377 //Reorder the labels underneath the graph
378 QList<QLayoutItem *> labelsInOldOrder;
379 while(!mLabelLayout->isEmpty())
380 labelsInOldOrder.append(mLabelLayout->takeAt(0));
381
382 for(int newIndex = 0; newIndex < orderOfBeams.count(); newIndex++) {
383 int oldIndex = orderOfBeams.at(newIndex);
384 mLabelLayout->addItem(labelsInOldOrder.at(oldIndex));
385 }
386
387 for ( int i = 0; i < sensors().count(); ++i ) {
388 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
389 for(int newIndex = 0; newIndex < orderOfBeams.count(); newIndex++) {
390 int oldIndex = orderOfBeams.at(newIndex);
391 if(oldIndex == sensor->beamId) {
392 sensor->beamId = newIndex;
393 break;
394 }
395 }
396 }
397 }
applyStyle()398 void FancyPlotter::applyStyle()
399 {
400 QFont font = mPlotter->font();
401 font.setPointSize(KSGRD::Style->fontSize() );
402 mPlotter->setFont( font );
403 for ( int i = 0; i < mPlotter->numBeams() &&
404 i < KSGRD::Style->numSensorColors(); ++i ) {
405 setBeamColor(i, KSGRD::Style->sensorColor(i));
406 }
407
408 mPlotter->update();
409 }
setBeamColor(int i,const QColor & color)410 void FancyPlotter::setBeamColor(int i, const QColor &color)
411 {
412 mPlotter->setBeamColor( i, color );
413 static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(i)))->widget())->changeLabel(color);
414 }
addSensor(const QString & hostName,const QString & name,const QString & type,const QString & title)415 bool FancyPlotter::addSensor( const QString &hostName, const QString &name,
416 const QString &type, const QString &title )
417 {
418 return addSensor( hostName, name, type, title,
419 KSGRD::Style->sensorColor( mBeams ), QString(), mBeams );
420 }
421
addSensor(const QString & hostName,const QString & name,const QString & type,const QString & title,const QColor & color,const QString & regexpName,int beamId,const QString & summationName)422 bool FancyPlotter::addSensor( const QString &hostName, const QString &name,
423 const QString &type, const QString &title,
424 const QColor &color, const QString ®expName,
425 int beamId, const QString & summationName)
426 {
427 if ( type != QLatin1String("integer") && type != QLatin1String("float") )
428 return false;
429
430
431 registerSensor( new FPSensorProperties( hostName, name, type, title, color, regexpName, beamId, summationName ) );
432
433 /* To differentiate between answers from value requests and info
434 * requests we add 100 to the beam index for info requests. */
435 sendRequest( hostName, name + QLatin1Char('?'), sensors().size() - 1 + 100 );
436
437 if((int)mBeams == beamId) {
438 mPlotter->addBeam( color );
439 /* Add a label for this beam */
440 FancyPlotterLabel *label = new FancyPlotterLabel(this);
441 mLabelLayout->addWidget(label);
442 if(!summationName.isEmpty()) {
443 label->setLabel(summationName, mPlotter->beamColor(mBeams));
444 }
445 ++mBeams;
446 }
447
448 return true;
449 }
450
removeBeam(uint beamId)451 bool FancyPlotter::removeBeam( uint beamId )
452 {
453 if ( beamId >= mBeams ) {
454 qDebug() << "FancyPlotter::removeBeam: beamId out of range ("
455 << beamId << ")";
456 return false;
457 }
458
459 mPlotter->removeBeam( beamId );
460 --mBeams;
461 QWidget *label = (static_cast<QWidgetItem *>(mLabelLayout->takeAt( beamId )))->widget();
462 mLabelLayout->removeWidget(label);
463 delete label;
464
465 mSensorReportedMax = 0;
466 mSensorReportedMin = 0;
467 for ( int i = sensors().count()-1; i >= 0; --i ) {
468 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
469
470 if(sensor->beamId == (int)beamId)
471 removeSensor( i );
472 else {
473 if(sensor->beamId > (int)beamId)
474 sensor->beamId--; //sensor pointer is no longer valid after removing the sensor
475 mSensorReportedMax = qMax(mSensorReportedMax, sensor->maxValue);
476 mSensorReportedMin = qMin(mSensorReportedMin, sensor->minValue);
477 }
478 }
479 //change the plotter's range to the new maximum
480 if ( !mUseManualRange )
481 mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
482 else
483 mPlotter->changeRange( mSensorManualMin, mSensorManualMax );
484
485 //loop through the new sensors to find the new unit
486 for ( int i = 0; i < sensors().count(); i++ ) {
487 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
488 if(i == 0)
489 mUnit = sensor->unit();
490 else if(mUnit != sensor->unit()) {
491 mUnit = QLatin1String("");
492 break;
493 }
494 }
495 //adjust the scale to take into account the removed sensor
496 plotterAxisScaleChanged();
497
498 return true;
499 }
500
isPercentage(FPSensorProperties * sensor)501 static inline bool isPercentage(FPSensorProperties* sensor)
502 {
503 return sensor->unit() == QLatin1String("%");
504 }
505
506 // The "useful precision" of a value; integers don't need
507 // any decimal point, percentages can do with 0.1% or 99.9%,
508 // and the rest should be fine with 2 digits.
usefulPrecision(FPSensorProperties * sensor)509 static inline int usefulPrecision(FPSensorProperties* sensor)
510 {
511 if (sensor->isInteger)
512 return 0;
513 if (isPercentage(sensor))
514 return 1;
515 return 2;
516 }
517
setTooltip()518 void FancyPlotter::setTooltip()
519 {
520 QString tooltip = QStringLiteral("<qt><p style='white-space:pre'>");
521
522 QString description;
523 QString lastValue;
524 bool neednewline = false;
525 bool showingSummationGroup = false;
526 int beamId = -1;
527 //Note that the number of beams can be less than the number of sensors, since some sensors
528 //get added together for a beam.
529 //For the tooltip, we show all the sensors
530 for ( int i = 0; i < sensors().count(); ++i ) {
531 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
532 description = sensor->description();
533 if(description.isEmpty())
534 description = sensor->name();
535
536 if(sensor->isOk()) {
537 static_assert(std::is_same<double, decltype(sensor->lastValue)>::value, "Sensor values should be double");
538 lastValue = QLocale().toString( sensor->lastValue, 'f', usefulPrecision(sensor) );
539 if (isPercentage(sensor))
540 lastValue = i18nc("units", "%1%", lastValue);
541 else if( !sensor->unit().isEmpty() )
542 lastValue = i18nc("units", QString(QLatin1String("%1 ") + sensor->unit()).toUtf8().constData(), lastValue);
543 } else {
544 lastValue = i18n("Error");
545 }
546 if (beamId != sensor->beamId) {
547 if (!sensor->summationName.isEmpty()) {
548 tooltip += i18nc("%1 is what is being shown statistics for, like 'Memory', 'Swap', etc.", "<p><b>%1:</b><br>", i18n(sensor->summationName.toUtf8().constData()));
549 showingSummationGroup = true;
550 neednewline = false;
551 } else if (showingSummationGroup) {
552 //When a summation group has finished, separate the next sensor with a newline
553 showingSummationGroup = false;
554 tooltip += QLatin1String("<br>");
555 }
556
557 }
558 beamId = sensor->beamId;
559
560 if(sensor->isLocalhost()) {
561 tooltip += QStringLiteral( "%1%2 %3 (%4)" ).arg( neednewline ? QStringLiteral("<br>") : QString())
562 .arg(QLatin1String("<font color=\"") + mPlotter->beamColor( beamId ).name() + QLatin1String("\">") + mIndicatorSymbol+ QLatin1String("</font>"))
563 .arg( i18n(description.toUtf8().constData()) )
564 .arg( lastValue );
565
566 } else {
567 tooltip += QStringLiteral( "%1%2 %3:%4 (%5)" ).arg( neednewline ? QStringLiteral("<br>") : QString() )
568 .arg(QLatin1String("<font color=\"") + mPlotter->beamColor( beamId ).name() + QLatin1String("\">") + mIndicatorSymbol+ QLatin1String("</font>"))
569 .arg( sensor->hostName() )
570 .arg( i18n(description.toUtf8().constData()) )
571 .arg( lastValue );
572 }
573 neednewline = true;
574 }
575 // tooltip += "</td></tr></table>";
576 mPlotter->setToolTip( tooltip );
577 }
578
sendDataToPlotter()579 void FancyPlotter::sendDataToPlotter( )
580 {
581 if(!mSampleBuf.isEmpty() && mBeams != 0) {
582 if((uint)mSampleBuf.count() > mBeams) {
583 mSampleBuf.clear();
584 return; //ignore invalid results - can happen if a sensor is deleted
585 }
586 while((uint)mSampleBuf.count() < mBeams)
587 mSampleBuf.append(mPlotter->lastValue(mSampleBuf.count())); //we might have sensors missing so set their values to the previously known value
588 mPlotter->addSample( mSampleBuf );
589 if(isVisible()) {
590 if(QToolTip::isVisible() && (qApp->topLevelAt(QCursor::pos()) == window()) && mPlotter->geometry().contains(mPlotter->mapFromGlobal( QCursor::pos() ))) {
591 setTooltip();
592 QToolTip::showText(QCursor::pos(), mPlotter->toolTip(), mPlotter);
593 }
594 QString lastValue;
595 int beamId = -1;
596 for ( int i = 0; i < sensors().size(); ++i ) {
597 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
598 if(sensor->beamId == beamId)
599 continue;
600 beamId = sensor->beamId;
601 if(sensor->isOk() && mPlotter->numBeams() > beamId) {
602
603 int precision;
604 if(sensor->unit() == mUnit) {
605 precision = (sensor->isInteger && mPlotter->scaleDownBy() == 1)?0:-1;
606 lastValue = mPlotter->lastValueAsString(beamId, precision);
607 } else {
608 static_assert(std::is_same<double, decltype(mPlotter->lastValue(beamId))>::value, "Beam values should be double");
609 precision = usefulPrecision(sensor);
610 lastValue = QLocale().toString( mPlotter->lastValue(beamId), 'f', precision );
611 if (isPercentage(sensor))
612 lastValue = i18nc("units", "%1%", lastValue);
613 else if( !sensor->unit().isEmpty() ) {
614 lastValue = i18nc("units", QString(QLatin1String("%1 ") + sensor->unit()).toUtf8().constData(), lastValue);
615 }
616 }
617
618 if(sensor->maxValue != 0 && !isPercentage(sensor)) {
619 //Use a multi length string incase we do not have enough room
620 lastValue = i18n("%1 of %2", lastValue, mPlotter->valueAsString(sensor->maxValue, precision) ) + "\xc2\x9c" + lastValue;
621 }
622 } else {
623 lastValue = i18n("Error");
624 }
625 static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(beamId)))->widget())->setValueText(lastValue);
626 }
627 }
628
629 }
630 mSampleBuf.clear();
631 }
timerTick()632 void FancyPlotter::timerTick() //virtual
633 {
634 if(mNumAnswers < sensors().count())
635 sendDataToPlotter(); //we haven't received enough answers yet, but plot what we do have
636 mNumAnswers = 0;
637 SensorDisplay::timerTick();
638 }
plotterAxisScaleChanged()639 void FancyPlotter::plotterAxisScaleChanged()
640 {
641 //Prevent this being called recursively
642 disconnect(mPlotter, &KSignalPlotter::axisScaleChanged, this, &FancyPlotter::plotterAxisScaleChanged);
643 KLocalizedString unit;
644 double value = mPlotter->currentMaximumRangeValue();
645 if(mUnit == QLatin1String("KiB")) {
646 if(value >= 1024*1024*1024*0.7) { //If it's over 0.7TiB, then set the scale to terabytes
647 mPlotter->setScaleDownBy(1024*1024*1024);
648 unit = ki18nc("units", "%1 TiB"); // the unit - terabytes
649 } else if(value >= 1024*1024*0.7) { //If it's over 0.7GiB, then set the scale to gigabytes
650 mPlotter->setScaleDownBy(1024*1024);
651 unit = ki18nc("units", "%1 GiB"); // the unit - gigabytes
652 } else if(value > 1024) {
653 mPlotter->setScaleDownBy(1024);
654 unit = ki18nc("units", "%1 MiB"); // the unit - megabytes
655 } else {
656 mPlotter->setScaleDownBy(1);
657 unit = ki18nc("units", "%1 KiB"); // the unit - kilobytes
658 }
659 } else if(mUnit == QLatin1String("KiB/s")) {
660 if(value >= 1024*1024*1024*0.7) { //If it's over 0.7TiB, then set the scale to terabytes
661 mPlotter->setScaleDownBy(1024*1024*1024);
662 unit = ki18nc("units", "%1 TiB/s"); // the unit - terabytes per second
663 } else if(value >= 1024*1024*0.7) { //If it's over 0.7GiB, then set the scale to gigabytes
664 mPlotter->setScaleDownBy(1024*1024);
665 unit = ki18nc("units", "%1 GiB/s"); // the unit - gigabytes per second
666 } else if(value > 1024) {
667 mPlotter->setScaleDownBy(1024);
668 unit = ki18nc("units", "%1 MiB/s"); // the unit - megabytes per second
669 } else {
670 mPlotter->setScaleDownBy(1);
671 unit = ki18nc("units", "%1 KiB/s"); // the unit - kilobytes per second
672 }
673 } else if(mUnit == QLatin1String("%")) {
674 mPlotter->setScaleDownBy(1);
675 unit = ki18nc("units", "%1%"); //the unit - percentage
676 } else if(mUnit.isEmpty()) {
677 unit = ki18nc("unitless - just a number", "%1");
678 } else {
679 #if 0 // the strings are here purely for translation
680 NOOP_I18NC("units", "%1 1/s"); // the unit - 1 per second
681 NOOP_I18NC("units", "%1 s"); // the unit - seconds
682 NOOP_I18NC("units", "%1 MHz"); // the unit - frequency megahertz
683 #endif
684 mPlotter->setScaleDownBy(1);
685 //translate any others
686 unit = ki18nc("units", QString(QLatin1String("%1 ") + mUnit).toUtf8().constData());
687 }
688 mPlotter->setUnit(unit);
689 //reconnect
690 connect(mPlotter, &KSignalPlotter::axisScaleChanged, this, &FancyPlotter::plotterAxisScaleChanged);
691 }
answerReceived(int id,const QList<QByteArray> & answerlist)692 void FancyPlotter::answerReceived( int id, const QList<QByteArray> &answerlist )
693 {
694 QByteArray answer;
695
696 if(!answerlist.isEmpty()) answer = answerlist[0];
697 if ( (uint)id < 100 ) {
698 //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
699 if(id >= sensors().count())
700 return; //just ignore if we get a result for an invalid sensor
701 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(id));
702 int beamId = sensor->beamId;
703 double value = answer.toDouble();
704 while(beamId > mSampleBuf.count())
705 mSampleBuf.append(0); //we might have sensors missing so set their values to zero
706
707 if(beamId == mSampleBuf.count()) {
708 mSampleBuf.append( value );
709 } else {
710 mSampleBuf[beamId] += value; //If we get two answers for the same beamid, we should add them together. That's how the summation works
711 }
712 sensor->lastValue = value;
713 /* We received something, so the sensor is probably ok. */
714 sensorError( id, false );
715
716 if(++mNumAnswers == sensors().count())
717 sendDataToPlotter(); //we have received all the answers so start plotting
718 } else if ( id >= 100 && id < 200 ) {
719 if( (id - 100) >= sensors().count())
720 return; //just ignore if we get a result for an invalid sensor
721 KSGRD::SensorFloatInfo info( answer );
722 QString unit = info.unit();
723 if(unit.toUpper() == QLatin1String("KB") || unit.toUpper() == QLatin1String("KIB"))
724 unit = QStringLiteral("KiB");
725 if(unit.toUpper() == QLatin1String("KB/S") || unit.toUpper() == QLatin1String("KIB/S"))
726 unit = QStringLiteral("KiB/s");
727
728 if(id == 100) //if we are the first sensor, just use that sensors units as the global unit
729 mUnit = unit;
730 else if(unit != mUnit)
731 mUnit = QLatin1String(""); //if the units don't match, then set the units on the scale to empty, to avoid any confusion
732
733 mSensorReportedMax = qMax(mSensorReportedMax, info.max());
734 mSensorReportedMin = qMin(mSensorReportedMin, info.min());
735
736 if ( !mUseManualRange )
737 mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
738 plotterAxisScaleChanged();
739
740 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(id - 100));
741 sensor->maxValue = info.max();
742 sensor->minValue = info.min();
743 sensor->setUnit( unit );
744 sensor->setDescription( info.name() );
745
746 QString summationName = sensor->summationName;
747 int beamId = sensor->beamId;
748
749 Q_ASSERT(beamId < mPlotter->numBeams());
750 Q_ASSERT(beamId < mLabelLayout->count());
751
752 if(summationName.isEmpty())
753 static_cast<FancyPlotterLabel *>((static_cast<QWidgetItem *>(mLabelLayout->itemAt(beamId)))->widget())->setLabel(info.name(), mPlotter->beamColor(beamId));
754
755 } else if( id == 200) {
756 /* FIXME This doesn't check the host! */
757 if(!mSensorsToAdd.isEmpty()) {
758 foreach(SensorToAdd *sensor, mSensorsToAdd) {
759 int beamId = mBeams; //Assign the next sensor to the next available beamId
760 for ( int i = 0; i < answerlist.count(); ++i ) {
761 if ( answerlist[ i ].isEmpty() )
762 continue;
763 QString sensorName = QString::fromUtf8(answerlist[ i ].split('\t')[0]);
764 if(sensor->name.exactMatch(sensorName)) {
765 if(sensor->summationName.isEmpty())
766 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
767 QColor color;
768 if(!sensor->colors.isEmpty() )
769 color = sensor->colors.takeFirst();
770 else if(KSGRD::Style->numSensorColors() != 0)
771 color = KSGRD::Style->sensorColor( beamId % KSGRD::Style->numSensorColors());
772 addSensor( sensor->hostname, sensorName,
773 (sensor->type.isEmpty()) ? QStringLiteral("float") : sensor->type
774 , QLatin1String(""), color, sensor->name.pattern(), beamId, sensor->summationName);
775 }
776 }
777 }
778 qDeleteAll(mSensorsToAdd);
779 mSensorsToAdd.clear();
780 }
781 }
782 }
783
restoreSettings(QDomElement & element)784 bool FancyPlotter::restoreSettings( QDomElement &element )
785 {
786 mUseManualRange = element.attribute( QStringLiteral("manualRange"), QStringLiteral("0") ).toInt();
787
788 if(mUseManualRange) {
789 mSensorManualMax = element.attribute( QStringLiteral("max") ).toDouble();
790 mSensorManualMin = element.attribute( QStringLiteral("min") ).toDouble();
791 mPlotter->changeRange( mSensorManualMin, mSensorManualMax );
792 } else {
793 mPlotter->changeRange( mSensorReportedMin, mSensorReportedMax );
794 }
795
796 mPlotter->setUseAutoRange(element.attribute( QStringLiteral("autoRange"), QStringLiteral("1") ).toInt());
797
798 // Do not restore the color settings from a previous version
799 int version = element.attribute(QStringLiteral("version"), QStringLiteral("0")).toInt();
800
801 mPlotter->setShowVerticalLines( element.attribute( QStringLiteral("vLines"), QStringLiteral("0") ).toUInt() );
802 mPlotter->setVerticalLinesDistance( element.attribute( QStringLiteral("vDistance"), QStringLiteral("30") ).toUInt() );
803 mPlotter->setVerticalLinesScroll( element.attribute( QStringLiteral("vScroll"), QStringLiteral("0") ).toUInt() );
804 mPlotter->setHorizontalScale( element.attribute( QStringLiteral("hScale"), QStringLiteral("6") ).toUInt() );
805
806 mPlotter->setShowHorizontalLines( element.attribute( QStringLiteral("hLines"), QStringLiteral("1") ).toUInt() );
807 mPlotter->setStackGraph( element.attribute(QStringLiteral("stacked"), QStringLiteral("0")).toInt());
808
809 QString filename = element.attribute( QStringLiteral("svgBackground"));
810 if (!filename.isEmpty() && filename[0] == QLatin1Char('/')) {
811 filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("ksysguard/") + filename);
812 }
813 mPlotter->setSvgBackground( filename );
814 if(version >= 1) {
815 mPlotter->setShowAxis( element.attribute( QStringLiteral("labels"), QStringLiteral("1") ).toUInt() );
816 uint fontsize = element.attribute( QStringLiteral("fontSize"), QStringLiteral("0")).toUInt();
817 if(fontsize == 0) fontsize = KSGRD::Style->fontSize();
818 QFont font;
819 font.setPointSize( fontsize );
820 mPlotter->setFont( font );
821 }
822 QDomNodeList dnList = element.elementsByTagName( QStringLiteral("beam") );
823 for ( int i = 0; i < dnList.count(); ++i ) {
824 QDomElement el = dnList.item( i ).toElement();
825 if(el.hasAttribute(QStringLiteral("regexpSensorName"))) {
826 SensorToAdd *sensor = new SensorToAdd();
827 sensor->name = QRegExp(el.attribute(QStringLiteral("regexpSensorName")));
828 sensor->hostname = el.attribute( QStringLiteral("hostName") );
829 sensor->type = el.attribute( QStringLiteral("sensorType") );
830 sensor->summationName = el.attribute(QStringLiteral("summationName"));
831 QStringList colors = el.attribute(QStringLiteral("color")).split(QLatin1Char(','));
832 bool ok;
833 foreach(const QString &color, colors) {
834 int c = color.toUInt( &ok, 0 );
835 if(ok) {
836 QColor col( (c & 0xff0000) >> 16, (c & 0xff00) >> 8, (c & 0xff), (c & 0xff000000) >> 24);
837 if(col.isValid()) {
838 if(col.alpha() == 0) col.setAlpha(255);
839 sensor->colors << col;
840 }
841 else
842 sensor->colors << KSGRD::Style->sensorColor( i );
843 }
844 else
845 sensor->colors << KSGRD::Style->sensorColor( i );
846 }
847 mSensorsToAdd.append(sensor);
848 sendRequest( sensor->hostname, QStringLiteral("monitors"), 200 );
849 } else
850 addSensor( el.attribute( QStringLiteral("hostName") ), el.attribute( QStringLiteral("sensorName") ),
851 ( el.attribute( QStringLiteral("sensorType") ).isEmpty() ? QStringLiteral("float") :
852 el.attribute( QStringLiteral("sensorType") ) ), QLatin1String(""), restoreColor( el, QStringLiteral("color"),
853 KSGRD::Style->sensorColor( i ) ), QString(), mBeams, el.attribute(QStringLiteral("summationName")) );
854 }
855
856 SensorDisplay::restoreSettings( element );
857
858 return true;
859 }
860
saveSettings(QDomDocument & doc,QDomElement & element)861 bool FancyPlotter::saveSettings( QDomDocument &doc, QDomElement &element)
862 {
863 element.setAttribute( QStringLiteral("autoRange"), mPlotter->useAutoRange() );
864
865 element.setAttribute( QStringLiteral("manualRange"), mUseManualRange );
866 if(mUseManualRange) {
867 element.setAttribute( QStringLiteral("min"), mSensorManualMin );
868 element.setAttribute( QStringLiteral("max"), mSensorManualMax );
869 }
870
871
872 element.setAttribute( QStringLiteral("vLines"), mPlotter->showVerticalLines() );
873 element.setAttribute( QStringLiteral("vDistance"), mPlotter->verticalLinesDistance() );
874 element.setAttribute( QStringLiteral("vScroll"), mPlotter->verticalLinesScroll() );
875
876 element.setAttribute( QStringLiteral("hScale"), mPlotter->horizontalScale() );
877
878 element.setAttribute( QStringLiteral("hLines"), mPlotter->showHorizontalLines() );
879
880 element.setAttribute( QStringLiteral("svgBackground"), mPlotter->svgBackground() );
881 element.setAttribute( QStringLiteral("stacked"), mPlotter->stackGraph() );
882
883 element.setAttribute( QStringLiteral("version"), 1 );
884 element.setAttribute( QStringLiteral("labels"), mPlotter->showAxis() );
885 element.setAttribute( QStringLiteral("fontSize"), mPlotter->font().pointSize() );
886
887 QHash<QString,QDomElement> hash;
888 int beamId = -1;
889 for ( int i = 0; i < sensors().size(); ++i ) {
890 FPSensorProperties *sensor = static_cast<FPSensorProperties *>(sensors().at(i));
891 if(sensor->beamId == beamId)
892 continue;
893 beamId = sensor->beamId;
894
895 QString regExpName = sensor->regExpName();
896 if(!regExpName.isEmpty() && hash.contains( regExpName )) {
897 QDomElement oldBeam = hash.value(regExpName);
898 saveColorAppend( oldBeam, QStringLiteral("color"), mPlotter->beamColor( beamId ) );
899 } else {
900 QDomElement beam = doc.createElement( QStringLiteral("beam") );
901 element.appendChild( beam );
902 beam.setAttribute( QStringLiteral("hostName"), sensor->hostName() );
903 if(regExpName.isEmpty())
904 beam.setAttribute( QStringLiteral("sensorName"), sensor->name() );
905 else {
906 beam.setAttribute( QStringLiteral("regexpSensorName"), sensor->regExpName() );
907 hash[regExpName] = beam;
908 }
909 if(!sensor->summationName.isEmpty())
910 beam.setAttribute( QStringLiteral("summationName"), sensor->summationName);
911 beam.setAttribute( QStringLiteral("sensorType"), sensor->type() );
912 saveColor( beam, QStringLiteral("color"), mPlotter->beamColor( beamId ) );
913 }
914 }
915 SensorDisplay::saveSettings( doc, element );
916
917 return true;
918 }
919
hasSettingsDialog() const920 bool FancyPlotter::hasSettingsDialog() const
921 {
922 return true;
923 }
924
FPSensorProperties()925 FPSensorProperties::FPSensorProperties()
926 {
927 }
928
FPSensorProperties(const QString & hostName,const QString & name,const QString & type,const QString & description,const QColor & color,const QString & regexpName,int beamId_,const QString & summationName_)929 FPSensorProperties::FPSensorProperties( const QString &hostName,
930 const QString &name,
931 const QString &type,
932 const QString &description,
933 const QColor &color,
934 const QString ®expName,
935 int beamId_,
936 const QString &summationName_ )
937 : KSGRD::SensorProperties( hostName, name, type, description),
938 mColor( color )
939 {
940 setRegExpName(regexpName);
941 beamId = beamId_;
942 summationName = summationName_;
943 maxValue = 0;
944 minValue = 0;
945 lastValue = 0;
946 isInteger = (type == QLatin1String("integer"));
947 }
948
~FPSensorProperties()949 FPSensorProperties::~FPSensorProperties()
950 {
951 }
952
setColor(const QColor & color)953 void FPSensorProperties::setColor( const QColor &color )
954 {
955 mColor = color;
956 }
957
color() const958 QColor FPSensorProperties::color() const
959 {
960 return mColor;
961 }
962
963
964
965