1 /*
2     KSysGuard, the KDE System Guard
3 
4     Copyright (c) 1999 - 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 <QClipboard>
24 #include <QCursor>
25 #include <QLayout>
26 #include <QTextStream>
27 #include <QGridLayout>
28 #include <QEvent>
29 #include <QDropEvent>
30 #include <QDragEnterEvent>
31 #include <QFile>
32 #include <QByteArray>
33 #include <QApplication>
34 #include <QDebug>
35 #include <QDir>
36 #include <QFileInfo>
37 #include <QMimeData>
38 
39 #include <KLocalizedString>
40 #include <KMessageBox>
41 #include <QMenu>
42 
43 #include <ksgrd/SensorManager.h>
44 
45 #include "DancingBars.h"
46 #include "DummyDisplay.h"
47 #include "FancyPlotter.h"
48 #include "ksysguard.h"
49 #include "ListView.h"
50 #include "LogFile.h"
51 #include "MultiMeter.h"
52 #include "ProcessController.h"
53 #include "SensorLogger.h"
54 #include "WorkSheet.h"
55 #include "WorkSheetSettings.h"
56 
WorkSheet(QWidget * parent)57 WorkSheet::WorkSheet( QWidget *parent )
58   : QWidget( parent )
59 {
60     mGridLayout = nullptr;
61     mRows = mColumns = 0;
62     setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
63     setAcceptDrops( true );
64 }
65 
WorkSheet(int rows,int columns,float interval,QWidget * parent)66     WorkSheet::WorkSheet( int rows, int columns, float interval, QWidget* parent )
67 : QWidget( parent)
68 {
69     mGridLayout = nullptr;
70     setUpdateInterval( interval );
71 
72     createGrid( rows, columns );
73 
74     mGridLayout->activate();
75 
76     setAcceptDrops( true );
77 }
78 
~WorkSheet()79 WorkSheet::~WorkSheet()
80 {
81 }
82 
load(const QString & fileName)83 bool WorkSheet::load( const QString &fileName )
84 {
85     QFile file( fileName );
86     if ( !file.open( QIODevice::ReadOnly ) ) {
87         KMessageBox::sorry( this, i18n( "Cannot open the file %1." ,  fileName ) );
88         return false;
89     }
90 
91     QDomDocument doc;
92 
93     // Read in file and check for a valid XML header.
94     if ( !doc.setContent( &file) ) {
95         KMessageBox::sorry( this, i18n( "The file %1 does not contain valid XML." ,
96                     fileName ) );
97         return false;
98     }
99 
100     // Check for proper document type.
101     if ( doc.doctype().name() != QLatin1String("KSysGuardWorkSheet") ) {
102         KMessageBox::sorry( this, i18n( "The file %1 does not contain a valid worksheet "
103                     "definition, which must have a document type 'KSysGuardWorkSheet'.",
104                     fileName ) );
105         return false;
106     }
107 
108     QDomElement element = doc.documentElement();
109 
110     bool rowsOk, columnsOk;
111     int rows = element.attribute( QStringLiteral("rows") ).toInt( &rowsOk );
112     int columns = element.attribute( QStringLiteral("columns") ).toInt( &columnsOk );
113     if ( !( rowsOk && columnsOk ) ) {
114         KMessageBox::sorry( this, i18n("The file %1 has an invalid worksheet size.",
115                     fileName ) );
116         return false;
117     }
118 
119     // Check for proper size.
120     float interval = element.attribute( QStringLiteral("interval"), QStringLiteral("0.5") ).toFloat();
121     if( interval  < 0 || interval > 100000 )  //make sure the interval is fairly sane
122         interval = 0.5;
123 
124     setUpdateInterval( interval );
125 
126     createGrid( rows, columns );
127 
128     mGridLayout->activate();
129 
130     mTitle = element.attribute( QStringLiteral("title"));
131     mTranslatedTitle = mTitle.isEmpty() ? QLatin1String("") : i18n(mTitle.toUtf8().constData());
132     bool ok;
133     mSharedSettings.locked = element.attribute( QStringLiteral("locked") ).toUInt( &ok );
134     if(!ok) mSharedSettings.locked = false;
135 
136     int i;
137     /* Load lists of hosts that are needed for the work sheet and try
138      * to establish a connection. */
139     QDomNodeList dnList = element.elementsByTagName( QStringLiteral("host") );
140     for ( i = 0; i < dnList.count(); ++i ) {
141         QDomElement element = dnList.item( i ).toElement();
142         bool ok;
143         int port = element.attribute( QStringLiteral("port") ).toInt( &ok );
144         if ( !ok )
145             port = -1;
146         KSGRD::SensorMgr->engage( element.attribute( QStringLiteral("name") ),
147                 element.attribute( QStringLiteral("shell") ),
148                 element.attribute( QStringLiteral("command") ), port );
149     }
150     //if no hosts are specified, at least connect to localhost
151     if(dnList.count() == 0)
152         KSGRD::SensorMgr->engage( QStringLiteral("localhost"), QLatin1String(""), QStringLiteral("ksysguardd"), -1);
153 
154     // Load the displays and place them into the work sheet.
155     dnList = element.elementsByTagName( QStringLiteral("display") );
156     for ( i = 0; i < dnList.count(); ++i ) {
157         QDomElement element = dnList.item( i ).toElement();
158         int row = element.attribute( QStringLiteral("row") ).toInt();
159         int column = element.attribute( QStringLiteral("column") ).toInt();
160         int rowSpan = element.attribute( QStringLiteral("rowSpan"), QStringLiteral("1") ).toInt();
161         int columnSpan = element.attribute( QStringLiteral("columnSpan"), QStringLiteral("1") ).toInt();
162         if ( row < 0 || rowSpan < 0 || (row + rowSpan - 1) >= mRows || column < 0 || columnSpan < 0 || (column + columnSpan - 1) >= mColumns) {
163             qDebug() << "Row or Column out of range (" << row << ", "
164                 << column << ")-(" << (row + rowSpan - 1) << ", " << (column + columnSpan - 1) << ")";
165             return false;
166         }
167         replaceDisplay( row, column, element, rowSpan, columnSpan );
168     }
169 
170     mFullFileName = fileName;
171     return true;
172 }
173 
save(const QString & fileName)174 bool WorkSheet::save( const QString &fileName )
175 {
176     return exportWorkSheet(fileName);
177 }
178 
exportWorkSheet(const QString & fileName)179 bool WorkSheet::exportWorkSheet( const QString &fileName )
180 {
181     QDomDocument doc( QStringLiteral("KSysGuardWorkSheet") );
182     doc.appendChild( doc.createProcessingInstruction(
183                 QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"") ) );
184 
185     // save work sheet information
186     QDomElement ws = doc.createElement( QStringLiteral("WorkSheet") );
187     doc.appendChild( ws );
188     ws.setAttribute( QStringLiteral("title"), mTitle );
189     ws.setAttribute( QStringLiteral("locked"), mSharedSettings.locked?"1":"0" );
190     ws.setAttribute( QStringLiteral("interval"), updateInterval() );
191     ws.setAttribute( QStringLiteral("rows"), mRows );
192     ws.setAttribute( QStringLiteral("columns"), mColumns );
193 
194     QStringList hosts;
195     collectHosts( hosts );
196 
197     // save host information (name, shell, etc.)
198     QStringList::Iterator it;
199     for ( it = hosts.begin(); it != hosts.end(); ++it ) {
200         QString shell, command;
201         int port;
202 
203         if ( KSGRD::SensorMgr->hostInfo( *it, shell, command, port ) ) {
204             QDomElement host = doc.createElement( QStringLiteral("host") );
205             ws.appendChild( host );
206             host.setAttribute( QStringLiteral("name"), *it );
207             host.setAttribute( QStringLiteral("shell"), shell );
208             host.setAttribute( QStringLiteral("command"), command );
209             host.setAttribute( QStringLiteral("port"), port );
210         }
211     }
212 
213     for (int i = 0; i < mGridLayout->count(); i++)
214     {
215         KSGRD::SensorDisplay* display = static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget());
216         if (display->metaObject()->className() != QByteArray("DummyDisplay"))
217         {
218             int row, column, rowSpan, columnSpan;
219             mGridLayout->getItemPosition(i, &row, &column, &rowSpan, &columnSpan);
220 
221             QDomElement element = doc.createElement(QStringLiteral("display"));
222             ws.appendChild(element);
223             element.setAttribute(QStringLiteral("row"), row);
224             element.setAttribute(QStringLiteral("column"), column);
225             element.setAttribute(QStringLiteral("rowSpan"), rowSpan);
226             element.setAttribute(QStringLiteral("columnSpan"), columnSpan);
227             element.setAttribute(QStringLiteral("class"), display->metaObject()->className());
228 
229             display->saveSettings(doc, element);
230         }
231     }
232 
233     if (!QFileInfo::exists(QFileInfo(fileName).path())) {
234         QDir().mkpath(QFileInfo(fileName).path());
235     }
236 
237     QFile file( fileName );
238     if ( !file.open( QIODevice::WriteOnly ) ) {
239         KMessageBox::sorry( this, i18n( "Cannot save file %1" ,  fileName ) );
240         return false;
241     }
242 
243     QTextStream s( &file );
244 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
245     s.setCodec( "UTF-8" );
246 #endif
247     s << doc;
248     file.close();
249 
250     return true;
251 }
252 
cut()253 void WorkSheet::cut()
254 {
255     if ( !currentDisplay() || currentDisplay()->metaObject()->className() == QByteArray("DummyDisplay" ) )
256         return;
257 
258     QClipboard* clip = QApplication::clipboard();
259 
260     clip->setText( currentDisplayAsXML() );
261 
262     removeDisplay( currentDisplay() );
263 }
264 
copy()265 void WorkSheet::copy()
266 {
267     if ( !currentDisplay() || currentDisplay()->metaObject()->className() == QByteArray( "DummyDisplay" ) )
268         return;
269 
270     QClipboard* clip = QApplication::clipboard();
271 
272     clip->setText( currentDisplayAsXML() );
273 }
274 
paste()275 void WorkSheet::paste()
276 {
277     int row, column;
278     if ( !currentDisplay( &row, &column ) )
279         return;
280 
281     QClipboard* clip = QApplication::clipboard();
282 
283     QDomDocument doc;
284     /* Get text from clipboard and check for a valid XML header and
285      * proper document type. */
286     if ( !doc.setContent( clip->text() ) || doc.doctype().name() != QLatin1String("KSysGuardDisplay") ) {
287         KMessageBox::sorry( this, i18n("The clipboard does not contain a valid display "
288                     "description." ) );
289         return;
290     }
291 
292     QDomElement element = doc.documentElement();
293     replaceDisplay( row, column, element );
294 }
295 
setFileName(const QString & fileName)296 void WorkSheet::setFileName( const QString &fileName )
297 {
298     mFileName = fileName;
299 }
300 
fullFileName() const301 QString WorkSheet::fullFileName() const
302 {
303     return mFullFileName;
304 }
305 
fileName() const306 QString WorkSheet::fileName() const
307 {
308     return mFileName;
309 }
310 
setTitle(const QString & title)311 void WorkSheet::setTitle( const QString &title )
312 {
313     mTitle = title;
314     mTranslatedTitle = mTitle.isEmpty() ? QLatin1String("") : i18n(mTitle.toUtf8().constData());
315     emit titleChanged(this);
316 }
317 
translatedTitle() const318 QString WorkSheet::translatedTitle() const {
319     return mTranslatedTitle;
320 }
321 
title() const322 QString WorkSheet::title() const {
323     return mTitle;
324 }
325 
insertDisplay(DisplayType displayType,QString displayTitle,int row,int column,int rowSpan,int columnSpan)326 KSGRD::SensorDisplay* WorkSheet::insertDisplay( DisplayType displayType, QString displayTitle, int row, int column, int rowSpan, int columnSpan )
327 {
328     KSGRD::SensorDisplay* newDisplay = nullptr;
329     switch(displayType) {
330         case DisplayDummy:
331             newDisplay = new DummyDisplay( this, &mSharedSettings );
332             break;
333         case DisplayFancyPlotter:
334             newDisplay = new FancyPlotter( this, displayTitle, &mSharedSettings );
335             break;
336         case DisplayMultiMeter:
337             newDisplay = new MultiMeter( this, displayTitle, &mSharedSettings);
338             break;
339         case DisplayDancingBars:
340             newDisplay = new DancingBars( this, displayTitle, &mSharedSettings);
341             break;
342         case DisplaySensorLogger:
343             newDisplay = new SensorLogger( this, displayTitle, &mSharedSettings);
344             break;
345         case DisplayListView:
346             newDisplay = new ListView( this, displayTitle, &mSharedSettings);
347             break;
348         case DisplayLogFile:
349             newDisplay = new LogFile( this, displayTitle, &mSharedSettings );
350             break;
351         case DisplayProcessControllerRemote:
352             newDisplay = new ProcessController(this, &mSharedSettings);
353             newDisplay->setObjectName(QStringLiteral("remote process controller"));
354             break;
355         case DisplayProcessControllerLocal:
356             newDisplay = new ProcessController(this, &mSharedSettings);
357             if (!Toplevel->localProcessController())
358                 Toplevel->setLocalProcessController(static_cast<ProcessController *>(newDisplay));
359             break;
360         default:
361             Q_ASSERT(false);
362             return nullptr;
363     }
364     newDisplay->applyStyle();
365     connect(&mTimer, &QTimer::timeout, newDisplay, &KSGRD::SensorDisplay::timerTick);
366     replaceDisplay( row, column, newDisplay, rowSpan, columnSpan );
367     return newDisplay;
368 }
369 
addDisplay(const QString & hostName,const QString & sensorName,const QString & sensorType,const QString & sensorDescr,int row,int column)370 KSGRD::SensorDisplay *WorkSheet::addDisplay( const QString &hostName,
371         const QString &sensorName,
372         const QString &sensorType,
373         const QString& sensorDescr,
374         int row, int column )
375 {
376     KSGRD::SensorDisplay* display = static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAtPosition(row, column)->widget());
377     /* If the by 'row' and 'column' specified display is a QGroupBox dummy
378      * display we replace the widget. Otherwise we just try to add
379      * the new sensor to an existing display. */
380     if ( display->metaObject()->className() == QByteArray( "DummyDisplay" ) ) {
381         DisplayType displayType = DisplayDummy;
382         /* If the sensor type is supported by more than one display
383          * type we popup a menu so the user can select what display is
384          * wanted. */
385         if ( sensorType == QLatin1String("integer") || sensorType == QLatin1String("float") ) {
386             QMenu pm;
387             pm.addSection( i18n( "Select Display Type" ) );
388             QAction *a1 = pm.addAction( i18n( "&Line graph" ) );
389             QAction *a2 = pm.addAction( i18n( "&Digital display" ) );
390             QAction *a3 = pm.addAction( i18n( "&Bar graph" ) );
391             QAction *a4 = pm.addAction( i18n( "Log to a &file" ) );
392             QAction *execed = pm.exec( QCursor::pos() );
393             if (execed == a1)
394                 displayType = DisplayFancyPlotter;
395             else if (execed == a2)
396                 displayType = DisplayMultiMeter;
397             else if (execed == a3)
398                 displayType = DisplayDancingBars;
399             else if (execed == a4)
400                 displayType = DisplaySensorLogger;
401             else
402                 return nullptr;
403         } else if ( sensorType == QLatin1String("listview") ) {
404             displayType = DisplayListView;
405         }
406         else if ( sensorType == QLatin1String("logfile") ) {
407             displayType = DisplayLogFile;
408         }
409         else if ( sensorType == QLatin1String("sensorlogger") ) {
410             displayType = DisplaySensorLogger;
411         }
412         else if ( sensorType == QLatin1String("table") ) {
413             if(hostName.isEmpty() || hostName == QLatin1String("localhost"))
414                 displayType = DisplayProcessControllerLocal;
415             else
416                 displayType = DisplayProcessControllerRemote;
417         }
418         else {
419             qDebug() << "Unknown sensor type: " <<  sensorType;
420             return nullptr;
421         }
422         display = insertDisplay(displayType, sensorDescr, row, column);
423     }
424     if (!display->addSensor( hostName, sensorName, sensorType, sensorDescr )) {
425             // Failed to add sensor, so we need to remove the display that we just added
426             removeDisplay(display);
427             return nullptr;
428     }
429 
430     return display;
431 }
432 
settings()433 void WorkSheet::settings()
434 {
435     WorkSheetSettings dlg( this, mSharedSettings.locked );
436     dlg.setSheetTitle( mTranslatedTitle );
437     dlg.setInterval( updateInterval() );
438 
439     if(!mSharedSettings.locked) {
440         dlg.setRows( mRows );
441         dlg.setColumns( mColumns );
442     }
443 
444     if ( dlg.exec() ) {
445         setUpdateInterval( dlg.interval() );
446 
447         if (!mSharedSettings.locked)
448             resizeGrid( dlg.rows(), dlg.columns() );
449 
450         if(mTranslatedTitle != dlg.sheetTitle()) { //Title has changed
451             if(mRows == 1 && mColumns == 1) {
452                 static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(0)->widget())->setTitle(dlg.sheetTitle());
453             } else {
454                 setTitle(dlg.sheetTitle());
455             }
456         }
457     }
458 }
459 
showPopupMenu(KSGRD::SensorDisplay * display)460 void WorkSheet::showPopupMenu( KSGRD::SensorDisplay *display )
461 {
462     display->configureSettings();
463 }
464 
applyStyle()465 void WorkSheet::applyStyle()
466 {
467     for (int i = 0; i < mGridLayout->count(); i++)
468         static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget())->applyStyle();
469 }
470 
dragEnterEvent(QDragEnterEvent * event)471 void WorkSheet::dragEnterEvent( QDragEnterEvent* event)
472 {
473     if ( !event->mimeData()->hasFormat(QStringLiteral("application/x-ksysguard")) )
474         return;
475     event->accept();
476 }
dragMoveEvent(QDragMoveEvent * event)477 void WorkSheet::dragMoveEvent( QDragMoveEvent *event )
478 {
479     /* Find the sensor display that is supposed to get the drop
480      * event and replace or add sensor. */
481     const QPoint globalPos = mapToGlobal( event->pos() );
482     for ( int i = 0; i < mGridLayout->count(); i++ ) {
483         KSGRD::SensorDisplay* display = static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget());
484         const QRect widgetRect = QRect( display->mapToGlobal( QPoint( 0, 0 ) ),
485                 display->size() );
486 
487         if ( widgetRect.contains( globalPos ) ) {
488             QByteArray widgetType = display->metaObject()->className();
489             if(widgetType == "MultiMeter" || widgetType == "ProcessController" || widgetType == "table")
490                 event->ignore(widgetRect);
491             else if(widgetType != "Dummy")
492                 event->accept(widgetRect);
493             return;
494         }
495     }
496 }
497 
dropEvent(QDropEvent * event)498 void WorkSheet::dropEvent( QDropEvent *event )
499 {
500     if ( !event->mimeData()->hasFormat(QStringLiteral("application/x-ksysguard")) )
501         return;
502 
503     const QString dragObject = QString::fromUtf8(event->mimeData()->data(QStringLiteral("application/x-ksysguard")));
504 
505     // The host name, sensor name and type are separated by a ' '.
506     QStringList parts = dragObject.split( ' ');
507 
508     QString hostName = parts[ 0 ];
509     QString sensorName = parts[ 1 ];
510     QString sensorType = parts[ 2 ];
511     QString sensorDescr = QStringList(parts.mid( 3 )).join(' ');
512 
513     if ( hostName.isEmpty() || sensorName.isEmpty() || sensorType.isEmpty() )
514         return;
515 
516     /* Find the sensor display that is supposed to get the drop
517      * event and replace or add sensor. */
518     const QPoint globalPos = mapToGlobal( event->pos() );
519     for ( int i = 0; i < mGridLayout->count(); i++ ) {
520         KSGRD::SensorDisplay* display = static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget());
521         const QSize displaySize = display->size();
522 
523         const QPoint displayPoint( displaySize.width(), displaySize.height() );
524 
525         const QRect widgetRect = QRect( display->mapToGlobal( QPoint( 0, 0 ) ),
526                 display->mapToGlobal( displayPoint ) );
527 
528 
529         if ( widgetRect.contains( globalPos ) ) {
530             int row, column, rowSpan, columnSpan;
531             mGridLayout->getItemPosition(i, &row, &column, &rowSpan, &columnSpan);
532             addDisplay( hostName, sensorName, sensorType, sensorDescr, row, column );
533             return;
534         }
535     }
536 }
537 
sizeHint() const538 QSize WorkSheet::sizeHint() const
539 {
540     return QSize( 800,600 );
541 }
542 
event(QEvent * e)543 bool WorkSheet::event( QEvent *e )
544 {
545     if ( e->type() == QEvent::User ) {
546         // SensorDisplays send out this event if they want to be removed.
547         if ( KMessageBox::warningContinueCancel( this, i18n( "Remove this display?" ),
548                     i18n("Remove Display"), KStandardGuiItem::del() )
549                 == KMessageBox::Continue ) {
550             KSGRD::SensorDisplay::DeleteEvent *event = static_cast<KSGRD::SensorDisplay::DeleteEvent*>( e );
551             removeDisplay( event->display() );
552 
553             return true;
554         }
555     }
556 
557     return QWidget::event( e );
558 }
559 
replaceDisplay(int row,int column,QDomElement & element,int rowSpan,int columnSpan)560 bool WorkSheet::replaceDisplay( int row, int column, QDomElement& element, int rowSpan, int columnSpan )
561 {
562     QString classType = element.attribute( QStringLiteral("class") );
563     QString hostName = element.attribute( QStringLiteral("hostName") );
564     DisplayType displayType = DisplayDummy;
565     KSGRD::SensorDisplay* newDisplay;
566 
567     if ( classType == QLatin1String("FancyPlotter") )
568         displayType = DisplayFancyPlotter;
569     else if ( classType == QLatin1String("MultiMeter") )
570         displayType = DisplayMultiMeter;
571     else if ( classType == QLatin1String("DancingBars") )
572         displayType = DisplayDancingBars;
573     else if ( classType == QLatin1String("ListView") )
574         displayType = DisplayListView;
575     else if ( classType == QLatin1String("LogFile") )
576         displayType = DisplayLogFile;
577     else if ( classType == QLatin1String("SensorLogger") )
578         displayType = DisplaySensorLogger;
579     else if ( classType == QLatin1String("ProcessController") ) {
580         if(hostName.isEmpty() || hostName == QLatin1String("localhost"))
581             displayType = DisplayProcessControllerLocal;
582         else
583             displayType = DisplayProcessControllerRemote;
584     } else {
585         qDebug() << "Unknown class " <<  classType;
586         return false;
587     }
588 
589     newDisplay = insertDisplay(displayType, i18n("Dummy"), row, column, rowSpan, columnSpan);
590 
591     // load display specific settings
592     if ( !newDisplay->restoreSettings( element ) )
593         return false;
594 
595     return true;
596 }
597 
598 
replaceDisplay(int row,int column,KSGRD::SensorDisplay * newDisplay,int rowSpan,int columnSpan)599 void WorkSheet::replaceDisplay( int row, int column, KSGRD::SensorDisplay* newDisplay, int rowSpan, int columnSpan )
600 {
601     if ( !newDisplay )
602         newDisplay = new DummyDisplay( this, &mSharedSettings );
603 
604     // remove the old display && sensor frame at this location
605     QSet<QLayoutItem*> oldDisplays;
606     for (int i = row; i < row + rowSpan; i++)
607         for (int j = column; j < column + columnSpan; j++)
608         {
609             QLayoutItem* item = mGridLayout->itemAtPosition(i, j);
610             if (item)
611                 oldDisplays.insert(item);
612         }
613 
614     for (QSet<QLayoutItem*>::iterator iter = oldDisplays.begin(); iter != oldDisplays.end(); iter++)
615     {
616         QLayoutItem* item = *iter;
617 
618         int oldDisplayRow, oldDisplayColumn, oldDisplayRowSpan, oldDisplayColumnSpan;
619         mGridLayout->getItemPosition(mGridLayout->indexOf(item->widget()), &oldDisplayRow, &oldDisplayColumn, &oldDisplayRowSpan, &oldDisplayColumnSpan);
620 
621         mGridLayout->removeItem(item);
622         if (item->widget() != Toplevel->localProcessController())
623             delete item->widget();
624         delete item;
625 
626         for (int i = oldDisplayRow; i < oldDisplayRow + oldDisplayRowSpan; i++)
627             for (int j = oldDisplayColumn; j < oldDisplayColumn + oldDisplayColumnSpan; j++)
628                 if ((i < row || i >= row + rowSpan || j < column || j >= column + columnSpan) && !mGridLayout->itemAtPosition(i, j))
629                     mGridLayout->addWidget(new DummyDisplay(this, &mSharedSettings), i, j);
630     }
631 
632 
633     mGridLayout->addWidget(newDisplay, row, column, rowSpan, columnSpan);
634 
635     if (newDisplay->metaObject()->className() != QByteArray("DummyDisplay"))
636     {
637         connect(newDisplay, &KSGRD::SensorDisplay::showPopupMenu, this, &WorkSheet::showPopupMenu);
638         newDisplay->setDeleteNotifier(this);
639     }
640 
641     // if there's only item, the tab's title should be the widget's title
642     if (row == 0 && mRows == rowSpan && column == 0 && mColumns == columnSpan)
643     {
644         connect(newDisplay, &KSGRD::SensorDisplay::titleChanged, this, &WorkSheet::setTitle);
645         setTitle(newDisplay->title());
646     }
647     if (isVisible())
648         newDisplay->show();
649 }
650 
refreshSheet()651 void WorkSheet::refreshSheet()
652 {
653     for (int i = 0; i < mGridLayout->count(); i++)
654         static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget())->timerTick();
655 }
656 
removeDisplay(KSGRD::SensorDisplay * display)657 void WorkSheet::removeDisplay( KSGRD::SensorDisplay *display )
658 {
659     if ( !display )
660         return;
661 
662     int row, column, rowSpan, columnSpan;
663     mGridLayout->getItemPosition(mGridLayout->indexOf(display), &row, &column, &rowSpan, &columnSpan);
664     replaceDisplay(row, column);
665 }
666 
collectHosts(QStringList & list)667 void WorkSheet::collectHosts( QStringList &list )
668 {
669     for (int i = 0; i < mGridLayout->count(); i++)
670         static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget())->hosts(list);
671 }
672 
createGrid(int rows,int columns)673 void WorkSheet::createGrid( int rows, int columns )
674 {
675     mRows = rows;
676     mColumns = columns;
677 
678     // create grid layout with specified dimensions
679     mGridLayout = new QGridLayout( this );
680     mGridLayout->setSpacing( 5 );
681 
682     /* set stretch factors for rows and columns */
683     for ( int r = 0; r < mRows; ++r )
684         mGridLayout->setRowStretch( r, 100 );
685     for ( int c = 0; c < mColumns; ++c )
686         mGridLayout->setColumnStretch( c, 100 );
687 
688     for (int r = 0; r < mRows; r++)
689         for (int c = 0; c < mColumns; c++)
690             replaceDisplay(r, c);
691 }
692 
resizeGrid(int newRows,int newColumns)693 void WorkSheet::resizeGrid( int newRows, int newColumns )
694 {
695     int oldRows = mRows, oldColumns = mColumns;
696     mRows = newRows;
697     mColumns = newColumns;
698 
699     /* delete any excess displays */
700     for (int i = 0; i < mGridLayout->count(); i++)
701     {
702         int row, column, rowSpan, columnSpan;
703         mGridLayout->getItemPosition(i, &row, &column, &rowSpan, &columnSpan);
704         if (row + rowSpan - 1 >= mRows || column + columnSpan - 1 >= mColumns)
705         {
706             QLayoutItem* item = mGridLayout->takeAt(i);
707             if (item->widget() != Toplevel->localProcessController())
708                 delete item->widget();
709             delete item;
710             --i;
711         }
712     }
713 
714     /* create new displays */
715     if (mRows > oldRows || mColumns > oldColumns)
716         for (int i = 0; i < mRows; ++i)
717             for (int j = 0; j < mColumns; ++j)
718                 if (i >= oldRows || j >= oldColumns)
719                     replaceDisplay(i, j);
720 
721     /* set stretch factors for new rows and columns (if any) */
722     for ( int r = oldRows; r < mRows; ++r )
723         mGridLayout->setRowStretch( r, 100 );
724     for ( int c = oldColumns; c < mColumns; ++c )
725         mGridLayout->setColumnStretch( c, 100 );
726 
727     /* Obviously Qt does not shrink the size of the QGridLayout
728      * automatically.  So we simply force the rows and columns that
729      * are no longer used to have a stretch factor of 0 and hence be
730      * invisible. */
731     for ( int r = mRows; r < oldRows; ++r )
732         mGridLayout->setRowStretch( r, 0 );
733     for ( int c = mColumns; c < oldColumns; ++c )
734         mGridLayout->setColumnStretch( c, 0 );
735 
736     fixTabOrder();
737 
738     mGridLayout->activate();
739 }
740 
currentDisplay(int * row,int * column)741 KSGRD::SensorDisplay *WorkSheet::currentDisplay( int * row, int * column )
742 {
743     int dummyRow, dummyColumn, rowSpan, columnSpan;
744     if (!row) row = &dummyRow;
745     if (!column) column = &dummyColumn;
746 
747     for (int i = 0; i < mGridLayout->count(); i++)
748     {
749         KSGRD::SensorDisplay* display = static_cast<KSGRD::SensorDisplay*>(mGridLayout->itemAt(i)->widget());
750         if (display->hasFocus())
751         {
752             mGridLayout->getItemPosition(i, row, column, &rowSpan, &columnSpan);
753             return display;
754         }
755     }
756 
757     return nullptr;
758 }
759 
fixTabOrder()760 void WorkSheet::fixTabOrder()
761 {
762     QWidget* previous = nullptr;
763     for (int i = 0; i < mGridLayout->count(); i++)
764     {
765         QWidget* current = mGridLayout->itemAt(i)->widget();
766         if (previous)
767             setTabOrder(previous, current);
768         previous = current;
769     }
770 }
771 
currentDisplayAsXML()772 QString WorkSheet::currentDisplayAsXML()
773 {
774     KSGRD::SensorDisplay* display = currentDisplay();
775     if ( !display )
776         return QString();
777 
778     /* We create an XML description of the current display. */
779     QDomDocument doc( QStringLiteral("KSysGuardDisplay") );
780     doc.appendChild( doc.createProcessingInstruction(
781                 QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"") ) );
782 
783     QDomElement element = doc.createElement( QStringLiteral("display") );
784     doc.appendChild( element );
785     element.setAttribute( QStringLiteral("class"), display->metaObject()->className() );
786     display->saveSettings( doc, element );
787 
788     return doc.toString();
789 }
790 
changeEvent(QEvent * event)791 void WorkSheet::changeEvent( QEvent * event ) {
792     if (event->type() == QEvent::LanguageChange) {
793         setTitle(mTitle);  //retranslate
794     }
795 }
796 
setUpdateInterval(float secs)797 void WorkSheet::setUpdateInterval( float secs)
798 {
799     if(secs == 0)
800         mTimer.stop();
801     else {
802         mTimer.setInterval(secs*1000);
803         mTimer.start();
804     }
805 }
updateInterval() const806 float WorkSheet::updateInterval() const
807 {
808     if(mTimer.isActive())
809         return mTimer.interval()/1000.0;
810     else
811         return 0;
812 }
813 
814 
815