/* KSysGuard, the KDE System Guard Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. KSysGuard is currently maintained by Chris Schlaeger <cs@kde.org>. Please do not commit any changes without consulting me first. Thanks! */ #include <tqclipboard.h> #include <tqcursor.h> #include <tqdragobject.h> #include <tqfile.h> #include <tqlayout.h> #include <kdebug.h> #include <klocale.h> #include <kmessagebox.h> #include <kpopupmenu.h> #include <SensorManager.h> #include "DancingBars.h" #include "DummyDisplay.h" #include "FancyPlotter.h" #include "ListView.h" #include "LogFile.h" #include "MultiMeter.h" #include "ProcessController.h" #include "SensorLogger.h" #include "WorkSheet.h" #include "WorkSheetSettings.h" WorkSheet::WorkSheet( TQWidget *parent, const char *name ) : TQWidget( parent, name ) { mGridLayout = 0; mRows = mColumns = 0; mDisplayList = 0; mModified = false; mFileName = ""; setAcceptDrops( true ); } WorkSheet::WorkSheet( uint rows, uint columns, uint interval, TQWidget* parent, const char *name ) : TQWidget( parent, name ) { mRows = mColumns = 0; mGridLayout = 0; mDisplayList = 0; updateInterval( interval ); mModified = false; mFileName = ""; createGrid( rows, columns ); // Initialize worksheet with dummy displays. for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) replaceDisplay( r, c ); mGridLayout->activate(); setAcceptDrops( true ); } WorkSheet::~WorkSheet() { } bool WorkSheet::load( const TQString &fileName ) { setModified( false ); mFileName = fileName; TQFile file( mFileName ); if ( !file.open( IO_ReadOnly ) ) { KMessageBox::sorry( this, i18n( "Cannot open the file %1." ).arg( mFileName ) ); return false; } TQDomDocument doc; // Read in file and check for a valid XML header. if ( !doc.setContent( &file) ) { KMessageBox::sorry( this, i18n( "The file %1 does not contain valid XML." ) .arg( mFileName ) ); return false; } // Check for proper document type. if ( doc.doctype().name() != "KSysGuardWorkSheet" ) { KMessageBox::sorry( this, i18n( "The file %1 does not contain a valid worksheet " "definition, which must have a document type 'KSysGuardWorkSheet'.") .arg( mFileName ) ); return false; } // Check for proper size. TQDomElement element = doc.documentElement(); updateInterval( element.attribute( "interval" ).toUInt() ); if ( updateInterval() < 1 || updateInterval() > 300 ) updateInterval( 2 ); bool rowsOk, columnsOk; uint rows = element.attribute( "rows" ).toUInt( &rowsOk ); uint columns = element.attribute( "columns" ).toUInt( &columnsOk ); if ( !( rowsOk && columnsOk ) ) { KMessageBox::sorry( this, i18n("The file %1 has an invalid worksheet size.") .arg( mFileName ) ); return false; } createGrid( rows, columns ); uint i; /* Load lists of hosts that are needed for the work sheet and try * to establish a connection. */ TQDomNodeList dnList = element.elementsByTagName( "host" ); for ( i = 0; i < dnList.count(); ++i ) { TQDomElement element = dnList.item( i ).toElement(); bool ok; int port = element.attribute( "port" ).toInt( &ok ); if ( !ok ) port = -1; KSGRD::SensorMgr->engage( element.attribute( "name" ), element.attribute( "shell" ), element.attribute( "command" ), port ); } //if no hosts are specified, at least connect to localhost if(dnList.count() == 0) KSGRD::SensorMgr->engage( "localhost", "", "ksysguardd", -1); // Load the displays and place them into the work sheet. dnList = element.elementsByTagName( "display" ); for ( i = 0; i < dnList.count(); ++i ) { TQDomElement element = dnList.item( i ).toElement(); uint row = element.attribute( "row" ).toUInt(); uint column = element.attribute( "column" ).toUInt(); if ( row >= mRows || column >= mColumns) { kdDebug(1215) << "Row or Column out of range (" << row << ", " << column << ")" << endl; return false; } replaceDisplay( row, column, element ); } // Fill empty cells with dummy displays for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) if ( !mDisplayList[ r ][ c ] ) replaceDisplay( r, c ); setModified( false ); return true; } bool WorkSheet::save( const TQString &fileName ) { mFileName = fileName; TQDomDocument doc( "KSysGuardWorkSheet" ); doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); // save work sheet information TQDomElement ws = doc.createElement( "WorkSheet" ); doc.appendChild( ws ); ws.setAttribute( "interval", updateInterval() ); ws.setAttribute( "rows", mRows ); ws.setAttribute( "columns", mColumns ); TQStringList hosts; collectHosts( hosts ); // save host information (name, shell, etc.) TQStringList::Iterator it; for ( it = hosts.begin(); it != hosts.end(); ++it ) { TQString shell, command; int port; if ( KSGRD::SensorMgr->hostInfo( *it, shell, command, port ) ) { TQDomElement host = doc.createElement( "host" ); ws.appendChild( host ); host.setAttribute( "name", *it ); host.setAttribute( "shell", shell ); host.setAttribute( "command", command ); host.setAttribute( "port", port ); } } for ( uint r = 0; r < mRows; ++r ) for (uint c = 0; c < mColumns; ++c ) if ( !mDisplayList[ r ][ c ]->isA( "DummyDisplay" ) ) { KSGRD::SensorDisplay* display = (KSGRD::SensorDisplay*)mDisplayList[ r ][ c ]; TQDomElement element = doc.createElement( "display" ); ws.appendChild( element ); element.setAttribute( "row", r ); element.setAttribute( "column", c ); element.setAttribute( "class", display->className() ); display->saveSettings( doc, element ); } TQFile file( mFileName ); if ( !file.open( IO_WriteOnly ) ) { KMessageBox::sorry( this, i18n( "Cannot save file %1" ).arg( mFileName ) ); return false; } TQTextStream s( &file ); s.setEncoding( TQTextStream::UnicodeUTF8 ); s << doc; file.close(); setModified( false ); return true; } void WorkSheet::cut() { if ( !currentDisplay() || currentDisplay()->isA( "DummyDisplay" ) ) return; TQClipboard* clip = TQApplication::tqclipboard(); clip->setText( currentDisplayAsXML() ); removeDisplay( currentDisplay() ); } void WorkSheet::copy() { if ( !currentDisplay() || currentDisplay()->isA( "DummyDisplay" ) ) return; TQClipboard* clip = TQApplication::tqclipboard(); clip->setText( currentDisplayAsXML() ); } void WorkSheet::paste() { uint row, column; if ( !currentDisplay( &row, &column ) ) return; TQClipboard* clip = TQApplication::tqclipboard(); TQDomDocument doc; /* Get text from clipboard and check for a valid XML header and * proper document type. */ if ( !doc.setContent( clip->text() ) || doc.doctype().name() != "KSysGuardDisplay" ) { KMessageBox::sorry( this, i18n("The clipboard does not contain a valid display " "description." ) ); return; } TQDomElement element = doc.documentElement(); replaceDisplay( row, column, element ); } void WorkSheet::setFileName( const TQString &fileName ) { mFileName = fileName; setModified( true ); } const TQString& WorkSheet::fileName() const { return mFileName; } bool WorkSheet::modified() const { return mModified; } void WorkSheet::setTitle( const TQString &title ) { mTitle = title; } TQString WorkSheet::title() const { return mTitle; } KSGRD::SensorDisplay *WorkSheet::addDisplay( const TQString &hostName, const TQString &sensorName, const TQString &sensorType, const TQString& sensorDescr, uint row, uint column ) { if ( !KSGRD::SensorMgr->engageHost( hostName ) ) { TQString msg = i18n( "It is impossible to connect to \'%1\'." ).arg( hostName ); KMessageBox::error( this, msg ); return 0; } /* If the by 'row' and 'column' specified display is a TQGroupBox dummy * display we replace the widget. Otherwise we just try to add * the new sensor to an existing display. */ if ( mDisplayList[ row ][ column ]->isA( "DummyDisplay" ) ) { KSGRD::SensorDisplay* newDisplay = 0; /* If the sensor type is supported by more than one display * type we popup a menu so the user can select what display is * wanted. */ if ( sensorType == "integer" || sensorType == "float" ) { KPopupMenu pm; pm.insertTitle( i18n( "Select Display Type" ) ); pm.insertItem( i18n( "&Signal Plotter" ), 1 ); pm.insertItem( i18n( "&Multimeter" ), 2 ); pm.insertItem( i18n( "&BarGraph" ), 3 ); pm.insertItem( i18n( "S&ensorLogger" ), 4 ); switch ( pm.exec( TQCursor::pos() ) ) { case 1: newDisplay = new FancyPlotter( this, "FancyPlotter", sensorDescr ); break; case 2: newDisplay = new MultiMeter( this, "MultiMeter", sensorDescr ); break; case 3: newDisplay = new DancingBars( this, "DancingBars", sensorDescr ); break; case 4: newDisplay = new SensorLogger( this, "SensorLogger", sensorDescr ); break; default: return 0; } } else if ( sensorType == "listview" ) newDisplay = new ListView( this, "ListView", sensorDescr ); else if ( sensorType == "logfile" ) newDisplay = new LogFile( this, "LogFile", sensorDescr ); else if ( sensorType == "sensorlogger" ) newDisplay = new SensorLogger( this, "SensorLogger", sensorDescr ); else if ( sensorType == "table" ) newDisplay = new ProcessController( this ); else { kdDebug(1215) << "Unkown sensor type: " << sensorType << endl; return 0; } replaceDisplay( row, column, newDisplay ); } mDisplayList[ row ][ column ]->addSensor( hostName, sensorName, sensorType, sensorDescr ); setModified( true ); return ((KSGRD::SensorDisplay*)mDisplayList[ row ][ column ] ); } void WorkSheet::settings() { WorkSheetSettings dlg( this ); /* The sheet name should be changed with the "Save as..." function, * so we don't have to display the display frame. */ dlg.setSheetTitle( mTitle ); dlg.setRows( mRows ); dlg.setColumns( mColumns ); dlg.setInterval( updateInterval() ); if ( dlg.exec() ) { updateInterval( dlg.interval() ); for (uint r = 0; r < mRows; ++r) for (uint c = 0; c < mColumns; ++c) if ( mDisplayList[ r ][ c ]->useGlobalUpdateInterval() ) mDisplayList[ r ][ c ]->setUpdateInterval( updateInterval() ); resizeGrid( dlg.rows(), dlg.columns() ); mTitle = dlg.sheetTitle(); emit titleChanged( this ); setModified( true ); } } void WorkSheet::showPopupMenu( KSGRD::SensorDisplay *display ) { display->configureSettings(); } void WorkSheet::setModified( bool modified ) { if ( modified != mModified ) { mModified = modified; if ( !modified ) for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) mDisplayList[ r ][ c ]->setModified( false ); emit sheetModified( this ); } } void WorkSheet::applyStyle() { for ( uint r = 0; r < mRows; ++r ) for (uint c = 0; c < mColumns; ++c ) mDisplayList[ r ][ c ]->applyStyle(); } void WorkSheet::dragEnterEvent( TQDragEnterEvent *e ) { e->accept( TQTextDrag::canDecode( e ) ); } void WorkSheet::dropEvent( TQDropEvent *e ) { TQString dragObject; if ( TQTextDrag::decode( e, dragObject) ) { // The host name, sensor name and type are seperated by a ' '. TQStringList parts = TQStringList::split( ' ', dragObject ); TQString hostName = parts[ 0 ]; TQString sensorName = parts[ 1 ]; TQString sensorType = parts[ 2 ]; TQString sensorDescr = parts[ 3 ]; if ( hostName.isEmpty() || sensorName.isEmpty() || sensorType.isEmpty() ) { return; } /* Find the sensor display that is supposed to get the drop * event and replace or add sensor. */ for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) if ( mDisplayList[ r ][ c ]->tqgeometry().contains( e->pos() ) ) { addDisplay( hostName, sensorName, sensorType, sensorDescr, r, c ); return; } } } TQSize WorkSheet::tqsizeHint() const { return TQSize( 200,150 ); } void WorkSheet::customEvent( TQCustomEvent *e ) { if ( e->type() == TQEvent::User ) { // SensorDisplays send out this event if they want to be removed. removeDisplay( (KSGRD::SensorDisplay*)e->data() ); } } bool WorkSheet::replaceDisplay( uint row, uint column, TQDomElement& element ) { TQString classType = element.attribute( "class" ); KSGRD::SensorDisplay* newDisplay; if ( classType == "FancyPlotter" ) newDisplay = new FancyPlotter( this ); else if ( classType == "MultiMeter" ) newDisplay = new MultiMeter( this ); else if ( classType == "DancingBars" ) newDisplay = new DancingBars( this ); else if ( classType == "ListView" ) newDisplay = new ListView( this ); else if ( classType == "LogFile" ) newDisplay = new LogFile( this ); else if ( classType == "SensorLogger" ) newDisplay = new SensorLogger( this ); else if ( classType == "ProcessController" ) newDisplay = new ProcessController( this ); else { kdDebug(1215) << "Unkown class " << classType << endl; return false; } if ( newDisplay->useGlobalUpdateInterval() ) newDisplay->setUpdateInterval( updateInterval() ); // load display specific settings if ( !newDisplay->restoreSettings( element ) ) return false; replaceDisplay( row, column, newDisplay ); return true; } void WorkSheet::replaceDisplay( uint row, uint column, KSGRD::SensorDisplay* newDisplay ) { // remove the old display at this location delete mDisplayList[ row ][ column ]; // insert new display if ( !newDisplay ) mDisplayList[ row ][ column ] = new DummyDisplay( this, "DummyDisplay" ); else { mDisplayList[ row ][ column ] = newDisplay; if ( mDisplayList[ row ][ column ]->useGlobalUpdateInterval() ) mDisplayList[ row ][ column ]->setUpdateInterval( updateInterval() ); connect( newDisplay, TQT_SIGNAL( showPopupMenu( KSGRD::SensorDisplay* ) ), TQT_SLOT( showPopupMenu( KSGRD::SensorDisplay* ) ) ); connect( newDisplay, TQT_SIGNAL( modified( bool ) ), TQT_SLOT( setModified( bool ) ) ); } mGridLayout->addWidget( mDisplayList[ row ][ column ], row, column ); if ( isVisible() ) { mDisplayList[ row ][ column ]->show(); } setMinimumSize(tqsizeHint()); setModified( true ); } void WorkSheet::removeDisplay( KSGRD::SensorDisplay *display ) { if ( !display ) return; for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) if ( mDisplayList[ r ][ c ] == display ) { replaceDisplay( r, c ); setModified( true ); return; } } void WorkSheet::collectHosts( TQStringList &list ) { for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) if ( !mDisplayList[ r ][ c ]->isA( "DummyDisplay" ) ) ((KSGRD::SensorDisplay*)mDisplayList[ r ][ c ])->hosts( list ); } void WorkSheet::createGrid( uint rows, uint columns ) { mRows = rows; mColumns = columns; // create grid layout with specified dimentions mGridLayout = new TQGridLayout( this, mRows, mColumns, 5 ); mDisplayList = new KSGRD::SensorDisplay**[ mRows ]; for ( uint r = 0; r < mRows; ++r ) { mDisplayList[ r ] = new KSGRD::SensorDisplay*[ mColumns ]; for ( uint c = 0; c < mColumns; ++c ) mDisplayList[ r ][ c ] = 0; } /* set stretch factors for rows and columns */ for ( uint r = 0; r < mRows; ++r ) mGridLayout->setRowStretch( r, 100 ); for ( uint c = 0; c < mColumns; ++c ) mGridLayout->setColStretch( c, 100 ); } void WorkSheet::resizeGrid( uint newRows, uint newColumns ) { uint r, c; /* Create new array for display pointers */ KSGRD::SensorDisplay*** newDisplayList = new KSGRD::SensorDisplay**[ newRows ]; for ( r = 0; r < newRows; ++r ) { newDisplayList[ r ] = new KSGRD::SensorDisplay*[ newColumns ]; for ( c = 0; c < newColumns; ++c ) { if ( c < mColumns && r < mRows ) newDisplayList[ r ][ c ] = mDisplayList[ r ][ c ]; else newDisplayList[ r ][ c ] = 0; } } /* remove obsolete displays */ for ( r = 0; r < mRows; ++r ) { for ( c = 0; c < mColumns; ++c ) if ( r >= newRows || c >= newColumns ) delete mDisplayList[ r ][ c ]; delete mDisplayList[ r ]; } delete [] mDisplayList; /* now we make the new display the regular one */ mDisplayList = newDisplayList; /* create new displays */ for ( r = 0; r < newRows; ++r ) for ( c = 0; c < newColumns; ++c ) if ( r >= mRows || c >= mColumns ) replaceDisplay( r, c ); /* set stretch factors for new rows and columns (if any) */ for ( r = mRows; r < newRows; ++r ) mGridLayout->setRowStretch( r, 100 ); for ( c = mColumns; c < newColumns; ++c ) mGridLayout->setColStretch( c, 100 ); /* Obviously Qt does not shrink the size of the QGridLayout * automatically. So we simply force the rows and columns that * are no longer used to have a strech factor of 0 and hence be * invisible. */ for ( r = newRows; r < mRows; ++r ) mGridLayout->setRowStretch( r, 0 ); for ( c = newColumns; c < mColumns; ++c ) mGridLayout->setColStretch( c, 0 ); mRows = newRows; mColumns = newColumns; fixTabOrder(); mGridLayout->activate(); } KSGRD::SensorDisplay *WorkSheet::display( uint row, uint column ) { if(row >= mRows || column >= mColumns) return 0; return mDisplayList[row][column]; } KSGRD::SensorDisplay *WorkSheet::currentDisplay( uint *row, uint *column ) { for ( uint r = 0 ; r < mRows; ++r ) for ( uint c = 0 ; c < mColumns; ++c ) if ( mDisplayList[ r ][ c ]->hasFocus() ) { if ( row ) *row = r; if ( column ) *column = c; return ( mDisplayList[ r ][ c ] ); } return 0; } void WorkSheet::fixTabOrder() { for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) { if ( c + 1 < mColumns ) setTabOrder( mDisplayList[ r ][ c ], mDisplayList[ r ][ c + 1 ] ); else if ( r + 1 < mRows ) setTabOrder( mDisplayList[ r ][ c ], mDisplayList[ r + 1 ][ 0 ] ); } } TQString WorkSheet::currentDisplayAsXML() { KSGRD::SensorDisplay* display = currentDisplay(); if ( !display ) return TQString::null; /* We create an XML description of the current display. */ TQDomDocument doc( "KSysGuardDisplay" ); doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); TQDomElement element = doc.createElement( "display" ); doc.appendChild( element ); element.setAttribute( "class", display->className() ); display->saveSettings( doc, element ); return doc.toString(); } void WorkSheet::setIsOnTop( bool /* onTop */ ) { /* for ( uint r = 0; r < mRows; ++r ) for ( uint c = 0; c < mColumns; ++c ) mDisplayList[ r ][ c ]->setIsOnTop( onTop ); */ } #include "WorkSheet.moc"