KDEWebKit
kwebpage.cpp
Go to the documentation of this file.
00001 /* 00002 * This file is part of the KDE project. 00003 * 00004 * Copyright (C) 2008 Dirk Mueller <mueller@kde.org> 00005 * Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org> 00006 * Copyright (C) 2008 Michael Howell <mhowell123@gmail.com> 00007 * Copyright (C) 2009,2010 Dawit Alemayehu <adawit@kde.org> 00008 * 00009 * This library is free software; you can redistribute it and/or 00010 * modify it under the terms of the GNU Library General Public 00011 * License as published by the Free Software Foundation; either 00012 * version 2 of the License, or (at your option) any later version. 00013 * 00014 * This library is distributed in the hope that it will be useful, 00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 * Library General Public License for more details. 00018 * 00019 * You should have received a copy of the GNU Library General Public License 00020 * along with this library; see the file COPYING.LIB. If not, write to 00021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00022 * Boston, MA 02110-1301, USA. 00023 * 00024 */ 00025 00026 // Own 00027 #include "kwebpage.h" 00028 #include "kwebwallet.h" 00029 00030 // Local 00031 #include "kwebpluginfactory.h" 00032 00033 // KDE 00034 #include <kaction.h> 00035 #include <kfiledialog.h> 00036 #include <kprotocolmanager.h> 00037 #include <kjobuidelegate.h> 00038 #include <krun.h> 00039 #include <kstandarddirs.h> 00040 #include <kstandardshortcut.h> 00041 #include <kurl.h> 00042 #include <kdebug.h> 00043 #include <kshell.h> 00044 #include <kmimetypetrader.h> 00045 #include <klocalizedstring.h> 00046 #include <ktemporaryfile.h> 00047 #include <kio/accessmanager.h> 00048 #include <kio/job.h> 00049 #include <kio/copyjob.h> 00050 #include <kio/jobuidelegate.h> 00051 #include <kio/renamedialog.h> 00052 #include <kparts/browseropenorsavequestion.h> 00053 00054 // Qt 00055 #include <QtCore/QPointer> 00056 #include <QtCore/QFileInfo> 00057 #include <QtCore/QCoreApplication> 00058 #include <QtWebKit/QWebFrame> 00059 #include <QtNetwork/QNetworkReply> 00060 00061 00062 #define QL1S(x) QLatin1String(x) 00063 #define QL1C(x) QLatin1Char(x) 00064 00065 static void reloadRequestWithoutDisposition (QNetworkReply* reply) 00066 { 00067 QNetworkRequest req (reply->request()); 00068 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00069 00070 QWebFrame* frame = qobject_cast<QWebFrame*> (req.originatingObject()); 00071 if (!frame) 00072 return; 00073 00074 frame->load(req); 00075 } 00076 00077 static bool isMimeTypeAssociatedWithSelf(const KService::Ptr &offer) 00078 { 00079 if (!offer) 00080 return false; 00081 00082 kDebug(800) << offer->desktopEntryName(); 00083 00084 const QString& appName = QCoreApplication::applicationName(); 00085 00086 if (appName == offer->desktopEntryName() || offer->exec().trimmed().startsWith(appName)) 00087 return true; 00088 00089 // konqueror exception since it uses kfmclient to open html content... 00090 if (appName == QL1S("konqueror") && offer->exec().trimmed().startsWith(QL1S("kfmclient"))) 00091 return true; 00092 00093 return false; 00094 } 00095 00096 static void extractMimeType(const QNetworkReply* reply, QString& mimeType) 00097 { 00098 mimeType.clear(); 00099 const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00100 if (metaData.contains(QL1S("content-type"))) 00101 mimeType = metaData.value(QL1S("content-type")); 00102 00103 if (!mimeType.isEmpty()) 00104 return; 00105 00106 if (!reply->hasRawHeader("Content-Type")) 00107 return; 00108 00109 const QString value (QL1S(reply->rawHeader("Content-Type").simplified().constData())); 00110 const int index = value.indexOf(QL1C(';')); 00111 mimeType = ((index == -1) ? value : value.left(index)); 00112 } 00113 00114 static bool downloadResource (const KUrl& srcUrl, const QString& suggestedName = QString(), 00115 QWidget* parent = 0, const KIO::MetaData& metaData = KIO::MetaData()) 00116 { 00117 const QString fileName = suggestedName.isEmpty() ? srcUrl.fileName() : suggestedName; 00118 // convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202) 00119 KUrl destUrl = KFileDialog::getSaveFileName(KUrl::fromPath(fileName), QString(), parent); 00120 if (!destUrl.isValid()) 00121 return false; 00122 00123 // Using KIO::copy rather than file_copy, to benefit from "dest already exists" dialogs. 00124 KIO::Job *job = KIO::copy(srcUrl, destUrl); 00125 00126 if (!metaData.isEmpty()) 00127 job->setMetaData(metaData); 00128 00129 job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache. 00130 job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available. 00131 job->ui()->setWindow((parent ? parent->window() : 0)); 00132 job->ui()->setAutoErrorHandlingEnabled(true); 00133 return true; 00134 } 00135 00136 static bool isReplyStatusOk(const QNetworkReply* reply) 00137 { 00138 if (!reply || reply->error() != QNetworkReply::NoError) 00139 return false; 00140 00141 // Check HTTP status code only for http and webdav protocols... 00142 const QString scheme = reply->url().scheme(); 00143 if (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive) || 00144 scheme.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive)) { 00145 bool ok = false; 00146 const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); 00147 if (!ok || statusCode < 200 || statusCode > 299) 00148 return false; 00149 } 00150 00151 return true; 00152 } 00153 00154 class KWebPage::KWebPagePrivate 00155 { 00156 public: 00157 KWebPagePrivate() : inPrivateBrowsingMode(false) {} 00158 void _k_copyResultToTempFile(KJob * job) 00159 { 00160 if ( job->error() ) { 00161 job->uiDelegate()->showErrorMessage(); 00162 return; 00163 } 00164 // Same as KRun::foundMimeType but with a different URL 00165 (void)KRun::runUrl(static_cast<KIO::FileCopyJob *>(job)->destUrl(), mimeType, window); 00166 } 00167 00168 QPointer<QWidget> window; 00169 QString mimeType; 00170 QPointer<KWebWallet> wallet; 00171 bool inPrivateBrowsingMode; 00172 }; 00173 00174 static void setActionIcon(QAction* action, const QIcon& icon) 00175 { 00176 if (action) { 00177 action->setIcon(icon); 00178 } 00179 } 00180 00181 static void setActionShortcut(QAction* action, const KShortcut& shortcut) 00182 { 00183 if (action) { 00184 action->setShortcuts(shortcut.toList()); 00185 } 00186 } 00187 00188 KWebPage::KWebPage(QObject *parent, Integration flags) 00189 :QWebPage(parent), d(new KWebPagePrivate) 00190 { 00191 // KDE KParts integration for <embed> tag... 00192 if (!flags || (flags & KPartsIntegration)) 00193 setPluginFactory(new KWebPluginFactory(this)); 00194 00195 QWidget *parentWidget = qobject_cast<QWidget*>(parent); 00196 QWidget *window = parentWidget ? parentWidget->window() : 0; 00197 00198 // KDE IO (KIO) integration... 00199 if (!flags || (flags & KIOIntegration)) { 00200 KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this); 00201 // Disable QtWebKit's internal cache to avoid duplication with the one in KIO... 00202 manager->setCache(0); 00203 manager->setWindow(window); 00204 manager->setEmitReadyReadOnMetaDataChange(true); 00205 setNetworkAccessManager(manager); 00206 } 00207 00208 // KWallet integration... 00209 if (!flags || (flags & KWalletIntegration)) { 00210 setWallet(new KWebWallet(0, (window ? window->winId() : 0) )); 00211 } 00212 00213 setActionIcon(action(Back), KIcon("go-previous")); 00214 setActionIcon(action(Forward), KIcon("go-next")); 00215 setActionIcon(action(Reload), KIcon("view-refresh")); 00216 setActionIcon(action(Stop), KIcon("process-stop")); 00217 setActionIcon(action(Cut), KIcon("edit-cut")); 00218 setActionIcon(action(Copy), KIcon("edit-copy")); 00219 setActionIcon(action(Paste), KIcon("edit-paste")); 00220 setActionIcon(action(Undo), KIcon("edit-undo")); 00221 setActionIcon(action(Redo), KIcon("edit-redo")); 00222 setActionIcon(action(InspectElement), KIcon("view-process-all")); 00223 setActionIcon(action(OpenLinkInNewWindow), KIcon("window-new")); 00224 setActionIcon(action(OpenFrameInNewWindow), KIcon("window-new")); 00225 setActionIcon(action(OpenImageInNewWindow), KIcon("window-new")); 00226 setActionIcon(action(CopyLinkToClipboard), KIcon("edit-copy")); 00227 setActionIcon(action(CopyImageToClipboard), KIcon("edit-copy")); 00228 setActionIcon(action(ToggleBold), KIcon("format-text-bold")); 00229 setActionIcon(action(ToggleItalic), KIcon("format-text-italic")); 00230 setActionIcon(action(ToggleUnderline), KIcon("format-text-underline")); 00231 setActionIcon(action(DownloadLinkToDisk), KIcon("document-save")); 00232 setActionIcon(action(DownloadImageToDisk), KIcon("document-save")); 00233 00234 settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32)); 00235 settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32)); 00236 settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32)); 00237 00238 setActionShortcut(action(Back), KStandardShortcut::back()); 00239 setActionShortcut(action(Forward), KStandardShortcut::forward()); 00240 setActionShortcut(action(Reload), KStandardShortcut::reload()); 00241 setActionShortcut(action(Stop), KShortcut(QKeySequence(Qt::Key_Escape))); 00242 setActionShortcut(action(Cut), KStandardShortcut::cut()); 00243 setActionShortcut(action(Copy), KStandardShortcut::copy()); 00244 setActionShortcut(action(Paste), KStandardShortcut::paste()); 00245 setActionShortcut(action(Undo), KStandardShortcut::undo()); 00246 setActionShortcut(action(Redo), KStandardShortcut::redo()); 00247 setActionShortcut(action(SelectAll), KStandardShortcut::selectAll()); 00248 } 00249 00250 KWebPage::~KWebPage() 00251 { 00252 delete d; 00253 } 00254 00255 bool KWebPage::isExternalContentAllowed() const 00256 { 00257 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00258 if (manager) 00259 return manager->isExternalContentAllowed(); 00260 return true; 00261 } 00262 00263 KWebWallet *KWebPage::wallet() const 00264 { 00265 return d->wallet; 00266 } 00267 00268 void KWebPage::setAllowExternalContent(bool allow) 00269 { 00270 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00271 if (manager) 00272 manager->setExternalContentAllowed(allow); 00273 } 00274 00275 void KWebPage::setWallet(KWebWallet* wallet) 00276 { 00277 // Delete the current wallet if this object is its parent... 00278 if (d->wallet && this == d->wallet->parent()) 00279 delete d->wallet; 00280 00281 d->wallet = wallet; 00282 00283 if (d->wallet) 00284 d->wallet->setParent(this); 00285 } 00286 00287 void KWebPage::downloadRequest(const QNetworkRequest &request) 00288 { 00289 downloadResource(request.url(), QString(), view(), 00290 request.attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap()); 00291 } 00292 00293 void KWebPage::downloadUrl(const KUrl &url) 00294 { 00295 downloadResource(url, QString(), view()); 00296 } 00297 00298 void KWebPage::downloadResponse(QNetworkReply *reply) 00299 { 00300 Q_ASSERT(reply); 00301 00302 if (!reply) 00303 return; 00304 00305 // Put the job on hold only for the protocols we know about (read: http). 00306 KIO::Integration::AccessManager::putReplyOnHold(reply); 00307 00308 QString mimeType; 00309 KIO::MetaData metaData; 00310 00311 if (handleReply(reply, &mimeType, &metaData)) { 00312 return; 00313 } 00314 00315 const KUrl replyUrl (reply->url()); 00316 QWidget* topLevelWindow = view() ? view()->window() : 0; 00317 00318 // Ask KRun to handle the response when mimetype is unknown 00319 if (mimeType.isEmpty()) { 00320 (void)new KRun(replyUrl, topLevelWindow, 0 , replyUrl.isLocalFile()); 00321 return; 00322 } 00323 00324 // Ask KRun::runUrl to handle the response when mimetype is inode/* 00325 if (mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive) && 00326 KRun::runUrl(replyUrl, mimeType, topLevelWindow, false, false, 00327 metaData.value(QL1S("content-disposition-filename")))) { 00328 return; 00329 } 00330 } 00331 00332 QString KWebPage::sessionMetaData(const QString &key) const 00333 { 00334 QString value; 00335 00336 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00337 if (manager) 00338 value = manager->sessionMetaData().value(key); 00339 00340 return value; 00341 } 00342 00343 QString KWebPage::requestMetaData(const QString &key) const 00344 { 00345 QString value; 00346 00347 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00348 if (manager) 00349 value = manager->requestMetaData().value(key); 00350 00351 return value; 00352 } 00353 00354 void KWebPage::setSessionMetaData(const QString &key, const QString &value) 00355 { 00356 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00357 if (manager) 00358 manager->sessionMetaData()[key] = value; 00359 } 00360 00361 void KWebPage::setRequestMetaData(const QString &key, const QString &value) 00362 { 00363 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00364 if (manager) 00365 manager->requestMetaData()[key] = value; 00366 } 00367 00368 void KWebPage::removeSessionMetaData(const QString &key) 00369 { 00370 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00371 if (manager) 00372 manager->sessionMetaData().remove(key); 00373 } 00374 00375 void KWebPage::removeRequestMetaData(const QString &key) 00376 { 00377 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00378 if (manager) 00379 manager->requestMetaData().remove(key); 00380 } 00381 00382 QString KWebPage::userAgentForUrl(const QUrl& _url) const 00383 { 00384 const KUrl url(_url); 00385 const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host())); 00386 00387 if (userAgent == KProtocolManager::defaultUserAgent()) 00388 return QWebPage::userAgentForUrl(_url); 00389 00390 return userAgent; 00391 } 00392 00393 static void setDisableCookieJarStorage(QNetworkAccessManager* manager, bool status) 00394 { 00395 if (manager) { 00396 KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0; 00397 if (cookieJar) { 00398 //kDebug(800) << "Store cookies ?" << !status; 00399 cookieJar->setDisableCookieStorage(status); 00400 } 00401 } 00402 } 00403 00404 bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 00405 { 00406 kDebug(800) << "url:" << request.url() << ", type:" << type << ", frame:" << frame; 00407 00408 if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted) 00409 d->wallet->saveFormData(frame); 00410 00411 // Make sure nothing is cached when private browsing mode is enabled... 00412 if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) { 00413 if (!d->inPrivateBrowsingMode) { 00414 setDisableCookieJarStorage(networkAccessManager(), true); 00415 setSessionMetaData(QL1S("no-cache"), QL1S("true")); 00416 d->inPrivateBrowsingMode = true; 00417 } 00418 } else { 00419 if (d->inPrivateBrowsingMode) { 00420 setDisableCookieJarStorage(networkAccessManager(), false); 00421 removeSessionMetaData(QL1S("no-cache")); 00422 d->inPrivateBrowsingMode = false; 00423 } 00424 } 00425 00426 /* 00427 If the navigation request is from the main frame, set the cross-domain 00428 meta-data value to the current url for proper integration with KCookieJar... 00429 */ 00430 if (frame == mainFrame() && type != QWebPage::NavigationTypeReload) 00431 setSessionMetaData(QL1S("cross-domain"), request.url().toString()); 00432 00433 return QWebPage::acceptNavigationRequest(frame, request, type); 00434 } 00435 00436 bool KWebPage::handleReply(QNetworkReply* reply, QString* contentType, KIO::MetaData* metaData) 00437 { 00438 // Reply url... 00439 const KUrl replyUrl (reply->url()); 00440 00441 // Get the top level window... 00442 QWidget* topLevelWindow = view() ? view()->window() : 0; 00443 00444 // Get suggested file name... 00445 const KIO::MetaData& data = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00446 const QString suggestedFileName = data.value(QL1S("content-disposition-filename")); 00447 if (metaData) { 00448 *metaData = data; 00449 } 00450 00451 // Get the mime-type... 00452 QString mimeType; 00453 extractMimeType(reply, mimeType); 00454 if (contentType) { 00455 *contentType = mimeType; 00456 } 00457 00458 // Let the calling function deal with handling empty or inode/* mimetypes... 00459 if (mimeType.isEmpty() || mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive)) { 00460 return false; 00461 } 00462 00463 // Convert executable text files to plain text... 00464 if (KParts::BrowserRun::isTextExecutable(mimeType)) 00465 mimeType = QL1S("text/plain"); 00466 00467 //kDebug(800) << "Content-disposition:" << suggestedFileName; 00468 //kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << replyUrl; 00469 //kDebug(800) << "Error code:" << reply->error() << reply->errorString(); 00470 00471 if (isReplyStatusOk(reply)) { 00472 KParts::BrowserOpenOrSaveQuestion::Result result; 00473 KParts::BrowserOpenOrSaveQuestion dlg(topLevelWindow, replyUrl, mimeType); 00474 dlg.setSuggestedFileName(suggestedFileName); 00475 dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); 00476 result = dlg.askOpenOrSave(); 00477 00478 switch (result) { 00479 case KParts::BrowserOpenOrSaveQuestion::Open: 00480 // Handle Post operations that return content... 00481 if (reply->operation() == QNetworkAccessManager::PostOperation) { 00482 d->mimeType = mimeType; 00483 d->window = topLevelWindow; 00484 QFileInfo finfo (suggestedFileName.isEmpty() ? replyUrl.fileName() : suggestedFileName); 00485 KTemporaryFile tempFile; 00486 tempFile.setSuffix(QL1C('.') + finfo.suffix()); 00487 tempFile.setAutoRemove(false); 00488 tempFile.open(); 00489 KUrl destUrl; 00490 destUrl.setPath(tempFile.fileName()); 00491 KIO::Job *job = KIO::file_copy(replyUrl, destUrl, 0600, KIO::Overwrite); 00492 job->ui()->setWindow(topLevelWindow); 00493 job->ui()->setAutoErrorHandlingEnabled(true); 00494 connect(job, SIGNAL(result(KJob*)), 00495 this, SLOT(_k_copyResultToTempFile(KJob*))); 00496 return true; 00497 } 00498 00499 // Ask before running any executables... 00500 if (KParts::BrowserRun::allowExecution(mimeType, replyUrl)) { 00501 KService::Ptr offer = dlg.selectedService(); 00502 // HACK: The check below is necessary to break an infinite 00503 // recursion that occurs whenever this function is called as a result 00504 // of receiving content that can be rendered by the app using this engine. 00505 // For example a text/html header that containing a content-disposition 00506 // header is received by the app using this class. 00507 if (isMimeTypeAssociatedWithSelf(offer)) { 00508 reloadRequestWithoutDisposition(reply); 00509 } else { 00510 KUrl::List list; 00511 list.append(replyUrl); 00512 bool success = false; 00513 // kDebug(800) << "Suggested file name:" << suggestedFileName; 00514 if (offer) { 00515 success = KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00516 } else { 00517 success = KRun::displayOpenWithDialog(list, topLevelWindow, false, suggestedFileName); 00518 } 00519 // For non KIO apps and cancelled Open With dialog, remove slave on hold. 00520 if (!success || (offer && !offer->categories().contains(QL1S("KDE")))) { 00521 KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold... 00522 } 00523 } 00524 return true; 00525 } 00526 // TODO: Instead of silently failing when allowExecution fails, notify 00527 // the user why the requested action cannot be fulfilled... 00528 break; 00529 case KParts::BrowserOpenOrSaveQuestion::Save: 00530 // Do not download local files... 00531 if (!replyUrl.isLocalFile()) { 00532 QString downloadCmd (reply->property("DownloadManagerExe").toString()); 00533 if (!downloadCmd.isEmpty()) { 00534 downloadCmd += QLatin1Char(' '); 00535 downloadCmd += KShell::quoteArg(replyUrl.url()); 00536 if (!suggestedFileName.isEmpty()) { 00537 downloadCmd += QLatin1Char(' '); 00538 downloadCmd += KShell::quoteArg(suggestedFileName); 00539 } 00540 // kDebug(800) << "download command:" << downloadCmd; 00541 if (KRun::runCommand(downloadCmd, view())) 00542 return true; 00543 } 00544 return downloadResource(replyUrl, suggestedFileName, topLevelWindow); 00545 } 00546 return true; 00547 case KParts::BrowserOpenOrSaveQuestion::Cancel: 00548 default: 00549 return true; 00550 } 00551 } else { 00552 KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType); 00553 if (isMimeTypeAssociatedWithSelf(offer)) { 00554 reloadRequestWithoutDisposition(reply); 00555 return true; 00556 } 00557 } 00558 00559 return false; 00560 } 00561 00562 #include "kwebpage.moc" 00563
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 18:47:57 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 18:47:57 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.