kbugreport.cpp
00001 /* This file is part of the KDE project 00002 Copyright (C) 1999 David Faure <faure@kde.org> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include <qhbuttongroup.h> 00021 #include <qpushbutton.h> 00022 #include <qlabel.h> 00023 #include <qlayout.h> 00024 #include <qmultilineedit.h> 00025 #include <qradiobutton.h> 00026 #include <qwhatsthis.h> 00027 #include <qregexp.h> 00028 00029 #include <kaboutdata.h> 00030 #include <kapplication.h> 00031 #include <kconfig.h> 00032 #include <kdebug.h> 00033 #include <klineedit.h> 00034 #include <klocale.h> 00035 #include <kmessagebox.h> 00036 #include <kprocess.h> 00037 #include <kstandarddirs.h> 00038 #include <kstdguiitem.h> 00039 #include <kurl.h> 00040 #include <kurllabel.h> 00041 00042 #include "kbugreport.h" 00043 00044 #include <stdio.h> 00045 #include <pwd.h> 00046 #include <unistd.h> 00047 00048 #include <sys/utsname.h> 00049 00050 #include "kdepackages.h" 00051 #include <kcombobox.h> 00052 #include <config.h> 00053 #include <ktempfile.h> 00054 #include <qtextstream.h> 00055 #include <qfile.h> 00056 00057 class KBugReportPrivate { 00058 public: 00059 KComboBox *appcombo; 00060 QString lastError; 00061 QString kde_version; 00062 QString appname; 00063 QString os; 00064 QPushButton *submitBugButton; 00065 KURL url; 00066 }; 00067 00068 KBugReport::KBugReport( QWidget * parentw, bool modal, const KAboutData *aboutData ) 00069 : KDialogBase( Plain, 00070 i18n("Submit Bug Report"), 00071 Ok | Cancel, 00072 Ok, 00073 parentw, 00074 "KBugReport", 00075 modal, // modal 00076 true // separator 00077 ) 00078 { 00079 d = new KBugReportPrivate; 00080 00081 // Use supplied aboutdata, otherwise the one from the active instance 00082 // otherwise the KGlobal one. _activeInstance should neved be 0L in theory. 00083 m_aboutData = aboutData 00084 ? aboutData 00085 : ( KGlobal::_activeInstance ? KGlobal::_activeInstance->aboutData() 00086 : KGlobal::instance()->aboutData() ); 00087 m_process = 0; 00088 QWidget * parent = plainPage(); 00089 d->submitBugButton = 0; 00090 00091 if ( m_aboutData->bugAddress() == QString::fromLatin1("submit@bugs.kde.org") ) 00092 { 00093 // This is a core KDE application -> redirect to the web form 00094 d->submitBugButton = new QPushButton( parent ); 00095 setButtonCancel( KStdGuiItem::close() ); 00096 } 00097 00098 QLabel * tmpLabel; 00099 QVBoxLayout * lay = new QVBoxLayout( parent, 0, spacingHint() ); 00100 00101 QGridLayout *glay = new QGridLayout( lay, 4, 3 ); 00102 glay->setColStretch( 1, 10 ); 00103 glay->setColStretch( 2, 10 ); 00104 00105 int row = 0; 00106 00107 if ( !d->submitBugButton ) 00108 { 00109 // From 00110 QString qwtstr = i18n( "Your email address. If incorrect, use the Configure Email button to change it" ); 00111 tmpLabel = new QLabel( i18n("From:"), parent ); 00112 glay->addWidget( tmpLabel, row,0 ); 00113 QWhatsThis::add( tmpLabel, qwtstr ); 00114 m_from = new QLabel( parent ); 00115 glay->addWidget( m_from, row, 1 ); 00116 QWhatsThis::add( m_from, qwtstr ); 00117 00118 00119 // Configure email button 00120 m_configureEmail = new QPushButton( i18n("Configure Email..."), 00121 parent ); 00122 connect( m_configureEmail, SIGNAL( clicked() ), this, 00123 SLOT( slotConfigureEmail() ) ); 00124 glay->addMultiCellWidget( m_configureEmail, 0, 2, 2, 2, AlignTop|AlignRight ); 00125 00126 // To 00127 qwtstr = i18n( "The email address this bug report is sent to." ); 00128 tmpLabel = new QLabel( i18n("To:"), parent ); 00129 glay->addWidget( tmpLabel, ++row,0 ); 00130 QWhatsThis::add( tmpLabel, qwtstr ); 00131 tmpLabel = new QLabel( m_aboutData->bugAddress(), parent ); 00132 glay->addWidget( tmpLabel, row, 1 ); 00133 QWhatsThis::add( tmpLabel, qwtstr ); 00134 00135 setButtonOK( KGuiItem( i18n("&Send"), "mail_send", i18n( "Send bug report." ), 00136 i18n( "Send this bug report to %1." ).arg( m_aboutData->bugAddress() ) ) ); 00137 00138 } 00139 else 00140 { 00141 m_configureEmail = 0; 00142 m_from = 0; 00143 showButtonOK( false ); 00144 } 00145 00146 // Program name 00147 QString qwtstr = i18n( "The application for which you wish to submit a bug report - if incorrect, please use the Report Bug menu item of the correct application" ); 00148 tmpLabel = new QLabel( i18n("Application: "), parent ); 00149 glay->addWidget( tmpLabel, ++row, 0 ); 00150 QWhatsThis::add( tmpLabel, qwtstr ); 00151 d->appcombo = new KComboBox( false, parent, "app"); 00152 QWhatsThis::add( d->appcombo, qwtstr ); 00153 d->appcombo->insertStrList((const char**)packages); 00154 connect(d->appcombo, SIGNAL(activated(int)), SLOT(appChanged(int))); 00155 d->appname = QString::fromLatin1( m_aboutData 00156 ? m_aboutData->productName() 00157 : qApp->name() ); 00158 glay->addWidget( d->appcombo, row, 1 ); 00159 int index = 0; 00160 for (; index < d->appcombo->count(); index++) { 00161 if (d->appcombo->text(index) == d->appname) { 00162 break; 00163 } 00164 } 00165 if (index == d->appcombo->count()) { // not present 00166 d->appcombo->insertItem(d->appname); 00167 } 00168 d->appcombo->setCurrentItem(index); 00169 00170 QWhatsThis::add( tmpLabel, qwtstr ); 00171 00172 // Version 00173 qwtstr = i18n( "The version of this application - please make sure that no newer version is available before sending a bug report" ); 00174 tmpLabel = new QLabel( i18n("Version:"), parent ); 00175 glay->addWidget( tmpLabel, ++row, 0 ); 00176 QWhatsThis::add( tmpLabel, qwtstr ); 00177 if (m_aboutData) 00178 m_strVersion = m_aboutData->version(); 00179 else 00180 m_strVersion = i18n("no version set (programmer error!)"); 00181 d->kde_version = QString::fromLatin1( KDE_VERSION_STRING ); 00182 d->kde_version += ", " + QString::fromLatin1( KDE_DISTRIBUTION_TEXT ); 00183 if ( !d->submitBugButton ) 00184 m_strVersion += " " + d->kde_version; 00185 m_version = new QLabel( m_strVersion, parent ); 00186 //glay->addWidget( m_version, row, 1 ); 00187 glay->addMultiCellWidget( m_version, row, row, 1, 2 ); 00188 QWhatsThis::add( m_version, qwtstr ); 00189 00190 tmpLabel = new QLabel(i18n("OS:"), parent); 00191 glay->addWidget( tmpLabel, ++row, 0 ); 00192 00193 struct utsname unameBuf; 00194 uname( &unameBuf ); 00195 d->os = QString::fromLatin1( unameBuf.sysname ) + 00196 " (" + QString::fromLatin1( unameBuf.machine ) + ") " 00197 "release " + QString::fromLatin1( unameBuf.release ); 00198 00199 tmpLabel = new QLabel(d->os, parent); 00200 glay->addMultiCellWidget( tmpLabel, row, row, 1, 2 ); 00201 00202 tmpLabel = new QLabel(i18n("Compiler:"), parent); 00203 glay->addWidget( tmpLabel, ++row, 0 ); 00204 tmpLabel = new QLabel(QString::fromLatin1(KDE_COMPILER_VERSION), parent); 00205 glay->addMultiCellWidget( tmpLabel, row, row, 1, 2 ); 00206 00207 if ( !d->submitBugButton ) 00208 { 00209 // Severity 00210 m_bgSeverity = new QHButtonGroup( i18n("Se&verity"), parent ); 00211 static const char * const sevNames[5] = { "critical", "grave", "normal", "wishlist", "i18n" }; 00212 const QString sevTexts[5] = { i18n("Critical"), i18n("Grave"), i18n("normal severity","Normal"), i18n("Wishlist"), i18n("Translation") }; 00213 00214 for (int i = 0 ; i < 5 ; i++ ) 00215 { 00216 // Store the severity string as the name 00217 QRadioButton *rb = new QRadioButton( sevTexts[i], m_bgSeverity, sevNames[i] ); 00218 if (i==2) rb->setChecked(true); // default : "normal" 00219 } 00220 00221 lay->addWidget( m_bgSeverity ); 00222 00223 // Subject 00224 QHBoxLayout * hlay = new QHBoxLayout( lay ); 00225 tmpLabel = new QLabel( i18n("S&ubject: "), parent ); 00226 hlay->addWidget( tmpLabel ); 00227 m_subject = new KLineEdit( parent ); 00228 m_subject->setFocus(); 00229 tmpLabel->setBuddy(m_subject); 00230 hlay->addWidget( m_subject ); 00231 00232 QString text = i18n("Enter the text (in English if possible) that you wish to submit for the " 00233 "bug report.\n" 00234 "If you press \"Send\", a mail message will be sent to the maintainer of " 00235 "this program.\n"); 00236 QLabel * label = new QLabel( parent, "label" ); 00237 00238 label->setText( text ); 00239 lay->addWidget( label ); 00240 00241 // The multiline-edit 00242 m_lineedit = new QMultiLineEdit( parent, "QMultiLineEdit" ); 00243 m_lineedit->setMinimumHeight( 180 ); // make it big 00244 m_lineedit->setWordWrap(QMultiLineEdit::WidgetWidth); 00245 lay->addWidget( m_lineedit, 10 /*stretch*/ ); 00246 00247 slotSetFrom(); 00248 } else { 00249 // Point to the web form 00250 00251 lay->addSpacing(10); 00252 QString text = i18n("To submit a bug report, click on the button below.\n" 00253 "This will open a web browser window on http://bugs.kde.org where you will find a form to fill in.\n" 00254 "The information displayed above will be transferred to that server."); 00255 QLabel * label = new QLabel( text, parent, "label"); 00256 lay->addWidget( label ); 00257 lay->addSpacing(10); 00258 00259 updateURL(); 00260 d->submitBugButton->setText( i18n("&Launch Bug Report Wizard") ); 00261 d->submitBugButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed); 00262 lay->addWidget( d->submitBugButton ); 00263 lay->addSpacing(10); 00264 00265 connect( d->submitBugButton, SIGNAL(clicked()), 00266 this, SLOT(slotOk())); 00267 } 00268 } 00269 00270 KBugReport::~KBugReport() 00271 { 00272 delete d; 00273 } 00274 00275 void KBugReport::updateURL() 00276 { 00277 KURL url ( "http://bugs.kde.org/wizard.cgi" ); 00278 url.addQueryItem( "os", d->os ); 00279 url.addQueryItem( "compiler", KDE_COMPILER_VERSION ); 00280 url.addQueryItem( "kdeVersion", d->kde_version ); 00281 url.addQueryItem( "appVersion", m_strVersion ); 00282 url.addQueryItem( "package", d->appcombo->currentText() ); 00283 url.addQueryItem( "kbugreport", "1" ); 00284 d->url = url; 00285 } 00286 00287 void KBugReport::appChanged(int i) 00288 { 00289 QString appName = d->appcombo->text(i); 00290 int index = appName.find( '/' ); 00291 if ( index > 0 ) 00292 appName = appName.left( index ); 00293 kdDebug() << "appName " << appName << endl; 00294 00295 if (d->appname == appName && m_aboutData) 00296 m_strVersion = m_aboutData->version(); 00297 else 00298 m_strVersion = i18n("unknown program name", "unknown"); 00299 00300 if ( !d->submitBugButton ) 00301 m_strVersion += d->kde_version; 00302 00303 m_version->setText(m_strVersion); 00304 if ( d->submitBugButton ) 00305 updateURL(); 00306 } 00307 00308 void KBugReport::slotConfigureEmail() 00309 { 00310 if (m_process) return; 00311 m_process = new KProcess; 00312 *m_process << QString::fromLatin1("kcmshell") << QString::fromLatin1("kcm_useraccount"); 00313 connect(m_process, SIGNAL(processExited(KProcess *)), SLOT(slotSetFrom())); 00314 if (!m_process->start()) 00315 { 00316 kdDebug() << "Couldn't start kcmshell.." << endl; 00317 delete m_process; 00318 m_process = 0; 00319 return; 00320 } 00321 m_configureEmail->setEnabled(false); 00322 } 00323 00324 void KBugReport::slotSetFrom() 00325 { 00326 delete m_process; 00327 m_process = 0; 00328 m_configureEmail->setEnabled(true); 00329 00330 // ### KDE4: why oh why is KEmailSettings in kio? 00331 KConfig emailConf( QString::fromLatin1("emaildefaults") ); 00332 00333 // find out the default profile 00334 emailConf.setGroup( QString::fromLatin1("Defaults") ); 00335 QString profile = QString::fromLatin1("PROFILE_"); 00336 profile += emailConf.readEntry( QString::fromLatin1("Profile"), 00337 QString::fromLatin1("Default") ); 00338 00339 emailConf.setGroup( profile ); 00340 QString fromaddr = emailConf.readEntry( QString::fromLatin1("EmailAddress") ); 00341 if (fromaddr.isEmpty()) { 00342 struct passwd *p; 00343 p = getpwuid(getuid()); 00344 fromaddr = QString::fromLatin1(p->pw_name); 00345 } else { 00346 QString name = emailConf.readEntry( QString::fromLatin1("FullName")); 00347 if (!name.isEmpty()) 00348 fromaddr = name + QString::fromLatin1(" <") + fromaddr + QString::fromLatin1(">"); 00349 } 00350 m_from->setText( fromaddr ); 00351 } 00352 00353 void KBugReport::slotUrlClicked(const QString &urlText) 00354 { 00355 if ( kapp ) 00356 kapp->invokeBrowser( urlText ); 00357 00358 // When using the web form, a click can also close the window, as there's 00359 // not much to do. It also gives the user a direct response to his click: 00360 if ( d->submitBugButton ) 00361 KDialogBase::slotCancel(); 00362 } 00363 00364 00365 void KBugReport::slotOk( void ) 00366 { 00367 if ( d->submitBugButton ) { 00368 if ( kapp ) 00369 kapp->invokeBrowser( d->url.url() ); 00370 return; 00371 } 00372 00373 if( m_lineedit->text().isEmpty() || 00374 m_subject->text().isEmpty() ) 00375 { 00376 QString msg = i18n("You must specify both a subject and a description " 00377 "before the report can be sent."); 00378 KMessageBox::error(this,msg); 00379 return; 00380 } 00381 00382 switch ( m_bgSeverity->id( m_bgSeverity->selected() ) ) 00383 { 00384 case 0: // critical 00385 if ( KMessageBox::questionYesNo( this, i18n( 00386 "<p>You chose the severity <b>Critical</b>. " 00387 "Please note that this severity is intended only for bugs that</p>" 00388 "<ul><li>break unrelated software on the system (or the whole system)</li>" 00389 "<li>cause serious data loss</li>" 00390 "<li>introduce a security hole on the system where the affected package is installed</li></ul>\n" 00391 "<p>Does the bug you are reporting cause any of the above damage? " 00392 "If it does not, please select a lower severity. Thank you!</p>" ),QString::null,KStdGuiItem::cont(),KStdGuiItem::cancel() ) == KMessageBox::No ) 00393 return; 00394 break; 00395 case 1: // grave 00396 if ( KMessageBox::questionYesNo( this, i18n( 00397 "<p>You chose the severity <b>Grave</b>. " 00398 "Please note that this severity is intended only for bugs that</p>" 00399 "<ul><li>make the package in question unusable or mostly so</li>" 00400 "<li>cause data loss</li>" 00401 "<li>introduce a security hole allowing access to the accounts of users who use the affected package</li></ul>\n" 00402 "<p>Does the bug you are reporting cause any of the above damage? " 00403 "If it does not, please select a lower severity. Thank you!</p>" ),QString::null,KStdGuiItem::cont(),KStdGuiItem::cancel() ) == KMessageBox::No ) 00404 return; 00405 break; 00406 } 00407 if( !sendBugReport() ) 00408 { 00409 QString msg = i18n("Unable to send the bug report.\n" 00410 "Please submit a bug report manually...\n" 00411 "See http://bugs.kde.org/ for instructions."); 00412 KMessageBox::error(this, msg + "\n\n" + d->lastError); 00413 return; 00414 } 00415 00416 KMessageBox::information(this, 00417 i18n("Bug report sent, thank you for your input.")); 00418 accept(); 00419 } 00420 00421 void KBugReport::slotCancel() 00422 { 00423 if( !d->submitBugButton && ( m_lineedit->edited() || m_subject->edited() ) ) 00424 { 00425 int rc = KMessageBox::warningYesNo( this, 00426 i18n( "Close and discard\nedited message?" ), 00427 i18n( "Close Message" ), KStdGuiItem::discard(), KStdGuiItem::cont() ); 00428 if( rc == KMessageBox::No ) 00429 return; 00430 } 00431 KDialogBase::slotCancel(); 00432 } 00433 00434 00435 QString KBugReport::text() const 00436 { 00437 kdDebug() << m_bgSeverity->selected()->name() << endl; 00438 // Prepend the pseudo-headers to the contents of the mail 00439 QString severity = QString::fromLatin1(m_bgSeverity->selected()->name()); 00440 QString appname = d->appcombo->currentText(); 00441 QString os = QString::fromLatin1("OS: %1 (%2)\n"). 00442 arg(KDE_COMPILING_OS). 00443 arg(KDE_DISTRIBUTION_TEXT); 00444 QString bodyText; 00445 for(int i = 0; i < m_lineedit->numLines(); i++) 00446 { 00447 QString line = m_lineedit->textLine(i); 00448 if (!line.endsWith("\n")) 00449 line += '\n'; 00450 bodyText += line; 00451 } 00452 00453 if (severity == QString::fromLatin1("i18n") && KGlobal::locale()->language() != KLocale::defaultLanguage()) { 00454 // Case 1 : i18n bug 00455 QString package = QString::fromLatin1("i18n_%1").arg(KGlobal::locale()->language()); 00456 package = package.replace(QString::fromLatin1("_"), QString::fromLatin1("-")); 00457 return QString::fromLatin1("Package: %1").arg(package) + 00458 QString::fromLatin1("\n" 00459 "Application: %1\n" 00460 // not really i18n's version, so better here IMHO 00461 "Version: %2\n").arg(appname).arg(m_strVersion)+ 00462 os+QString::fromLatin1("\n")+bodyText; 00463 } else { 00464 appname = appname.replace(QString::fromLatin1("_"), QString::fromLatin1("-")); 00465 // Case 2 : normal bug 00466 return QString::fromLatin1("Package: %1\n" 00467 "Version: %2\n" 00468 "Severity: %3\n") 00469 .arg(appname).arg(m_strVersion).arg(severity)+ 00470 QString::fromLatin1("Compiler: %1\n").arg(KDE_COMPILER_VERSION)+ 00471 os+QString::fromLatin1("\n")+bodyText; 00472 } 00473 } 00474 00475 bool KBugReport::sendBugReport() 00476 { 00477 QString recipient ( m_aboutData ? 00478 m_aboutData->bugAddress() : 00479 QString::fromLatin1("submit@bugs.kde.org") ); 00480 00481 QString command; 00482 command = locate("exe", "ksendbugmail"); 00483 if (command.isEmpty()) 00484 command = KStandardDirs::findExe( QString::fromLatin1("ksendbugmail") ); 00485 00486 KTempFile outputfile; 00487 outputfile.close(); 00488 00489 QString subject = m_subject->text(); 00490 command += " --subject "; 00491 command += KProcess::quote(subject); 00492 command += " --recipient "; 00493 command += KProcess::quote(recipient); 00494 command += " > "; 00495 command += KProcess::quote(outputfile.name()); 00496 00497 fflush(stdin); 00498 fflush(stderr); 00499 00500 FILE * fd = popen(QFile::encodeName(command), "w"); 00501 if (!fd) 00502 { 00503 kdError() << "Unable to open a pipe to " << command << endl; 00504 return false; 00505 } 00506 00507 QString btext = text(); 00508 fwrite(btext.ascii(),btext.length(),1,fd); 00509 fflush(fd); 00510 00511 int error = pclose(fd); 00512 kdDebug() << "exit status1 " << error << " " << (WIFEXITED(error)) << " " << WEXITSTATUS(error) << endl; 00513 00514 if ((WIFEXITED(error)) && WEXITSTATUS(error) == 1) { 00515 QFile of(outputfile.name()); 00516 if (of.open(IO_ReadOnly )) { 00517 QTextStream is(&of); 00518 is.setEncoding(QTextStream::UnicodeUTF8); 00519 QString line; 00520 while (!is.eof()) 00521 line = is.readLine(); 00522 d->lastError = line; 00523 } else { 00524 d->lastError = QString::null; 00525 } 00526 outputfile.unlink(); 00527 return false; 00528 } 00529 outputfile.unlink(); 00530 return true; 00531 } 00532 00533 void KBugReport::virtual_hook( int id, void* data ) 00534 { KDialogBase::virtual_hook( id, data ); } 00535 00536 #include "kbugreport.moc"