00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "selftestdialog_p.h"
00021 #include "agentmanager.h"
00022 #include "session_p.h"
00023 #include "servermanager_p.h"
00024
00025 #include <akonadi/private/xdgbasedirs_p.h>
00026
00027 #include <KDebug>
00028 #include <KIcon>
00029 #include <KFileDialog>
00030 #include <KLocale>
00031 #include <KMessageBox>
00032 #include <KRun>
00033 #include <KStandardDirs>
00034
00035 #include <QtCore/QFileInfo>
00036 #include <QtCore/QProcess>
00037 #include <QtCore/QSettings>
00038 #include <QtCore/QTextStream>
00039 #include <QtDBus/QtDBus>
00040 #include <QtGui/QApplication>
00041 #include <QtGui/QClipboard>
00042 #include <QtGui/QStandardItemModel>
00043 #include <QtSql/QSqlDatabase>
00044
00045
00046
00047 #define AKONADI_CONTROL_SERVICE QLatin1String("org.freedesktop.Akonadi.Control")
00048 #define AKONADI_SERVER_SERVICE QLatin1String("org.freedesktop.Akonadi")
00049
00050 using namespace Akonadi;
00051
00052 static QString makeLink( const QString &file )
00053 {
00054 return QString::fromLatin1( "<a href=\"%1\">%2</a>" ).arg( file, file );
00055 }
00056
00057 enum SelfTestRole {
00058 ResultTypeRole = Qt::UserRole,
00059 FileIncludeRole,
00060 ListDirectoryRole,
00061 EnvVarRole,
00062 SummaryRole,
00063 DetailsRole
00064 };
00065
00066 SelfTestDialog::SelfTestDialog(QWidget * parent) :
00067 KDialog( parent )
00068 {
00069 setCaption( i18n( "Akonadi Server Self-Test" ) );
00070 setButtons( Close | User1 | User2 );
00071 setButtonText( User1, i18n( "Save Report..." ) );
00072 setButtonIcon( User1, KIcon( "document-save" ) );
00073 setButtonText( User2, i18n( "Copy Report to Clipboard" ) );
00074 setButtonIcon( User2, KIcon( "edit-copy" ) );
00075 showButtonSeparator( true );
00076 ui.setupUi( mainWidget() );
00077
00078 mTestModel = new QStandardItemModel( this );
00079 ui.testView->setModel( mTestModel );
00080 connect( ui.testView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
00081 SLOT(selectionChanged(QModelIndex)) );
00082 connect( ui.detailsLabel, SIGNAL(linkActivated(QString)), SLOT(linkActivated(QString)) );
00083
00084 connect( this, SIGNAL(user1Clicked()), SLOT(saveReport()) );
00085 connect( this, SIGNAL(user2Clicked()), SLOT(copyReport()) );
00086
00087 runTests();
00088 }
00089
00090 void SelfTestDialog::hideIntroduction()
00091 {
00092 ui.introductionLabel->hide();
00093 }
00094
00095 QStandardItem* SelfTestDialog::report( ResultType type, const KLocalizedString & summary, const KLocalizedString & details)
00096 {
00097 QStandardItem *item = new QStandardItem( summary.toString() );
00098 switch ( type ) {
00099 case Skip:
00100 item->setIcon( KIcon( "dialog-ok" ) );
00101 break;
00102 case Success:
00103 item->setIcon( KIcon( "dialog-ok-apply" ) );
00104 break;
00105 case Warning:
00106 item->setIcon( KIcon( "dialog-warning" ) );
00107 break;
00108 case Error:
00109 default:
00110 item->setIcon( KIcon( "dialog-error" ) );
00111 }
00112 item->setEditable( false );
00113 item->setWhatsThis( details.toString() );
00114 item->setData( type, ResultTypeRole );
00115 item->setData( summary.toString( 0 ), SummaryRole );
00116 item->setData( details.toString( 0 ), DetailsRole );
00117 mTestModel->appendRow( item );
00118 return item;
00119 }
00120
00121 void SelfTestDialog::selectionChanged(const QModelIndex &index )
00122 {
00123 if ( index.isValid() ) {
00124 ui.detailsLabel->setText( index.data( Qt::WhatsThisRole ).toString() );
00125 ui.detailsGroup->setEnabled( true );
00126 } else {
00127 ui.detailsLabel->setText( QString() );
00128 ui.detailsGroup->setEnabled( false );
00129 }
00130 }
00131
00132 void SelfTestDialog::runTests()
00133 {
00134 testSQLDriver();
00135 testMySQLServer();
00136 testMySQLServerLog();
00137 testMySQLServerConfig();
00138 testAkonadiCtl();
00139 testServerStatus();
00140 testProtocolVersion();
00141 testResources();
00142 testServerLog();
00143 testControlLog();
00144 }
00145
00146 QVariant SelfTestDialog::serverSetting(const QString & group, const QString & key, const QVariant &def ) const
00147 {
00148 const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite );
00149 QSettings settings( serverConfigFile, QSettings::IniFormat );
00150 settings.beginGroup( group );
00151 return settings.value( key, def );
00152 }
00153
00154 bool SelfTestDialog::useStandaloneMysqlServer() const
00155 {
00156 const QString driver = serverSetting( "General", "Driver", "QMYSQL" ).toString();
00157 if ( driver != QLatin1String( "QMYSQL" ) )
00158 return false;
00159 const bool startServer = serverSetting( driver, "StartServer", true ).toBool();
00160 if ( !startServer )
00161 return false;
00162 return true;
00163 }
00164
00165 bool SelfTestDialog::runProcess(const QString & app, const QStringList & args, QString & result) const
00166 {
00167 QProcess proc;
00168 proc.start( app, args );
00169 const bool rv = proc.waitForFinished();
00170 result.clear();
00171 result += QString::fromLocal8Bit( proc.readAllStandardError() );
00172 result += QString::fromLocal8Bit( proc.readAllStandardOutput() );
00173 return rv;
00174 }
00175
00176 void SelfTestDialog::testSQLDriver()
00177 {
00178 const QString driver = serverSetting( "General", "Driver", "QMYSQL" ).toString();
00179 const QStringList availableDrivers = QSqlDatabase::drivers();
00180 const KLocalizedString details = ki18n( "The QtSQL driver '%1' is required by your current Akonadi server configuration.\n"
00181 "The following drivers are installed: %2.\n"
00182 "Make sure the required driver is installed." )
00183 .subs( driver )
00184 .subs( availableDrivers.join( QLatin1String(", ") ) );
00185 QStandardItem *item = 0;
00186 if ( availableDrivers.contains( driver ) )
00187 item = report( Success, ki18n( "Database driver found." ), details );
00188 else
00189 item = report( Error, ki18n( "Database driver not found." ), details );
00190 item->setData( XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite ), FileIncludeRole );
00191 }
00192
00193 void SelfTestDialog::testMySQLServer()
00194 {
00195 if ( !useStandaloneMysqlServer() ) {
00196 report( Skip, ki18n( "MySQL server executable not tested." ),
00197 ki18n( "The current configuration does not require an internal MySQL server." ) );
00198 return;
00199 }
00200
00201 const QString driver = serverSetting( "General", "Driver", "QMYSQL" ).toString();
00202 const QString serverPath = serverSetting( driver, "ServerPath", "" ).toString();
00203
00204 const KLocalizedString details = ki18n( "You currently have configured Akonadi to use the MySQL server '%1'.\n"
00205 "Make sure you have the MySQL server installed, set the correct path and ensure you have the "
00206 "necessary read and execution rights on the server executable. The server executable is typically "
00207 "called 'mysqld', its locations varies depending on the distribution." ).subs( serverPath );
00208
00209 QFileInfo info( serverPath );
00210 if ( !info.exists() )
00211 report( Error, ki18n( "MySQL server not found." ), details );
00212 else if ( !info.isReadable() )
00213 report( Error, ki18n( "MySQL server not readable." ), details );
00214 else if ( !info.isExecutable() )
00215 report( Error, ki18n( "MySQL server not executable." ), details );
00216 else if ( !serverPath.contains( "mysqld" ) )
00217 report( Warning, ki18n( "MySQL found with unexpected name." ), details );
00218 else
00219 report( Success, ki18n( "MySQL server found." ), details );
00220
00221
00222 QString result;
00223 if ( runProcess( serverPath, QStringList() << QLatin1String( "--version" ), result ) ) {
00224 const KLocalizedString details = ki18n( "MySQL server found: %1" ).subs( result );
00225 report( Success, ki18n( "MySQL server is executable." ), details );
00226 } else {
00227 const KLocalizedString details = ki18n( "Executing the MySQL server '%1' failed with the following error message: '%2'" )
00228 .subs( serverPath ).subs( result );
00229 report( Error, ki18n( "Executing the MySQL server failed." ), details );
00230 }
00231 }
00232
00233 void SelfTestDialog::testMySQLServerLog()
00234 {
00235 if ( !useStandaloneMysqlServer() ) {
00236 report( Skip, ki18n( "MySQL server error log not tested." ),
00237 ki18n( "The current configuration does not require an internal MySQL server." ) );
00238 return;
00239 }
00240
00241 const QString logFileName = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi/db_data" ) )
00242 + QDir::separator() + QString::fromLatin1( "mysql.err" );
00243 const QFileInfo logFileInfo( logFileName );
00244 if ( !logFileInfo.exists() || logFileInfo.size() == 0 ) {
00245 report( Success, ki18n( "No current MySQL error log found." ),
00246 ki18n( "The MySQL server did not report any errors during this startup into '%1'." ).subs( logFileName ) );
00247 return;
00248 }
00249 QFile logFile( logFileName );
00250 if ( !logFile.open( QFile::ReadOnly | QFile::Text ) ) {
00251 report( Error, ki18n( "MySQL error log not readable." ),
00252 ki18n( "A MySQL server error log file was found but is not readable: %1" ).subs( makeLink( logFileName ) ) );
00253 return;
00254 }
00255 bool warningsFound = false;
00256 QStandardItem *item = 0;
00257 while ( !logFile.atEnd() ) {
00258 const QString line = QString::fromUtf8( logFile.readLine() );
00259 if ( line.contains( QLatin1String( "error" ), Qt::CaseInsensitive ) ) {
00260 item = report( Error, ki18n( "MySQL server log contains errors." ),
00261 ki18n( "The MySQL server error log file '%1' contains errors." ).subs( makeLink( logFileName ) ) );
00262 item->setData( logFileName, FileIncludeRole );
00263 return;
00264 }
00265 if ( !warningsFound && line.contains( QLatin1String( "warn" ), Qt::CaseInsensitive ) ) {
00266 warningsFound = true;
00267 }
00268 }
00269 if ( warningsFound ) {
00270 item = report( Warning, ki18n( "MySQL server log contains warnings." ),
00271 ki18n( "The MySQL server log file '%1' contains warnings." ).subs( makeLink( logFileName ) ) );
00272 } else {
00273 item = report( Success, ki18n( "MySQL server log contains no errors." ),
00274 ki18n( "The MySQL server log file '%1' does not contain any errors or warnings." )
00275 .subs( makeLink( logFileName ) ) );
00276 }
00277 item->setData( logFileName, FileIncludeRole );
00278
00279 logFile.close();
00280 }
00281
00282 void SelfTestDialog::testMySQLServerConfig()
00283 {
00284 if ( !useStandaloneMysqlServer() ) {
00285 report( Skip, ki18n( "MySQL server configuration not tested." ),
00286 ki18n( "The current configuration does not require an internal MySQL server." ) );
00287 return;
00288 }
00289
00290 QStandardItem *item = 0;
00291 const QString globalConfig = XdgBaseDirs::findResourceFile( "config", QLatin1String( "akonadi/mysql-global.conf" ) );
00292 const QFileInfo globalConfigInfo( globalConfig );
00293 if ( !globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable() ) {
00294 item = report( Success, ki18n( "MySQL server default configuration found." ),
00295 ki18n( "The default configuration for the MySQL server was found and is readable at %1." )
00296 .subs( makeLink( globalConfig ) ) );
00297 item->setData( globalConfig, FileIncludeRole );
00298 } else {
00299 report( Error, ki18n( "MySQL server default configuration not found." ),
00300 ki18n( "The default configuration for the MySQL server was not found or was not readable. "
00301 "Check your Akonadi installation is complete and you have all required access rights." ) );
00302 }
00303
00304 const QString localConfig = XdgBaseDirs::findResourceFile( "config", QLatin1String( "akonadi/mysql-local.conf" ) );
00305 const QFileInfo localConfigInfo( localConfig );
00306 if ( localConfig.isEmpty() || !localConfigInfo.exists() ) {
00307 report( Skip, ki18n( "MySQL server custom configuration not available." ),
00308 ki18n( "The custom configuration for the MySQL server was not found but is optional." ) );
00309 } else if ( localConfigInfo.exists() && localConfigInfo.isReadable() ) {
00310 item = report( Success, ki18n( "MySQL server custom configuration found." ),
00311 ki18n( "The custom configuration for the MySQL server was found and is readable at %1" )
00312 .subs( makeLink( localConfig ) ) );
00313 item->setData( localConfig, FileIncludeRole );
00314 } else {
00315 report( Error, ki18n( "MySQL server custom configuration not readable." ),
00316 ki18n( "The custom configuration for the MySQL server was found at %1 but is not readable. "
00317 "Check your access rights." ).subs( makeLink( localConfig ) ) );
00318 }
00319
00320 const QString actualConfig = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) ) + QLatin1String("/mysql.conf");
00321 const QFileInfo actualConfigInfo( actualConfig );
00322 if ( actualConfig.isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable() ) {
00323 report( Error, ki18n( "MySQL server configuration not found or not readable." ),
00324 ki18n( "The MySQL server configuration was not found or is not readable." ) );
00325 } else {
00326 item = report( Success, ki18n( "MySQL server configuration is usable." ),
00327 ki18n( "The MySQL server configuration was found at %1 and is readable.").subs( makeLink( actualConfig ) ) );
00328 item->setData( actualConfig, FileIncludeRole );
00329 }
00330 }
00331
00332 void SelfTestDialog::testAkonadiCtl()
00333 {
00334 const QString path = KStandardDirs::findExe( QLatin1String("akonadictl") );
00335 if ( path.isEmpty() ) {
00336 report( Error, ki18n( "akonadictl not found" ),
00337 ki18n( "The program 'akonadictl' needs to be accessible in $PATH. "
00338 "Make sure you have the Akonadi server installed." ) );
00339 return;
00340 }
00341 QString result;
00342 if ( runProcess( path, QStringList() << QLatin1String( "status" ), result ) ) {
00343 report( Success, ki18n( "akonadictl found and usable" ),
00344 ki18n( "The program '%1' to control the Akonadi server was found "
00345 "and could be executed successfully.\nResult:\n%2" ).subs( path ).subs( result ) );
00346 } else {
00347 report( Error, ki18n( "akonadictl found but not usable" ),
00348 ki18n( "The program '%1' to control the Akonadi server was found "
00349 "but could not be executed successfully.\nResult:\n%2\n"
00350 "Make sure the Akonadi server is installed correctly." ).subs( path ).subs( result ) );
00351 }
00352 }
00353
00354 void SelfTestDialog::testServerStatus()
00355 {
00356 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( AKONADI_CONTROL_SERVICE ) ) {
00357 report( Success, ki18n( "Akonadi control process registered at D-Bus." ),
00358 ki18n( "The Akonadi control process is registered at D-Bus which typically indicates it is operational." ) );
00359 } else {
00360 report( Error, ki18n( "Akonadi control process not registered at D-Bus." ),
00361 ki18n( "The Akonadi control process is not registered at D-Bus which typically means it was not started "
00362 "or encountered a fatal error during startup." ) );
00363 }
00364
00365 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( AKONADI_SERVER_SERVICE ) ) {
00366 report( Success, ki18n( "Akonadi server process registered at D-Bus." ),
00367 ki18n( "The Akonadi server process is registered at D-Bus which typically indicates it is operational." ) );
00368 } else {
00369 report( Error, ki18n( "Akonadi server process not registered at D-Bus." ),
00370 ki18n( "The Akonadi server process is not registered at D-Bus which typically means it was not started "
00371 "or encountered a fatal error during startup." ) );
00372 }
00373 }
00374
00375 void SelfTestDialog::testProtocolVersion()
00376 {
00377 if ( Internal::serverProtocolVersion() < 0 ) {
00378 report( Skip, ki18n( "Protocol version check not possible." ),
00379 ki18n( "Without a connection to the server it is not possible to check if the protocol version meets the requirements." ) );
00380 return;
00381 }
00382 if ( Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion() ) {
00383 report( Error, ki18n( "Server protocol version is too old." ),
00384 ki18n( "The server protocol version is %1, but at least version %2 is required. "
00385 "Install a newer version of the Akonadi server." )
00386 .subs( Internal::serverProtocolVersion() )
00387 .subs( SessionPrivate::minimumProtocolVersion() ) );
00388 } else {
00389 report( Success, ki18n( "Server protocol version is recent enough." ),
00390 ki18n( "The server Protocol version is %1, which equal or newer than the required version %2." )
00391 .subs( Internal::serverProtocolVersion() )
00392 .subs( SessionPrivate::minimumProtocolVersion() ) );
00393 }
00394 }
00395
00396 void SelfTestDialog::testResources()
00397 {
00398 AgentType::List agentTypes = AgentManager::self()->types();
00399 bool resourceFound = false;
00400 foreach ( const AgentType &type, agentTypes ) {
00401 if ( type.capabilities().contains( "Resource" ) ) {
00402 resourceFound = true;
00403 break;
00404 }
00405 }
00406
00407 const QStringList pathList = XdgBaseDirs::findAllResourceDirs( "data", QLatin1String( "akonadi/agents" ) );
00408 QStandardItem *item = 0;
00409 if ( resourceFound ) {
00410 item = report( Success, ki18n( "Resource agents found." ), ki18n( "At least one resource agent has been found." ) );
00411 } else {
00412 item = report( Error, ki18n( "No resource agents found." ),
00413 ki18n( "No resource agents have been found, Akonadi is not usable without at least one. "
00414 "This usually means that no resource agents are installed or that there is a setup problem. "
00415 "The following paths have been searched: '%1'. "
00416 "The XDG_DATA_DIRS environment variable is set to '%2', make sure this includes all paths "
00417 "where Akonadi agents are installed to." )
00418 .subs( pathList.join( QLatin1String(" ") ) )
00419 .subs( QString::fromLocal8Bit( qgetenv( "XDG_DATA_DIRS" ) ) ) );
00420 }
00421 item->setData( pathList, ListDirectoryRole );
00422 item->setData( QByteArray( "XDG_DATA_DIRS" ), EnvVarRole );
00423 }
00424
00425 void Akonadi::SelfTestDialog::testServerLog()
00426 {
00427 QString serverLog = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) )
00428 + QDir::separator() + QString::fromLatin1( "akonadiserver.error" );
00429 QFileInfo info( serverLog );
00430 if ( !info.exists() || info.size() <= 0 ) {
00431 report( Success, ki18n( "No current Akonadi server error log found." ),
00432 ki18n( "The Akonadi server did not report any errors during its current startup." ) );
00433 } else {
00434 QStandardItem *item = report( Error, ki18n( "Current Akonadi server error log found." ),
00435 ki18n( "The Akonadi server did report error during startup into %1." ).subs( makeLink( serverLog ) ) );
00436 item->setData( serverLog, FileIncludeRole );
00437 }
00438
00439 serverLog += ".old";
00440 info.setFile( serverLog );
00441 if ( !info.exists() || info.size() <= 0 ) {
00442 report( Success, ki18n( "No previous Akonadi server error log found." ),
00443 ki18n( "The Akonadi server did not report any errors during its previous startup." ) );
00444 } else {
00445 QStandardItem *item = report( Error, ki18n( "Previous Akonadi server error log found." ),
00446 ki18n( "The Akonadi server did report error during its previous startup into %1." ).subs( makeLink( serverLog ) ) );
00447 item->setData( serverLog, FileIncludeRole );
00448 }
00449 }
00450
00451 void SelfTestDialog::testControlLog()
00452 {
00453 QString controlLog = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) )
00454 + QDir::separator() + QString::fromLatin1( "akonadi_control.error" );
00455 QFileInfo info( controlLog );
00456 if ( !info.exists() || info.size() <= 0 ) {
00457 report( Success, ki18n( "No current Akonadi control error log found." ),
00458 ki18n( "The Akonadi control process did not report any errors during its current startup." ) );
00459 } else {
00460 QStandardItem *item = report( Error, ki18n( "Current Akonadi control error log found." ),
00461 ki18n( "The Akonadi control process did report error during startup into %1." ).subs( makeLink( controlLog ) ) );
00462 item->setData( controlLog, FileIncludeRole );
00463 }
00464
00465 controlLog += ".old";
00466 info.setFile( controlLog );
00467 if ( !info.exists() || info.size() <= 0 ) {
00468 report( Success, ki18n( "No previous Akonadi control error log found." ),
00469 ki18n( "The Akonadi control process did not report any errors during its previous startup." ) );
00470 } else {
00471 QStandardItem *item = report( Error, ki18n( "Previous Akonadi control error log found." ),
00472 ki18n( "The Akonadi control process did report error during its previous startup into %1." ).subs( makeLink( controlLog ) ) );
00473 item->setData( controlLog, FileIncludeRole );
00474 }
00475 }
00476
00477
00478 QString SelfTestDialog::createReport()
00479 {
00480 QString result;
00481 QTextStream s( &result );
00482 s << "Akonadi Server Self-Test Report" << endl;
00483 s << "===============================" << endl;
00484
00485 for ( int i = 0; i < mTestModel->rowCount(); ++i ) {
00486 QStandardItem *item = mTestModel->item( i );
00487 s << endl;
00488 s << "Test " << (i + 1) << ": ";
00489 switch ( item->data( ResultTypeRole ).toInt() ) {
00490 case Skip:
00491 s << "SKIP"; break;
00492 case Success:
00493 s << "SUCCESS"; break;
00494 case Warning:
00495 s << "WARNING"; break;
00496 case Error:
00497 default:
00498 s << "ERROR"; break;
00499 }
00500 s << endl << "--------" << endl;
00501 s << endl;
00502 s << item->data( SummaryRole ).toString() << endl;
00503 s << "Details: " << item->data( DetailsRole ).toString() << endl;
00504 if ( item->data( FileIncludeRole ).isValid() ) {
00505 s << endl;
00506 const QString fileName = item->data( FileIncludeRole ).toString();
00507 QFile f( fileName );
00508 if ( f.open( QFile::ReadOnly ) ) {
00509 s << "File content of '" << fileName << "':" << endl;
00510 s << f.readAll() << endl;
00511 } else {
00512 s << "File '" << fileName << "' could not be opened" << endl;
00513 }
00514 }
00515 if ( item->data( ListDirectoryRole ).isValid() ) {
00516 s << endl;
00517 const QStringList pathList = item->data( ListDirectoryRole ).toStringList();
00518 if ( pathList.isEmpty() )
00519 s << "Directory list is empty." << endl;
00520 foreach ( const QString &path, pathList ) {
00521 s << "Directory listing of '" << path << "':" << endl;
00522 QDir dir( path );
00523 dir.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot );
00524 foreach ( const QString &entry, dir.entryList() )
00525 s << entry << endl;
00526 }
00527 }
00528 if ( item->data( EnvVarRole ).isValid() ) {
00529 s << endl;
00530 const QByteArray envVarName = item->data( EnvVarRole ).toByteArray();
00531 const QByteArray envVarValue = qgetenv( envVarName );
00532 s << "Environment variable " << envVarName << " is set to '" << envVarValue << "'" << endl;
00533 }
00534 }
00535
00536 s << endl;
00537 s.flush();
00538 return result;
00539 }
00540
00541 void SelfTestDialog::saveReport()
00542 {
00543 const QString fileName = KFileDialog::getSaveFileName( KUrl(), QString(), this, i18n("Save Test Report") );
00544 if ( fileName.isEmpty() )
00545 return;
00546
00547 QFile file( fileName );
00548 if ( !file.open( QFile::ReadWrite ) ) {
00549 KMessageBox::error( this, i18n( "Could not open file '%1'", fileName ) );
00550 return;
00551 }
00552
00553 file.write( createReport().toUtf8() );
00554 file.close();
00555 }
00556
00557 void SelfTestDialog::copyReport()
00558 {
00559 QApplication::clipboard()->setText( createReport() );
00560 }
00561
00562 void SelfTestDialog::linkActivated(const QString & link)
00563 {
00564 KRun::runUrl( KUrl::fromPath( link ), "text/plain", this );
00565 }
00566
00567
00568
00569 #include "selftestdialog_p.moc"