1 /*
2     KSysGuard, the KDE System Guard
3 
4     Copyright (c) 1999, 2000, 2001 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 <QCheckBox>
24 #include <QDebug>
25 #include <QDomElement>
26 #include <QPushButton>
27 #include <QHBoxLayout>
28 
29 #include <KLocalizedString>
30 #include <ksgrd/SensorManager.h>
31 #include "StyleEngine.h"
32 
33 #include "BarGraph.h"
34 #include "DancingBarsSettings.h"
35 
36 #include "DancingBars.h"
37 
DancingBars(QWidget * parent,const QString & title,SharedSettings * workSheetSettings)38 DancingBars::DancingBars( QWidget *parent, const QString &title, SharedSettings *workSheetSettings)
39   : KSGRD::SensorDisplay( parent, title, workSheetSettings)
40 {
41   mBars = 0;
42   mFlags = QBitArray( 100 );
43   mFlags.fill( false );
44 
45   QLayout *layout = new QHBoxLayout(this);
46   mPlotter = new BarGraph( this );
47   layout->addWidget(mPlotter);
48 
49   setMinimumSize( sizeHint() );
50 
51   /* All RMB clicks to the mPlotter widget will be handled by
52    * SensorDisplay::eventFilter. */
53   mPlotter->installEventFilter( this );
54 
55   setPlotterWidget( mPlotter );
56 
57 }
58 
~DancingBars()59 DancingBars::~DancingBars()
60 {
61 }
62 
configureSettings()63 void DancingBars::configureSettings()
64 {
65   DancingBarsSettings dlg( this );
66 
67   dlg.setTitle( title() );
68   dlg.setMinValue( mPlotter->getMin() );
69   dlg.setMaxValue( mPlotter->getMax() );
70 
71   double l, u;
72   bool la, ua;
73   mPlotter->getLimits( l, la, u, ua );
74 
75   dlg.setUseUpperLimit( ua );
76   dlg.setUpperLimit( u );
77 
78   dlg.setUseLowerLimit( la );
79   dlg.setLowerLimit( l );
80 
81   dlg.setForegroundColor( mPlotter->normalColor );
82   dlg.setAlarmColor( mPlotter->alarmColor );
83   dlg.setBackgroundColor( mPlotter->mBackgroundColor );
84   dlg.setFontSize( mPlotter->fontSize );
85 
86   SensorModelEntry::List list;
87   for(int i = 0; i < mBars; i++){
88     SensorModelEntry entry;
89     auto sensor = sensors().at( i );
90     entry.setId( i );
91     entry.setHostName( sensor->hostName() );
92     entry.setSensorName( KSGRD::SensorMgr->translateSensor( sensor->name() ) );
93     entry.setLabel( mPlotter->footers[ i ] );
94     entry.setUnit( KSGRD::SensorMgr->translateUnit( sensor->unit() ) );
95     entry.setStatus( sensor->isOk() ? i18n( "OK" ) : i18n( "Error" ) );
96 
97     list.append( entry );
98   }
99   dlg.setSensors( list );
100 
101   if ( !dlg.exec() )
102     return;
103 
104   setTitle( dlg.title() );
105   mPlotter->changeRange( dlg.minValue(), dlg.maxValue() );
106   mPlotter->setLimits( dlg.useLowerLimit() ?
107                        dlg.lowerLimit() : 0,
108                        dlg.useLowerLimit(),
109                        dlg.useUpperLimit() ?
110                        dlg.upperLimit() : 0,
111                        dlg.useUpperLimit() );
112 
113   mPlotter->normalColor = dlg.foregroundColor();
114   mPlotter->alarmColor = dlg.alarmColor();
115   mPlotter->mBackgroundColor = dlg.backgroundColor();
116   mPlotter->fontSize = dlg.fontSize();
117 
118   // Each deleted Id is relative to the length of the list
119   // at the time of deletion.
120   QList<uint> deletedIds = dlg.getDeletedIds();
121   for(int i = 0; i<deletedIds.count(); i++){
122 	  removeSensor(deletedIds[i]);
123   }
124 
125   // If the range has reset to "auto-range" then we need to ask for
126   // sensor info to re-calibrate. In answerReceived() there's a special-
127   // case recalibrating on sensor 0 (with id 100), so ask for that one.
128   if ( mPlotter->getMin() == 0.0 && mPlotter->getMax() == 0.0 && mBars > 0 ) {
129       const auto& sensor = sensors().at( 0 );
130       // The 100 is magic in answerReceived()
131       sendRequest( sensor->hostName(), sensor->name() + QLatin1Char('?'), 100 );
132   }
133 
134   // The remaining entries in the dialog are the ones that haven't been
135   // deleted, so that should match the remaining sensors. The dialog does
136   // not edit units or real sensor information, but can change the label.
137   // Reset the footer labels as needed.
138   const auto remainingSensors = dlg.sensors();
139   if (remainingSensors.count() == mPlotter->footers.count())
140   {
141     for(int i = 0; i < remainingSensors.count(); ++i) {
142       const auto newLabel = remainingSensors.at(i).label();
143       if (newLabel != mPlotter->footers[ i ]) {
144         mPlotter->footers[ i ] = newLabel;
145       }
146     }
147   }
148 
149   repaint();
150 }
151 
applyStyle()152 void DancingBars::applyStyle()
153 {
154   mPlotter->normalColor = KSGRD::Style->firstForegroundColor();
155   mPlotter->alarmColor = KSGRD::Style->alarmColor();
156   mPlotter->mBackgroundColor = KSGRD::Style->backgroundColor();
157   mPlotter->fontSize = KSGRD::Style->fontSize();
158 
159   repaint();
160 }
161 
addSensor(const QString & hostName,const QString & name,const QString & type,const QString & title)162 bool DancingBars::addSensor( const QString &hostName, const QString &name,
163                              const QString &type, const QString &title )
164 {
165   if ( type != QLatin1String("integer") && type != QLatin1String("float") )
166     return false;
167 
168   if ( mBars >= 32 )
169     return false;
170 
171   if ( !mPlotter->addBar( title ) )
172     return false;
173 
174   registerSensor( new KSGRD::SensorProperties( hostName, name, type, title ) );
175 
176   /* To differentiate between answers from value requests and info
177    * requests we add 100 to the beam index for info requests. */
178   sendRequest( hostName, name + QLatin1Char('?'), mBars + 100 );
179   ++mBars;
180   mSampleBuffer.resize( mBars );
181 
182   QString tooltip;
183   for ( int i = 0; i < mBars; ++i ) {
184     tooltip += QStringLiteral( "%1%2:%3" ).arg( i != 0 ? QStringLiteral("\n") : QString() )
185                                    .arg( sensors().at( i )->hostName() )
186                                    .arg( sensors().at( i )->name() );
187   }
188   mPlotter->setToolTip( tooltip );
189 
190   return true;
191 }
192 
removeSensor(uint pos)193 bool DancingBars::removeSensor( uint pos )
194 {
195   if ( pos >= mBars ) {
196     qDebug() << "DancingBars::removeSensor: idx out of range ("
197                   << pos << ")";
198     return false;
199   }
200 
201   mPlotter->removeBar( pos );
202   mBars--;
203   KSGRD::SensorDisplay::removeSensor( pos );
204 
205   QString tooltip;
206   for ( int i = 0; i < mBars; ++i ) {
207     tooltip += QStringLiteral( "%1%2:%3" ).arg( i != 0 ? QStringLiteral("\n") : QString() )
208                                    .arg( sensors().at( i )->hostName() )
209                                    .arg( sensors().at( i )->name() );
210   }
211   mPlotter->setToolTip( tooltip );
212 
213   return true;
214 }
215 
updateSamples(const QVector<double> & samples)216 void DancingBars::updateSamples( const QVector<double> &samples )
217 {
218   mPlotter->updateSamples( samples );
219 }
220 
answerReceived(int id,const QList<QByteArray> & answerlist)221 void DancingBars::answerReceived( int id, const QList<QByteArray> &answerlist )
222 {
223   /* We received something, so the sensor is probably ok. */
224   sensorError( id, false );
225   QByteArray answer;
226   if(!answerlist.isEmpty()) answer = answerlist[0];
227   if ( id < 100 ) {
228     if(id >= mSampleBuffer.count()) {
229       qDebug() << "ERROR: DancingBars received invalid data";
230       sensorError(id, true);
231       return;
232     }
233     mSampleBuffer[ id ] = answer.toDouble();
234     if ( mFlags.testBit( id ) == true ) {
235       qDebug() << "ERROR: DancingBars lost sample (" << mFlags
236                     << ", " << mBars << ")";
237       sensorError( id, true );
238       return;
239     }
240     mFlags.setBit( id, true );
241 
242     bool allBitsAvailable = true;
243     for ( int i = 0; i < mBars; ++i )
244       allBitsAvailable &= mFlags.testBit( i );
245 
246     if ( allBitsAvailable ) {
247       mPlotter->updateSamples( mSampleBuffer );
248       mFlags.fill( false );
249     }
250   } else if ( id >= 100 ) {
251     KSGRD::SensorIntegerInfo info( answer );
252     if ( id == 100 )
253       if ( mPlotter->getMin() == 0.0 && mPlotter->getMax() == 0.0 ) {
254         /* We only use this information from the sensor when the
255          * display is still using the default values. If the
256          * sensor has been restored we don't touch the already set
257          * values. */
258         mPlotter->changeRange( info.min(), info.max() );
259       }
260 
261     sensors().at( id - 100 )->setUnit( info.unit() );
262   }
263 }
264 
restoreSettings(QDomElement & element)265 bool DancingBars::restoreSettings( QDomElement &element )
266 {
267   SensorDisplay::restoreSettings( element );
268 
269   mPlotter->changeRange( element.attribute( QStringLiteral("min"), QStringLiteral("0") ).toDouble(),
270                          element.attribute( QStringLiteral("max"), QStringLiteral("0") ).toDouble() );
271 
272   mPlotter->setLimits( element.attribute( QStringLiteral("lowlimit"), QStringLiteral("0") ).toDouble(),
273                        element.attribute( QStringLiteral("lowlimitactive"), QStringLiteral("0") ).toInt(),
274                        element.attribute( QStringLiteral("uplimit"), QStringLiteral("0") ).toDouble(),
275                        element.attribute( QStringLiteral("uplimitactive"), QStringLiteral("0") ).toInt() );
276 
277   mPlotter->normalColor = restoreColor( element, QStringLiteral("normalColor"),
278                                         KSGRD::Style->firstForegroundColor() );
279   mPlotter->alarmColor = restoreColor( element, QStringLiteral("alarmColor"),
280                                        KSGRD::Style->alarmColor() );
281   mPlotter->mBackgroundColor = restoreColor( element, QStringLiteral("backgroundColor"),
282                                             KSGRD::Style->backgroundColor() );
283   mPlotter->fontSize = element.attribute( QStringLiteral("fontSize"), QStringLiteral( "%1" ).arg(
284                                           KSGRD::Style->fontSize() ) ).toInt();
285 
286   QDomNodeList dnList = element.elementsByTagName( QStringLiteral("beam") );
287   for ( int i = 0; i < dnList.count(); ++i ) {
288     QDomElement el = dnList.item( i ).toElement();
289     addSensor( el.attribute( QStringLiteral("hostName") ), el.attribute( QStringLiteral("sensorName") ),
290                ( el.attribute( QStringLiteral("sensorType") ).isEmpty() ? QStringLiteral("integer") :
291                el.attribute( QStringLiteral("sensorType") ) ), el.attribute( QStringLiteral("sensorDescr") ) );
292   }
293 
294 
295   return true;
296 }
297 
saveSettings(QDomDocument & doc,QDomElement & element)298 bool DancingBars::saveSettings( QDomDocument &doc, QDomElement &element)
299 {
300   element.setAttribute( QStringLiteral("min"), mPlotter->getMin() );
301   element.setAttribute( QStringLiteral("max"), mPlotter->getMax() );
302   double l, u;
303   bool la, ua;
304   mPlotter->getLimits( l, la, u, ua );
305   element.setAttribute( QStringLiteral("lowlimit"), l );
306   element.setAttribute( QStringLiteral("lowlimitactive"), la );
307   element.setAttribute( QStringLiteral("uplimit"), u );
308   element.setAttribute( QStringLiteral("uplimitactive"), ua );
309 
310   saveColor( element, QStringLiteral("normalColor"), mPlotter->normalColor );
311   saveColor( element, QStringLiteral("alarmColor"), mPlotter->alarmColor );
312 	saveColor( element, QStringLiteral("backgroundColor"), mPlotter->mBackgroundColor );
313   element.setAttribute( QStringLiteral("fontSize"), mPlotter->fontSize );
314 
315   for ( int i = 0; i < mBars; ++i ) {
316     QDomElement beam = doc.createElement( QStringLiteral("beam") );
317     element.appendChild( beam );
318     beam.setAttribute( QStringLiteral("hostName"), sensors().at( i )->hostName() );
319     beam.setAttribute( QStringLiteral("sensorName"), sensors().at( i )->name() );
320     beam.setAttribute( QStringLiteral("sensorType"), sensors().at( i )->type() );
321     beam.setAttribute( QStringLiteral("sensorDescr"), mPlotter->footers[ i ] );
322   }
323 
324   SensorDisplay::saveSettings( doc, element );
325 
326   return true;
327 }
328 
hasSettingsDialog() const329 bool DancingBars::hasSettingsDialog() const
330 {
331   return true;
332 }
333 
334 
335