1 /*
2     KSysGuard, the KDE System Guard
3 
4     Copyright (c) 2001 Tobias Koenig <tokoe@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 as published by the Free Software Foundation; either
9     version 2 of the License, or (at your option) any later version.
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 <QApplication>
23 #include <QAbstractTableModel>
24 #include <QDate>
25 #include <QFile>
26 #include <QTextStream>
27 #include <QContextMenuEvent>
28 #include <QHeaderView>
29 #include <QMenu>
30 #include <QHBoxLayout>
31 #include <QDomNodeList>
32 #include <QDomDocument>
33 #include <QDomElement>
34 
35 #include <KIconLoader>
36 #include <KLocalizedString>
37 #include <KNotification>
38 #include <ksgrd/SensorManager.h>
39 #include "StyleEngine.h"
40 
41 #include "SensorLoggerDlg.h"
42 #include "SensorLoggerSettings.h"
43 #include "SensorLogger.h"
44 
45 #define NONE -1
46 
LogSensorView(QWidget * parent)47 LogSensorView::LogSensorView( QWidget *parent )
48  : QTreeView( parent )
49 {
50 }
51 
contextMenuEvent(QContextMenuEvent * event)52 void LogSensorView::contextMenuEvent( QContextMenuEvent *event )
53 {
54   const QModelIndex index = indexAt( event->pos() );
55 
56   emit contextMenuRequest( index, viewport()->mapToGlobal( event->pos() ) );
57 }
58 
59 class LogSensorModel : public QAbstractTableModel
60 {
61   public:
LogSensorModel(QObject * parent=nullptr)62     LogSensorModel( QObject *parent = nullptr )
63       : QAbstractTableModel( parent )
64     {
65     }
66 
columnCount(const QModelIndex & parent=QModelIndex ()) const67     int columnCount( const QModelIndex &parent = QModelIndex() ) const override
68     {
69       Q_UNUSED( parent );
70 
71       return 5;
72     }
73 
rowCount(const QModelIndex & parent=QModelIndex ()) const74     int rowCount( const QModelIndex &parent = QModelIndex() ) const override
75     {
76       Q_UNUSED( parent );
77 
78       return mSensors.count();
79     }
80 
data(const QModelIndex & index,int role=Qt::DisplayRole) const81     QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override
82     {
83       if ( !index.isValid() )
84         return QVariant();
85 
86       if ( index.row() >= mSensors.count() || index.row() < 0 )
87         return QVariant();
88 
89       LogSensor *sensor = mSensors[ index.row() ];
90 
91       if ( role == Qt::DisplayRole ) {
92         switch ( index.column() ) {
93           case 1:
94             return sensor->timerInterval();
95           case 2:
96             return sensor->sensorName();
97           case 3:
98             return sensor->hostName();
99           case 4:
100             return sensor->fileName();
101         }
102       } else if ( role == Qt::DecorationRole ) {
103         static QPixmap runningPixmap = KIconLoader::global()->loadIcon( QStringLiteral("running"), KIconLoader::Small, KIconLoader::SizeSmall );
104         static QPixmap waitingPixmap = KIconLoader::global()->loadIcon( QStringLiteral("waiting"), KIconLoader::Small, KIconLoader::SizeSmall );
105 
106         if ( index.column() == 0 ) {
107           if ( sensor->isLogging() )
108             return runningPixmap;
109           else
110             return waitingPixmap;
111         }
112       } else if ( role == Qt::ForegroundRole ) {
113         if ( sensor->limitReached() )
114           return mAlarmColor;
115         else
116           return mForegroundColor;
117       } else if ( role == Qt::BackgroundRole ) {
118           return mBackgroundColor;
119       }
120 
121       return QVariant();
122     }
123 
headerData(int section,Qt::Orientation orientation,int role=Qt::DisplayRole) const124     QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override
125     {
126       if ( orientation == Qt::Vertical )
127         return QVariant();
128 
129       if ( role == Qt::DisplayRole ) {
130         switch ( section ) {
131           case 0:
132             return i18nc("@title:column", "Logging");
133           case 1:
134             return i18nc("@title:column", "Timer Interval");
135           case 2:
136             return i18nc("@title:column", "Sensor Name");
137           case 3:
138             return i18nc("@title:column", "Host Name");
139           case 4:
140             return i18nc("@title:column", "Log File");
141           default:
142             return QVariant();
143         }
144       }
145 
146       return QVariant();
147     }
148 
addSensor(LogSensor * sensor)149     void addSensor( LogSensor *sensor )
150     {
151       mSensors.append( sensor );
152 
153       connect( sensor, SIGNAL(changed()), this, SIGNAL(layoutChanged()) );
154 
155       emit layoutChanged();
156     }
157 
removeSensor(LogSensor * sensor)158     void removeSensor( LogSensor *sensor )
159     {
160       delete mSensors.takeAt( mSensors.indexOf( sensor ) );
161 
162       emit layoutChanged();
163     }
164 
sensor(const QModelIndex & index) const165     LogSensor* sensor( const QModelIndex &index ) const
166     {
167       if ( !index.isValid() || index.row() >= mSensors.count() || index.row() < 0 )
168         return nullptr;
169 
170       return mSensors[ index.row() ];
171     }
172 
clear()173     void clear()
174     {
175       qDeleteAll( mSensors );
176       mSensors.clear();
177     }
178 
sensors() const179     const QList<LogSensor*> sensors() const
180     {
181       return mSensors;
182     }
183 
setForegroundColor(const QColor & color)184     void setForegroundColor( const QColor &color ) { mForegroundColor = color; }
foregroundColor() const185     QColor foregroundColor() const { return mForegroundColor; }
186 
setBackgroundColor(const QColor & color)187     void setBackgroundColor( const QColor &color ) { mBackgroundColor = color; }
backgroundColor() const188     QColor backgroundColor() const { return mBackgroundColor; }
189 
setAlarmColor(const QColor & color)190     void setAlarmColor( const QColor &color ) { mAlarmColor = color; }
alarmColor() const191     QColor alarmColor() const { return mAlarmColor; }
192 
193   private:
194 
195     QColor mForegroundColor;
196     QColor mBackgroundColor;
197     QColor mAlarmColor;
198 
199     QList<LogSensor*> mSensors;
200 };
201 
LogSensor(QObject * parent)202 LogSensor::LogSensor( QObject *parent )
203   : QObject( parent ),
204     mTimerID( NONE ),
205     mLowerLimitActive( false ),
206     mUpperLimitActive( 0 ),
207     mLowerLimit( 0 ),
208     mUpperLimit( 0 ),
209     mLimitReached( false )
210 {
211 }
212 
~LogSensor()213 LogSensor::~LogSensor()
214 {
215 }
216 
setHostName(const QString & name)217 void LogSensor::setHostName( const QString& name )
218 {
219   mHostName = name;
220 }
221 
hostName() const222 QString LogSensor::hostName() const
223 {
224   return mHostName;
225 }
226 
setSensorName(const QString & name)227 void LogSensor::setSensorName( const QString& name )
228 {
229   mSensorName = name;
230 }
231 
sensorName() const232 QString LogSensor::sensorName() const
233 {
234   return mSensorName;
235 }
236 
setFileName(const QString & name)237 void LogSensor::setFileName( const QString& name )
238 {
239   mFileName = name;
240 }
241 
fileName() const242 QString LogSensor::fileName() const
243 {
244   return mFileName;
245 }
246 
setUpperLimitActive(bool value)247 void LogSensor::setUpperLimitActive( bool value )
248 {
249   mUpperLimitActive = value;
250 }
251 
upperLimitActive() const252 bool LogSensor::upperLimitActive() const
253 {
254   return mUpperLimitActive;
255 }
256 
setLowerLimitActive(bool value)257 void LogSensor::setLowerLimitActive( bool value )
258 {
259   mLowerLimitActive = value;
260 }
261 
lowerLimitActive() const262 bool LogSensor::lowerLimitActive() const
263 {
264   return mLowerLimitActive;
265 }
266 
setUpperLimit(double value)267 void LogSensor::setUpperLimit( double value )
268 {
269   mUpperLimit = value;
270 }
271 
upperLimit() const272 double LogSensor::upperLimit() const
273 {
274   return mUpperLimit;
275 }
276 
setLowerLimit(double value)277 void LogSensor::setLowerLimit( double value )
278 {
279   mLowerLimit = value;
280 }
281 
lowerLimit() const282 double LogSensor::lowerLimit() const
283 {
284   return mLowerLimit;
285 }
286 
setTimerInterval(int interval)287 void LogSensor::setTimerInterval( int interval )
288 {
289   mTimerInterval = interval;
290 
291   if ( mTimerID != NONE ) {
292     timerOff();
293     timerOn();
294   }
295 }
296 
timerInterval() const297 int LogSensor::timerInterval() const
298 {
299   return mTimerInterval;
300 }
301 
isLogging() const302 bool LogSensor::isLogging() const
303 {
304   return mTimerID != NONE;
305 }
306 
limitReached() const307 bool LogSensor::limitReached() const
308 {
309   return mLimitReached;
310 }
311 
timerOff()312 void LogSensor::timerOff()
313 {
314   if ( mTimerID > 0 )
315     killTimer( mTimerID );
316   mTimerID = NONE;
317 }
318 
timerOn()319 void LogSensor::timerOn()
320 {
321   mTimerID = startTimer( mTimerInterval * 1000 );
322 }
323 
startLogging()324 void LogSensor::startLogging()
325 {
326   timerOn();
327 }
328 
stopLogging()329 void LogSensor::stopLogging()
330 {
331   timerOff();
332 }
333 
timerEvent(QTimerEvent * event)334 void LogSensor::timerEvent ( QTimerEvent * event )
335 {
336   Q_UNUSED(event);
337   KSGRD::SensorMgr->sendRequest( mHostName, mSensorName, static_cast<KSGRD::SensorClient*>(this), 42 );
338 }
339 
answerReceived(int id,const QList<QByteArray> & answer)340 void LogSensor::answerReceived( int id, const QList<QByteArray>& answer ) //virtual
341 {
342   QFile mLogFile( mFileName );
343 
344   if ( !mLogFile.open( QIODevice::ReadWrite | QIODevice::Append ) ) {
345     stopLogging();
346     return;
347   }
348 
349   switch ( id ) {
350     case 42: {
351       QTextStream stream( &mLogFile );
352       double value = 0;
353       if ( !answer.isEmpty() )
354         value = answer[ 0 ].toDouble();
355 
356       if ( mLowerLimitActive && value < mLowerLimit ) {
357         timerOff();
358         mLimitReached = true;
359 
360         // send notification
361         KNotification::event( QStringLiteral("sensor_alarm"), QStringLiteral( "sensor '%1' at '%2' reached lower limit" )
362                             .arg( mSensorName ).arg( mHostName), QPixmap(), nullptr );
363 
364         timerOn();
365       } else if ( mUpperLimitActive && value > mUpperLimit ) {
366         timerOff();
367         mLimitReached = true;
368 
369         // send notification
370         KNotification::event( QStringLiteral("sensor_alarm"), QStringLiteral( "sensor '%1' at '%2' reached upper limit" )
371                             .arg( mSensorName).arg( mHostName), QPixmap(), nullptr );
372 
373         timerOn();
374       } else {
375         mLimitReached = false;
376       }
377 
378       const QDate date = QDateTime::currentDateTime().date();
379       const QTime time = QDateTime::currentDateTime().time();
380 
381       stream << QStringLiteral( "%1 %2 %3 %4 %5: %6\n" ).arg( QLocale().monthName( date.month() ) )
382                                                  .arg( date.day() ).arg( time.toString() )
383                                                  .arg( mHostName).arg( mSensorName ).arg( value );
384     }
385   }
386 
387   emit changed();
388 
389   mLogFile.close();
390 }
391 
SensorLogger(QWidget * parent,const QString & title,SharedSettings * workSheetSettings)392 SensorLogger::SensorLogger( QWidget *parent, const QString& title, SharedSettings *workSheetSettings )
393   : KSGRD::SensorDisplay( parent, title, workSheetSettings )
394 {
395   mModel = new LogSensorModel( this );
396   mModel->setForegroundColor( KSGRD::Style->firstForegroundColor() );
397   mModel->setBackgroundColor( KSGRD::Style->backgroundColor() );
398   mModel->setAlarmColor( KSGRD::Style->alarmColor() );
399 
400   QLayout *layout = new QHBoxLayout(this);
401   mView = new LogSensorView( this );
402   layout->addWidget(mView);
403   setLayout(layout);
404 
405   mView->header()->setStretchLastSection( true );
406   mView->setRootIsDecorated( false );
407   mView->setItemsExpandable( false );
408   mView->setModel( mModel );
409   setPlotterWidget( mView );
410 
411   connect( mView, &LogSensorView::contextMenuRequest,
412            this, &SensorLogger::contextMenuRequest );
413 
414   QPalette palette = mView->palette();
415   palette.setColor( QPalette::Base, KSGRD::Style->backgroundColor() );
416   mView->setPalette( palette );
417 
418   setTitle( i18n( "Sensor Logger" ) );
419   setMinimumSize( 50, 25 );
420 }
421 
~SensorLogger(void)422 SensorLogger::~SensorLogger(void)
423 {
424 }
425 
addSensor(const QString & hostName,const QString & sensorName,const QString & sensorType,const QString &)426 bool SensorLogger::addSensor( const QString& hostName, const QString& sensorName, const QString& sensorType, const QString& )
427 {
428   if ( sensorType != QLatin1String("integer") && sensorType != QLatin1String("float") )
429     return false;
430 
431   SensorLoggerDlg dlg( this );
432 
433   if ( dlg.exec() ) {
434     if ( !dlg.fileName().isEmpty() ) {
435       LogSensor *sensor = new LogSensor( mModel );
436 
437       sensor->setHostName( hostName );
438       sensor->setSensorName( sensorName );
439       sensor->setFileName( dlg.fileName() );
440       sensor->setTimerInterval( dlg.timerInterval() );
441       sensor->setLowerLimitActive( dlg.lowerLimitActive() );
442       sensor->setUpperLimitActive( dlg.upperLimitActive() );
443       sensor->setLowerLimit( dlg.lowerLimit() );
444       sensor->setUpperLimit( dlg.upperLimit() );
445 
446       mModel->addSensor( sensor );
447     }
448   } else {
449     return false;  //User cancelled dialog, so don't add sensor logger
450   }
451 
452   return true;
453 }
454 
editSensor(LogSensor * sensor)455 bool SensorLogger::editSensor( LogSensor* sensor )
456 {
457   SensorLoggerDlg dlg( this );
458 
459   dlg.setFileName( sensor->fileName() );
460   dlg.setTimerInterval( sensor->timerInterval() );
461   dlg.setLowerLimitActive( sensor->lowerLimitActive() );
462   dlg.setLowerLimit( sensor->lowerLimit() );
463   dlg.setUpperLimitActive( sensor->upperLimitActive() );
464   dlg.setUpperLimit( sensor->upperLimit() );
465 
466   if ( dlg.exec() ) {
467     if ( !dlg.fileName().isEmpty() ) {
468       sensor->setFileName( dlg.fileName() );
469       sensor->setTimerInterval( dlg.timerInterval() );
470       sensor->setLowerLimitActive( dlg.lowerLimitActive() );
471       sensor->setUpperLimitActive( dlg.upperLimitActive() );
472       sensor->setLowerLimit( dlg.lowerLimit() );
473       sensor->setUpperLimit( dlg.upperLimit() );
474     }
475   }
476 
477   return true;
478 }
479 
configureSettings()480 void SensorLogger::configureSettings()
481 {
482   SensorLoggerSettings dlg( this );
483 
484   dlg.setTitle( title() );
485   dlg.setForegroundColor( mModel->foregroundColor() );
486   dlg.setBackgroundColor( mModel->backgroundColor() );
487   dlg.setAlarmColor( mModel->alarmColor() );
488 
489   if ( dlg.exec() ) {
490     setTitle( dlg.title() );
491 
492     mModel->setForegroundColor( dlg.foregroundColor() );
493     mModel->setBackgroundColor( dlg.backgroundColor() );
494     mModel->setAlarmColor( dlg.alarmColor() );
495 
496     QPalette palette = mView->palette();
497     palette.setColor( QPalette::Base, dlg.backgroundColor() );
498     mView->setPalette( palette );
499   }
500 }
501 
applyStyle()502 void SensorLogger::applyStyle()
503 {
504   mModel->setForegroundColor( KSGRD::Style->firstForegroundColor() );
505   mModel->setBackgroundColor( KSGRD::Style->backgroundColor() );
506   mModel->setAlarmColor( KSGRD::Style->alarmColor() );
507 
508   QPalette palette = mView->palette();
509   palette.setColor( QPalette::Base, KSGRD::Style->backgroundColor() );
510   mView->setPalette( palette );
511 }
512 
restoreSettings(QDomElement & element)513 bool SensorLogger::restoreSettings( QDomElement& element )
514 {
515   mModel->setForegroundColor( restoreColor( element, QStringLiteral("textColor"), Qt::green) );
516   mModel->setBackgroundColor( restoreColor( element, QStringLiteral("backgroundColor"), Qt::black ) );
517   mModel->setAlarmColor( restoreColor( element, QStringLiteral("alarmColor"), Qt::red ) );
518 
519   mModel->clear();
520 
521   QDomNodeList dnList = element.elementsByTagName( QStringLiteral("logsensors") );
522   for ( int i = 0; i < dnList.count(); i++ ) {
523     QDomElement element = dnList.item( i ).toElement();
524     LogSensor* sensor = new LogSensor( mModel );
525 
526     sensor->setHostName( element.attribute(QStringLiteral("hostName")) );
527     sensor->setSensorName( element.attribute(QStringLiteral("sensorName")) );
528     sensor->setFileName( element.attribute(QStringLiteral("fileName")) );
529     sensor->setTimerInterval( element.attribute(QStringLiteral("timerInterval")).toInt() );
530     sensor->setLowerLimitActive( element.attribute(QStringLiteral("lowerLimitActive")).toInt() );
531     sensor->setLowerLimit( element.attribute(QStringLiteral("lowerLimit")).toDouble() );
532     sensor->setUpperLimitActive( element.attribute(QStringLiteral("upperLimitActive")).toInt() );
533     sensor->setUpperLimit( element.attribute(QStringLiteral("upperLimit")).toDouble() );
534 
535     mModel->addSensor( sensor );
536   }
537 
538   SensorDisplay::restoreSettings( element );
539 
540   QPalette palette = mView->palette();
541   palette.setColor( QPalette::Base, mModel->backgroundColor() );
542   mView->setPalette( palette );
543 
544   return true;
545 }
546 
saveSettings(QDomDocument & doc,QDomElement & element)547 bool SensorLogger::saveSettings( QDomDocument& doc, QDomElement& element )
548 {
549   saveColor( element, QStringLiteral("textColor"), mModel->foregroundColor() );
550   saveColor( element, QStringLiteral("backgroundColor"), mModel->backgroundColor() );
551   saveColor( element, QStringLiteral("alarmColor"), mModel->alarmColor() );
552 
553   const QList<LogSensor*> sensors = mModel->sensors();
554   for ( int i = 0; i < sensors.count(); ++i ) {
555     LogSensor *sensor = sensors[ i ];
556     QDomElement log = doc.createElement( QStringLiteral("logsensors") );
557     log.setAttribute(QStringLiteral("sensorName"), sensor->sensorName());
558     log.setAttribute(QStringLiteral("hostName"), sensor->hostName());
559     log.setAttribute(QStringLiteral("fileName"), sensor->fileName());
560     log.setAttribute(QStringLiteral("timerInterval"), sensor->timerInterval());
561     log.setAttribute(QStringLiteral("lowerLimitActive"), QStringLiteral("%1").arg(sensor->lowerLimitActive()));
562     log.setAttribute(QStringLiteral("lowerLimit"), QStringLiteral("%1").arg(sensor->lowerLimit()));
563     log.setAttribute(QStringLiteral("upperLimitActive"), QStringLiteral("%1").arg(sensor->upperLimitActive()));
564     log.setAttribute(QStringLiteral("upperLimit"), QStringLiteral("%1").arg(sensor->upperLimit()));
565 
566     element.appendChild( log );
567   }
568 
569   SensorDisplay::saveSettings( doc, element );
570 
571   return true;
572 }
573 
answerReceived(int,const QList<QByteArray> &)574 void SensorLogger::answerReceived( int, const QList<QByteArray>& ) //virtual
575 {
576  // we do not use this, since all answers are received by the LogSensors
577 }
578 
contextMenuRequest(const QModelIndex & index,const QPoint & point)579 void SensorLogger::contextMenuRequest( const QModelIndex &index, const QPoint &point )
580 {
581   LogSensor *sensor = mModel->sensor( index );
582 
583   QMenu pm;
584 
585   QAction *action = nullptr;
586   if (hasSettingsDialog()) {
587     action = pm.addAction(i18n("&Properties"));
588     action->setData( 1 );
589   }
590   if(!mSharedSettings->locked) {
591 
592     action = pm.addAction(i18n("&Remove Display"));
593     action->setData( 2 );
594 
595     pm.addSeparator();
596 
597     action = pm.addAction(i18n("&Remove Sensor"));
598     action->setData( 3 );
599     if ( !sensor )
600       action->setEnabled( false );
601 
602     action = pm.addAction(i18n("&Edit Sensor..."));
603     action->setData( 4 );
604     if ( !sensor )
605       action->setEnabled( false );
606   }
607 
608   if ( sensor ) {
609     if ( sensor->isLogging() ) {
610       action = pm.addAction(i18n("St&op Logging"));
611       action->setData( 6 );
612     } else {
613       action = pm.addAction(i18n("S&tart Logging"));
614       action->setData( 5 );
615     }
616   }
617 
618   action = pm.exec( point );
619   if ( !action )
620     return;
621 
622   switch (action->data().toInt())
623   {
624     case 1:
625       configureSettings();
626       break;
627     case 2: {
628       KSGRD::SensorDisplay::DeleteEvent *ev = new KSGRD::SensorDisplay::DeleteEvent( this );
629       qApp->postEvent(parent(), ev);
630       break;
631       }
632     case 3:
633       if ( sensor )
634         mModel->removeSensor( sensor );
635       break;
636     case 4:
637       if ( sensor )
638         editSensor( sensor );
639       break;
640     case 5:
641       if ( sensor )
642         sensor->startLogging();
643       break;
644     case 6:
645       if ( sensor )
646         sensor->stopLogging();
647       break;
648   }
649 }
650 
651 
652