You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/ksysguard/gui/WorkSheet.cpp

699 lines
20 KiB

/*
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 <tdelocale.h>
#include <tdemessagebox.h>
#include <tdepopupmenu.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::clipboard();
clip->setText( currentDisplayAsXML() );
removeDisplay( currentDisplay() );
}
void WorkSheet::copy()
{
if ( !currentDisplay() || currentDisplay()->isA( "DummyDisplay" ) )
return;
TQClipboard* clip = TQApplication::clipboard();
clip->setText( currentDisplayAsXML() );
}
void WorkSheet::paste()
{
uint row, column;
if ( !currentDisplay( &row, &column ) )
return;
TQClipboard* clip = TQApplication::clipboard();
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" ) {
TDEPopupMenu 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 ]->geometry().contains( e->pos() ) ) {
addDisplay( hostName, sensorName, sensorType, sensorDescr, r, c );
return;
}
}
}
TQSize WorkSheet::sizeHint() 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, TQ_SIGNAL( showPopupMenu( KSGRD::SensorDisplay* ) ),
TQ_SLOT( showPopupMenu( KSGRD::SensorDisplay* ) ) );
connect( newDisplay, TQ_SIGNAL( modified( bool ) ),
TQ_SLOT( setModified( bool ) ) );
}
mGridLayout->addWidget( mDisplayList[ row ][ column ], row, column );
if ( isVisible() ) {
mDisplayList[ row ][ column ]->show();
}
setMinimumSize(sizeHint());
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"