~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to ksysguard/gui/SensorDisplayLib/FancyPlotter.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

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