/* KNode, the KDE newsreader Copyright (c) 2005-2006 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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, US */ #include "articlewidget.h" #include "utils/locale.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csshelper.h" #include "knarticle.h" #include "knarticlecollection.h" #include "knarticlefactory.h" #include "knarticlemanager.h" #include "knconfig.h" #include "knconfigmanager.h" #include "kndisplayedheader.h" #include "knfolder.h" #include "knfoldermanager.h" #include "knglobals.h" #include "kngroup.h" #include "knmainwidget.h" #include "knnntpaccount.h" #include "knsourceviewwindow.h" #include "nntpjobs.h" #include "settings.h" using namespace KNode; QList ArticleWidget::mInstances; ArticleWidget::ArticleWidget( QWidget *parent, KXMLGUIClient *guiClient, KActionCollection *actionCollection, bool isMainViewer ) : QWidget( parent ), mViewer( 0 ), mCSSHelper( 0 ), mHeaderStyle( "fancy" ), mAttachmentStyle( "inline" ), mShowHtml( false ), mRot13( false ), mForceCharset( false ), mOverrideCharset( KMime::Headers::Latin1 ), mTimer( 0 ), mIsMainViewer( isMainViewer ), mGuiClient( guiClient ), mActionCollection( actionCollection ) { mInstances.append( this ); QHBoxLayout *box = new QHBoxLayout( this ); box->setMargin( 0 ); box->setSpacing( 0 ); mViewer = new KHTMLPart( this ); box->addWidget( mViewer->widget() ); mViewer->widget()->setFocusPolicy( Qt::WheelFocus ); mViewer->setPluginsEnabled( false ); mViewer->setJScriptEnabled( false ); mViewer->setJavaEnabled( false ); mViewer->setMetaRefreshEnabled( false ); mViewer->setOnlyLocalReferences( true ); mViewer->view()->setFocusPolicy( Qt::WheelFocus ); connect( mViewer->browserExtension(), SIGNAL(openUrlRequestDelayed(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)), SLOT(slotURLClicked(KUrl)) ); connect( mViewer, SIGNAL(popupMenu(QString,QPoint)), SLOT(slotURLPopup(QString,QPoint)) ); mTimer = new QTimer( this ); mTimer->setSingleShot( true ); connect( mTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); initActions(); readConfig(); clear(); } ArticleWidget::~ArticleWidget() { mInstances.removeAll( this ); delete mTimer; delete mCSSHelper; if ( mArticle && mArticle->isOrphant() ) { // if the article manager is still loading the current article, // cancel the job. knGlobals.articleManager()->cancelJobs( mArticle ); } removeTempFiles(); } void ArticleWidget::initActions() { mSaveAction = KStandardAction::save( this, SLOT(slotSave()), mActionCollection ); mSaveAction->setText( KStandardGuiItem::saveAs().text() ); mPrintAction = KStandardAction::print( this, SLOT(slotPrint()), mActionCollection ); mCopySelectionAction = KStandardAction::copy( this, SLOT(slotCopySelection()), mActionCollection ); mSelectAllAction = KStandardAction::selectAll( this, SLOT(slotSelectAll()), mActionCollection ); mFindAction = KStandardAction::find( this, SLOT(slotFind()), mActionCollection); mActionCollection->addAction("find_in_article", mFindAction); mFindAction->setText( i18n("F&ind in Article...") ); mViewSourceAction = mActionCollection->addAction("article_viewSource"); mViewSourceAction->setText(i18n("&View Source")); connect(mViewSourceAction, SIGNAL(triggered(bool)), SLOT(slotViewSource())); mViewSourceAction->setShortcut(QKeySequence(Qt::Key_V)); mReplyAction = mActionCollection->addAction("article_postReply"); mReplyAction->setIcon(KIcon("mail-reply-all")); mReplyAction->setText(i18n("&Followup to Newsgroup...")); connect(mReplyAction, SIGNAL(triggered(bool)), SLOT(slotReply())); mReplyAction->setShortcut(QKeySequence(Qt::Key_R)); mRemailAction = mActionCollection->addAction("article_mailReply" ); mRemailAction->setIcon(KIcon("mail-reply-sender")); mRemailAction->setText(i18n("Reply by E&mail...")); connect(mRemailAction, SIGNAL(triggered(bool)), SLOT(slotRemail())); mRemailAction->setShortcut(QKeySequence(Qt::Key_A)); mForwardAction = mActionCollection->addAction("article_forward"); mForwardAction->setIcon(KIcon("mail-forward")); mForwardAction->setText(i18n("Forw&ard by Email...")); connect(mForwardAction, SIGNAL(triggered(bool)), SLOT(slotForward())); mForwardAction->setShortcut(QKeySequence(Qt::Key_F)); mCancelAction = mActionCollection->addAction("article_cancel"); mCancelAction->setText(i18nc("article", "&Cancel Article")); connect(mCancelAction, SIGNAL(triggered(bool)), SLOT(slotCancel())); mSupersedeAction = mActionCollection->addAction("article_supersede"); mSupersedeAction->setText(i18n("S&upersede Article")); connect(mSupersedeAction, SIGNAL(triggered(bool)), SLOT(slotSupersede())); mFixedFontToggle = mActionCollection->add("view_useFixedFont"); mFixedFontToggle->setText(i18n("U&se Fixed Font")); connect(mFixedFontToggle, SIGNAL(triggered(bool)), SLOT(slotToggleFixedFont())); mFixedFontToggle->setShortcut(QKeySequence(Qt::Key_X)); mFancyToggle = mActionCollection->add("view_fancyFormating"); mFancyToggle->setText(i18n("Fancy Formatting")); connect(mFancyToggle, SIGNAL(triggered(bool)), SLOT(slotToggleFancyFormating())); mFancyToggle->setShortcut(QKeySequence(Qt::Key_Y)); mRot13Toggle = mActionCollection->add("view_rot13"); mRot13Toggle->setIcon(KIcon("document-decrypt")); mRot13Toggle->setText(i18n("&Unscramble (Rot 13)")); connect(mRot13Toggle, SIGNAL(triggered(bool)), SLOT(slotToggleRot13())); mRot13Toggle->setChecked( false ); QActionGroup *ag = new QActionGroup( this ); KToggleAction *ra; mHeaderStyleMenu = mActionCollection->add("view_headers"); mHeaderStyleMenu->setText(i18n("&Headers")); ra = mActionCollection->add("view_headers_fancy"); ra->setText(i18n("&Fancy Headers")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotFancyHeaders())); ag->addAction ( ra ); mHeaderStyleMenu->addAction( ra ); ra = mActionCollection->add("view_headers_standard"); ra->setText(i18n("&Standard Headers")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotStandardHeaders())); ag->addAction( ra ); mHeaderStyleMenu->addAction( ra ); ra = mActionCollection->add("view_headers_all"); ra->setText(i18n("&All Headers")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotAllHeaders())); ag->addAction( ra ); mHeaderStyleMenu->addAction( ra ); ag = new QActionGroup( this ); mAttachmentStyleMenu = mActionCollection->add("view_attachments"); mAttachmentStyleMenu->setText(i18n("&Attachments")); ra = mActionCollection->add("view_attachments_icon"); ra->setText(i18n("&As Icon")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotIconAttachments())); ag->addAction( ra ); mAttachmentStyleMenu->addAction( ra ); ra = mActionCollection->add("view_attachments_inline"); ra->setText(i18n("&Inline")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotInlineAttachments())); ag->addAction( ra ); mAttachmentStyleMenu->addAction( ra ); ra = mActionCollection->add("view_attachments_hide"); ra->setText(i18n("&Hide")); connect(ra, SIGNAL(triggered(bool)), SLOT(slotHideAttachments())); ag->addAction( ra ); mAttachmentStyleMenu->addAction( ra ); mCharsetSelect = mActionCollection->add("set_charset"); mCharsetSelect->setText( i18n( "Set chars&et" ) ); mCharsetSelect->setShortcutConfigurable( false ); QStringList cs = Utilities::Locale::encodings(); cs.prepend( i18nc( "@item default character set", "Default") ); mCharsetSelect->setItems( cs ); mCharsetSelect->setCurrentItem( 0 ); connect( mCharsetSelect, SIGNAL(triggered(QString)),SLOT(slotSetCharset(QString)) ); mCharsetSelectKeyb = mActionCollection->addAction("set_charset_keyboard"); mCharsetSelectKeyb->setText( i18n( "Set charset" ) ); connect(mCharsetSelectKeyb, SIGNAL(triggered(bool)), SLOT(slotSetCharsetKeyboard())); mCharsetSelectKeyb->setShortcut(QKeySequence(Qt::Key_C)); QAction *action = mActionCollection->addAction("open_url"); action->setIcon(KIcon("document-open")); action->setText(i18n("&Open URL")); connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenURL())); action = mActionCollection->addAction("copy_url"); action->setIcon(KIcon("edit-copy")); action->setText(i18n("&Copy Link Address")); connect(action, SIGNAL(triggered(bool)), SLOT(slotCopyURL())); action = mActionCollection->addAction("add_bookmark"); action->setIcon(KIcon("bookmark-new")); action->setText(i18n("&Bookmark This Link")); connect(action, SIGNAL(triggered(bool)), SLOT(slotAddBookmark())); action = mActionCollection->addAction("add_addr_book"); action->setText(i18n("&Add to Address Book")); connect(action, SIGNAL(triggered(bool)), SLOT(slotAddToAddressBook())); action = mActionCollection->addAction("openin_addr_book"); action->setText(i18n("&Open in Address Book")); connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenInAddressBook())); action = mActionCollection->addAction("open_attachment"); action->setIcon(KIcon("document-open")); action->setText(i18n("&Open Attachment")); connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenAttachment())); action = mActionCollection->addAction("save_attachment"); action->setIcon(KIcon("document-save-as")); action->setText(i18n("&Save Attachment As...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotSaveAttachment())); } void ArticleWidget::enableActions() { if ( !mArticle ) { disableActions(); return; } mSaveAction->setEnabled( true ); mPrintAction->setEnabled( true ); mCopySelectionAction->setEnabled( true ); mSelectAllAction->setEnabled( true ); mFindAction->setEnabled( true ); mForwardAction->setEnabled( true ); mHeaderStyleMenu->setEnabled( true ); mAttachmentStyleMenu->setEnabled( true ); mRot13Toggle->setEnabled( true ); mViewSourceAction->setEnabled( true ); mCharsetSelect->setEnabled( true ); mCharsetSelectKeyb->setEnabled( true ); mFixedFontToggle->setEnabled( true ); mFancyToggle->setEnabled( true ); // only valid for remote articles bool enabled = ( mArticle->type() == KNArticle::ATremote ); mReplyAction->setEnabled( enabled ); mRemailAction->setEnabled( enabled ); enabled = ( mArticle->type() == KNArticle::ATremote || mArticle->collection() == knGlobals.folderManager()->sent() ); mCancelAction->setEnabled( enabled ); mSupersedeAction->setEnabled( enabled ); } void ArticleWidget::disableActions() { mSaveAction->setEnabled( false ); mPrintAction->setEnabled( false ); mCopySelectionAction->setEnabled( false ); mSelectAllAction->setEnabled( false ); mFindAction->setEnabled( false ); mReplyAction->setEnabled( false ); mRemailAction->setEnabled( false ); mForwardAction->setEnabled( false ); mCancelAction->setEnabled( false ); mSupersedeAction->setEnabled( false ); mHeaderStyleMenu->setEnabled( false ); mAttachmentStyleMenu->setEnabled( false ); mRot13Toggle->setEnabled( false ); mViewSourceAction->setEnabled( false ); mCharsetSelect->setEnabled( false ); mCharsetSelectKeyb->setEnabled( false ); mFixedFontToggle->setEnabled( false ); mFancyToggle->setEnabled( false ); } void ArticleWidget::readConfig() { mFixedFontToggle->setChecked( knGlobals.settings()->useFixedFont() ); mFancyToggle->setChecked( knGlobals.settings()->interpretFormatTags() ); mShowHtml = knGlobals.settings()->alwaysShowHTML(); mViewer->setOnlyLocalReferences( !knGlobals.settings()->allowExternalReferences() ); KConfigGroup conf(knGlobals.config(), "READNEWS" ); mAttachmentStyle = conf.readEntry( "attachmentStyle", "inline" ); mHeaderStyle = conf.readEntry( "headerStyle", "fancy" ); KToggleAction *ra = 0; ra = static_cast( mActionCollection->action( QString("view_attachments_%1").arg(mAttachmentStyle) ) ); ra->setChecked( true ); ra = static_cast( mActionCollection->action( QString("view_headers_%1").arg(mHeaderStyle) ) ); ra->setChecked( true ); delete mCSSHelper; mCSSHelper = new CSSHelper( mViewer->view() ); if ( !knGlobals.settings()->autoMark() ) mTimer->stop(); } void ArticleWidget::writeConfig() { // main viewer determines the settings if ( !mIsMainViewer ) { return; } KConfigGroup conf(knGlobals.config(), "READNEWS" ); conf.writeEntry( "attachmentStyle", mAttachmentStyle ); conf.writeEntry( "headerStyle", mHeaderStyle ); knGlobals.settings()->setUseFixedFont( mFixedFontToggle->isChecked() ); knGlobals.settings()->setInterpretFormatTags( mFancyToggle->isChecked() ); } void ArticleWidget::setArticle( KNArticle::Ptr article ) { mShowHtml = knGlobals.settings()->alwaysShowHTML(); mRot13 = false; mRot13Toggle->setChecked( false ); mTimer->stop(); mArticle = article; if ( !mArticle ) clear(); else { if ( mArticle->hasContent() ) { // article is already loaded => just show it displayArticle(); } else { if( !knGlobals.articleManager()->loadArticle( mArticle ) ) articleLoadError( mArticle, i18n("Unable to load the article.") ); else // try again for local articles if( mArticle->hasContent() && !( mArticle->type() == KNArticle::ATremote ) ) displayArticle(); } } } void ArticleWidget::clear() { disableActions(); mViewer->begin(); mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); mViewer->write( QString("") ); mViewer->end(); } void ArticleWidget::displayArticle() { if ( !mArticle) { clear(); return; } // scroll back to top mViewer->view()->ensureVisible( 0, 0 ); if ( !mArticle->hasContent() ) { displayErrorMessage( i18n("The article contains no data.") ); return; } if ( mForceCharset != mArticle->forceDefaultCharset() || ( mForceCharset && mArticle->defaultCharset() != mOverrideCharset ) ) { mArticle->setDefaultCharset( mOverrideCharset ); mArticle->setForceDefaultCharset( mForceCharset ); } removeTempFiles(); mViewer->begin(); mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); // headers displayHeader(); // body QString html; KMime::Content *text = 0; if ( mShowHtml ) { foreach ( KMime::Content *c, mArticle->contents() ) { if ( c->contentType()->isHTMLText() && c->contentType()->category() == KMime::Headers::CCalternativePart ) { text = c; break; } } } if ( !text ) text = mArticle->textContent(); // check if codec is available if ( text && !canDecodeText( text->contentType()->charset() ) ) { html += QString("
%1
") .arg( i18n("Unknown charset. Default charset is used instead.") ); kDebug(5003) <<"unknown charset =" << text->contentType()->charset(); } // if the article is pgp signed and the user asked for verifying the // signature, we show a nice header: QList pgpBlocks; QList nonPgpBlocks; bool containsPGP = Kpgp::Module::prepareMessageForDecryption( mArticle->body(), pgpBlocks, nonPgpBlocks ); mViewer->write ( html ); html.clear(); if ( containsPGP ) { QList::Iterator pbit = pgpBlocks.begin(); QList::Iterator npbit = nonPgpBlocks.begin(); QTextCodec *codec; if ( text ) codec = KGlobal::charsets()->codecForName( text->contentType()->charset() ); else codec = KGlobal::locale()->codecForEncoding(); for( ; pbit != pgpBlocks.end(); ++pbit, ++npbit ) { // handle non-pgp block QByteArray str = *npbit; if( !str.isEmpty() ) { QStringList lines = codec->toUnicode( str ).split( '\n' ); displayBodyBlock( lines ); } // handle pgp block Kpgp::Block block = *pbit; if ( block.type() == Kpgp::ClearsignedBlock ) block.verify(); QStringList lines = codec->toUnicode( block.text() ).split( '\n' ); if ( block.isSigned() ) { QString signClass = displaySigHeader( block ); displayBodyBlock( lines ); displaySigFooter( signClass ); } else { displayBodyBlock( lines ); } } // deal with the last non-pgp block QByteArray str = *npbit; if( !str.isEmpty() ) { QStringList lines = codec->toUnicode( str ).split( 'n' ); displayBodyBlock( lines ); } } KMime::Headers::ContentType *ct = mArticle->contentType(); // get attachments mAttachments.clear(); mAttachementMap.clear(); if( !text || ct->isMultipart() ) mAttachments = mArticle->attachments( knGlobals.settings()->showAlternativeContents() ); // partial message if(ct->isPartial()) { mViewer->write( i18n("
This article has the MIME type "message/partial", which KNode cannot handle yet.
Meanwhile you can save the article as a text file and reassemble it by hand.
") ); } // display body text if ( text && text->hasContent() && !ct->isPartial() ) { // handle HTML messages if ( text->contentType()->isHTMLText() ) { QString htmlTxt = text->decodedText( true, knGlobals.settings()->removeTrailingNewlines() ); if ( mShowHtml ) { // strip & int i = qMin( htmlTxt.lastIndexOf( "", -1, Qt::CaseInsensitive ), htmlTxt.lastIndexOf( "", -1, Qt::CaseInsensitive ) ); if ( i >= 0 ) htmlTxt.truncate( i ); html += htmlTxt; } else { html += "
\n"; html += i18n("Note: This is an HTML message. For " "security reasons, only the raw HTML code " "is shown. If you trust the sender of this " "message then you can activate formatted " "HTML display for this message " "by clicking here."); html += "


"; html += toHtmlString( htmlTxt ); } } else { if ( !containsPGP ) { QStringList lines = text->decodedText( true, knGlobals.settings()->removeTrailingNewlines() ).split( '\n' ); displayBodyBlock( lines ); } } } mViewer->write( html ); // display attachments if( !mAttachments.isEmpty() && !ct->isPartial() ) { int attCnt = 0; foreach ( KMime::Content *var, mAttachments ) { displayAttachment( var, attCnt ); attCnt++; } } mViewer->write( QString("") ); mViewer->end(); enableActions(); if( mArticle->type() == KNArticle::ATremote && knGlobals.settings()->autoMark() ) mTimer->start( knGlobals.settings()->autoMarkSeconds() * 1000 ); } void ArticleWidget::displayErrorMessage( const QString &msg ) { mViewer->begin(); mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); QString errMsg = msg; mViewer->write( QString("") ); mViewer->write( i18n("An error occurred.") ); mViewer->write( QString("

") ); mViewer->write( errMsg.replace( '\n', "
" ) ); mViewer->write( QString("") ); mViewer->end(); disableActions(); } void ArticleWidget::displayHeader() { QString headerHtml; // full header style if ( mHeaderStyle == "all" ) { QByteArray head = mArticle->head(); KMime::Headers::Base *header = 0; headerHtml += "
" " "; while ( !head.isEmpty() ) { header = KMime::HeaderParsing::extractFirstHeader( head ); if ( header ) { headerHtml+=QString( "" ) .arg( toHtmlString( header->type(), None ) + ": " ) .arg( toHtmlString( header->asUnicodeString().remove( QRegExp( "^[^:]+:\\s*" ) ) , ParseURL ) ); delete header; } } headerHtml += "
%1%2
"; mViewer->write( headerHtml ); return; } // standard & fancy header style KMime::Headers::Base *hb; KNDisplayedHeader::List dhs = knGlobals.configManager()->displayedHeaders()->headers(); for ( KNDisplayedHeader::List::Iterator it = dhs.begin(); it != dhs.end(); ++it ) { KNDisplayedHeader *dh = (*it); hb = mArticle->headerByType(dh->header().toLatin1()); if ( !hb || hb->is("Subject") || hb->is("Organization") ) continue; if ( dh->hasName() ) { headerHtml += ""; if ( mHeaderStyle == "fancy" ) headerHtml += ""; else headerHtml += ""; headerHtml += toHtmlString( dh->translatedName(), None ); headerHtml += ":"; } else headerHtml+=""; if ( hb->is("From") ) { headerHtml += QString( "%2") .arg( KPIMUtils::extractEmailAddress( hb->asUnicodeString() ) ) .arg( toHtmlString( hb->asUnicodeString(), None ) ); KMime::Headers::Base *orgHdr = mArticle->headerByType( "Organization" ); if ( orgHdr && !orgHdr->isEmpty() ) { headerHtml += "  ("; headerHtml += toHtmlString( orgHdr->asUnicodeString() ); headerHtml += ')'; } } else if ( hb->is("Date") ) { KMime::Headers::Date *date=static_cast(hb); headerHtml += toHtmlString( KGlobal::locale()->formatDateTime(date->dateTime().toLocalZone().dateTime(), KLocale::LongDate, true), None ); } else if ( hb->is("Newsgroups") ) { QString groups = hb->asUnicodeString(); groups.replace( ',', ", " ); headerHtml += toHtmlString( groups, ParseURL ); } else headerHtml += toHtmlString( hb->asUnicodeString(), ParseURL ); headerHtml += ""; } // standard header style if ( mHeaderStyle == "standard" ) { mViewer->write( QString("" + toHtmlString( mArticle->subject()->asUnicodeString() ) + "") ); mViewer->write( QString("
") ); mViewer->write( QString("") + headerHtml ); mViewer->write( QString("
") ); return; } // X-Face support QString xfhead; KMime::Headers::Base *temp = mArticle->headerByType("X-Face"); if (temp) xfhead = temp->asUnicodeString(); QString xface = ""; if ( !xfhead.isEmpty() ) { MessageViewer::KXFace xf; xface = QString::fromLatin1( "
" ) .arg( imgToDataUrl( xf.toImage( xfhead ), "PNG" ) ); } // fancy header style QString html = "
"; html += "
"; html += toHtmlString( mArticle->subject()->asUnicodeString(), ParseURL | FancyFormatting ); html += "
"; html += ""; html += ""; html += "
"; html += headerHtml; html += "
" + xface + "
"; // references KMime::Headers::References *refs = mArticle->references( false ); if ( mArticle->type() == KNArticle::ATremote && refs && knGlobals.settings()->showRefBar() ) { html += "
"; html += QString( "%1" ).arg( i18n("References:") ); QList references = refs->identifiers(); for ( int i = 0; i < references.count(); ++i ) { html += " " + QString::number( i + 1 ) + ""; } html += "
"; } html += "
"; mViewer->write( html ); } void ArticleWidget::displayBodyBlock( const QStringList &lines ) { int oldLevel = -2, newLevel = -2; bool isSig = false; QString line, html; QString quoteChars = knGlobals.settings()->quoteCharacters().simplified(); if (quoteChars.isEmpty()) quoteChars = '>'; for ( QStringList::const_iterator it = lines.constBegin(); it != lines.constEnd(); ++it) { line = (*it); if ( !line.isEmpty() ) { // signature found if ( !isSig && line == "-- " ) { isSig = true; // close previous body tag (if any) and open new one if ( newLevel != -2 ) html += ""; html += mCSSHelper->nonQuotedFontTag(); newLevel = -1; if ( knGlobals.settings()->showSignature() ) { html += "
"; continue; } else break; } // look for quoting characters if ( !isSig ) { oldLevel = newLevel; newLevel = quotingDepth( line, quoteChars ); if ( newLevel >= 3 ) newLevel = 2; // no more than three levels supported (0-2) // quoting level changed if ( newLevel != oldLevel ) { if ( oldLevel != -2 ) html += ""; // close previous level // open new level if ( newLevel == -1 ) html += mCSSHelper->nonQuotedFontTag(); else html += mCSSHelper->quoteFontTag( newLevel ); } // output the actual line html += toHtmlString( line, ParseURL | FancyFormatting | AllowROT13 ) + "
"; } else { // signature html += toHtmlString( line, ParseURL | AllowROT13 ) + "
"; } } else { // empty line html += "
"; } } // close body quoting level tags if ( newLevel != -2 ) html += ""; mViewer->write( html ); } QString ArticleWidget::displaySigHeader( const Kpgp::Block &block ) { QString signClass = "signErr"; QString signer = block.signatureUserId(); QString signerKey = block.signatureKeyId(); QString message; if ( signer.isEmpty() ) { message = i18n( "Message was signed with unknown key 0x%1." , signerKey ); message += "
"; message += i18n( "The validity of the signature cannot be verified." ); signClass = "signWarn"; } else { // determine the validity of the key Kpgp::Module *pgp = Kpgp::Module::getKpgp(); Kpgp::Validity keyTrust; if( !signerKey.isEmpty() ) keyTrust = pgp->keyTrust( signerKey ); else // This is needed for the PGP 6 support because PGP 6 doesn't // print the key id of the signing key if the key is known. keyTrust = pgp->keyTrust( signer ); // HTMLize the signer's user id and create mailto: link signer = toHtmlString( signer, None ); signer = "" + signer + ""; if( !signerKey.isEmpty() ) message += i18n( "Message was signed by %1 (Key ID: 0x%2)." , signer , signerKey ); else message += i18n( "Message was signed by %1.", signer ); message += "
"; if( block.goodSignature() ) { if ( keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL ) signClass = "signOkKeyBad"; else signClass = "signOkKeyOk"; switch( keyTrust ) { case Kpgp::KPGP_VALIDITY_UNKNOWN: message += i18n( "The signature is valid, but the key's " "validity is unknown." ); break; case Kpgp::KPGP_VALIDITY_MARGINAL: message += i18n( "The signature is valid and the key is " "marginally trusted." ); break; case Kpgp::KPGP_VALIDITY_FULL: message += i18n( "The signature is valid and the key is " "fully trusted." ); break; case Kpgp::KPGP_VALIDITY_ULTIMATE: message += i18n( "The signature is valid and the key is " "ultimately trusted." ); break; default: message += i18n( "The signature is valid, but the key is " "untrusted." ); } } else { message += i18n("Warning: The signature is bad."); signClass = "signErr"; } } QString html = ""; html += ""; html += "
"; html += message; html += "
"; mViewer->write( html ); return signClass; } void ArticleWidget::displaySigFooter( const QString &signClass ) { QString html = "
" + i18n( "End of signed message" ) + "
"; mViewer->write( html ); } void ArticleWidget::displayAttachment( KMime::Content *att, int partNum ) { if ( mAttachmentStyle == "hide" ) return; QString html; KMime::Headers::ContentType *ct = att->contentType(); // attachment label QString label = ct->name(); if ( label.isEmpty() ) label = i18n("unnamed" ); // if label consists of only whitespace replace them by underscores if ( label.count( ' ' ) == label.length() ) label.replace( QRegExp( " ", Qt::CaseSensitive, QRegExp::Wildcard ), "_" ); label = toHtmlString( label, None ); // attachment comment QString comment = att->contentDescription()->asUnicodeString(); comment = toHtmlString( comment, ParseURL | FancyFormatting ); QString href; KUrl file = writeAttachmentToTempFile( att, partNum ); if ( file.isEmpty() ) { href = "part://" + QString::number( partNum ); } else { href = file.url(); mAttachementMap[ file.path() ] = partNum; } if ( mAttachmentStyle == "inline" && inlinePossible( att ) ) { if ( ct->isImage() ) { html += "
" "" "
" + comment + "

"; } else { //text // frame html += "" "
" "" + label + ""; if ( !comment.isEmpty() ) html += "
" + comment; html += "
"; // content QString tmp = att->decodedText(); /*if( ct->isHTMLText() ) // ### to dangerous, we should use the same stuff as for the main text here html += tmp; else*/ html += toHtmlString( tmp, ParseURL ); // finish frame html += "
"; } } else { // icon QByteArray mimetype = ct->mimeType(); kAsciiToLower( mimetype.data() ); KMimeType::Ptr mimetypePtr = KMimeType::mimeType( mimetype ); if(mimetypePtr.isNull()) { mimetypePtr = KMimeType::mimeType( "text/plain" ); } QString iconName = mimetypePtr->iconName( QString() ); QString iconFile = KIconLoader::global()->iconPath( iconName, KIconLoader::Desktop ); html += "
" + comment + "

"; } mViewer->write( html ); } QString ArticleWidget::toHtmlString( const QString &line, int flags ) { int llflags = KPIMUtils::LinkLocator::PreserveSpaces; if ( !(flags & ArticleWidget::ParseURL) ) llflags |= KPIMUtils::LinkLocator::IgnoreUrls; if ( mFancyToggle->isChecked() && (flags & ArticleWidget::FancyFormatting) ) llflags |= KPIMUtils::LinkLocator::ReplaceSmileys | KPIMUtils::LinkLocator::HighlightText; QString text = line; if ( flags & ArticleWidget::AllowROT13 ) { if ( mRot13 ) text = MessageComposer::Util::rot13( line ); } return KPIMUtils::LinkLocator::convertToHtml( text, llflags ); } // from KMail headerstyle.cpp QString ArticleWidget::imgToDataUrl( const QImage &image, const char* fmt ) { QByteArray ba; QBuffer buffer( &ba ); buffer.open( QIODevice::WriteOnly ); image.save( &buffer, fmt ); return QString::fromLatin1("data:image/%1;base64,%2") .arg( fmt ).arg( QString( KCodecs::base64Encode( ba ) ) ); } int ArticleWidget::quotingDepth( const QString &line, const QString "eChars ) { int level = -1; for ( int i = 0; i < line.length(); ++i ) { // skip spaces if ( line[i].isSpace() ) continue; if ( quoteChars.indexOf( line[i] ) != -1 ) ++level; else break; } return level; } bool ArticleWidget::inlinePossible( KMime::Content *c ) { KMime::Headers::ContentType *ct = c->contentType(); return ( ct->isText() || ct->isImage() ); } bool ArticleWidget::canDecodeText( const QByteArray &charset ) const { kDebug( 5003 ) << charset; if ( charset.isEmpty() ) return false; bool ok = true; KGlobal::charsets()->codecForName( charset, ok ); return ok; } void ArticleWidget::updateContents() { // save current scrollbar position float savedPosition = (float)mViewer->view()->contentsY() / (float)mViewer->view()->contentsHeight(); if ( mArticle && mArticle->hasContent() ) displayArticle(); else clear(); // restore scrollbar position mViewer->view()->setContentsPos( 0, qRound( mViewer->view()->contentsHeight() * savedPosition ) ); } KUrl ArticleWidget::writeAttachmentToTempFile( KMime::Content *att, int partNum ) { // more or less KMail code KTemporaryFile *tempFile = new KTemporaryFile(); tempFile->setSuffix( '.' + QString::number( partNum ) ); tempFile->open(); KUrl file = KUrl( tempFile->fileName() ); delete tempFile; if( ::access( QFile::encodeName( file.path() ), W_OK ) != 0 ) // Not there or not writable if( KDE_mkdir( QFile::encodeName( file.path() ), 0 ) != 0 || ::chmod( QFile::encodeName( file.path() ), S_IRWXU ) != 0 ) return KUrl(); //failed create Q_ASSERT( !file.fileName().isNull() ); mTempDirs.append( file.path() ); // strip off a leading path KMime::Headers::ContentType* ct = att->contentType(); QString attName = ct->name(); int slashPos = attName.lastIndexOf( '/' ); if( -1 != slashPos ) attName = attName.mid( slashPos + 1 ); if( attName.isEmpty() ) attName = "unnamed"; file.addPath( attName ); QByteArray data = att->decodedContent(); // ### KMail does crlf2lf conversion here before writing the file if( !KPIMUtils::kByteArrayToFile( data, file.toLocalFile(), false, false, false ) ) return KUrl(); mTempFiles.append( file.toLocalFile() ); // make file read-only so that nobody gets the impression that he might // edit attached files ::chmod( QFile::encodeName( file.toLocalFile() ), S_IRUSR ); return file; } void ArticleWidget::removeTempFiles( ) { for ( QStringList::Iterator it = mTempFiles.begin(); it != mTempFiles.end(); ++it ) QFile::remove(*it); mTempFiles.clear(); for ( QStringList::ConstIterator it = mTempDirs.constBegin(); it != mTempDirs.constEnd(); ++it ) QDir(*it).rmdir(*it); mTempDirs.clear(); } void ArticleWidget::processJob( KNJobData * job ) { if ( job->type() == KNJobData::JTfetchSource || job->type() == KNJobData::JTfetchArticle ) { if ( !job->canceled() ) { if ( !job->success() ) KMessageBox::error( this, i18n("An error occurred while downloading the article source:\n%1", job->errorString() ) ); else { KNRemoteArticle::Ptr a = boost::static_pointer_cast( job->data() ); new KNSourceViewWindow( a->head() + QLatin1Char('\n') + a->body() ); } } } delete job; } typedef QList::ConstIterator InstanceIterator; void ArticleWidget::configChanged() { for( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) { (*it)->readConfig(); (*it)->updateContents(); } } bool ArticleWidget::articleVisible( KNArticle::Ptr article ) { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) if ( (*it)->article() == article ) return true; return false; } void ArticleWidget::articleRemoved( KNArticle::Ptr article ) { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) if ( (*it)->article() == article ) (*it)->setArticle( KNArticle::Ptr() ); } void ArticleWidget::articleChanged( KNArticle::Ptr article ) { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) if ( (*it)->article() == article ) (*it)->displayArticle(); } void ArticleWidget::articleLoadError( KNArticle::Ptr article, const QString &error ) { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) if ( (*it)->article() == article ) (*it)->displayErrorMessage( error ); } void ArticleWidget::collectionRemoved( KNArticleCollection::Ptr coll ) { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) if ( (*it)->article() && (*it)->article()->collection() == coll ) (*it)->setArticle( KNArticle::Ptr() ); } void ArticleWidget::cleanup() { for ( InstanceIterator it = mInstances.constBegin(); it != mInstances.constEnd(); ++it ) (*it)->setArticle( KNArticle::Ptr() ); //delete orphant articles => avoid crash in destructor } bool ArticleWidget::atBottom() const { const KHTMLView *view = mViewer->view(); return view->contentsY() + view->visibleHeight() >= view->contentsHeight(); } void ArticleWidget::scrollUp() { mViewer->view()->scrollBy( 0, -10 ); } void ArticleWidget::scrollDown() { mViewer->view()->scrollBy( 0, 10 ); } void ArticleWidget::scrollPrior() { mViewer->view()->scrollBy( 0, -(int)(mViewer->view()->height() * 0.8) ); } void ArticleWidget::scrollNext() { mViewer->view()->scrollBy( 0, (int)(mViewer->view()->height() * 0.8) ); } void ArticleWidget::slotURLClicked( const KUrl &url, bool forceOpen) { // internal URLs if ( url.protocol() == "knode" ) { if ( url.path() == "showHTML" ) { mShowHtml = true; updateContents(); } return; } // handle mailto if ( url.protocol() == "mailto" ) { KMime::Types::Mailbox addr; addr.fromUnicodeString( url.path() ); KNGlobals::self()->articleFactory()->createMail( &addr ); return; } // handle news URL's if ( url.protocol() == "news" ) { kDebug( 5003 ) << url; knGlobals.top->openURL( url ); return; } // handle attachments int partNum = 0; if ( url.protocol() == "file" || url.protocol() == "part" ) { if ( url.protocol() == "file" ) { if ( !mAttachementMap.contains( url.path() ) ) return; partNum = mAttachementMap[url.path()]; } if ( url.protocol() == "part" ) partNum = url.path().toInt(); KMime::Content *c = mAttachments.at( partNum ); if ( !c ) return; // TODO: replace with message box as done in KMail if ( forceOpen || knGlobals.settings()->openAttachmentsOnClick() ) knGlobals.articleManager()->openContent( c ); else knGlobals.articleManager()->saveContentToFile( c, this ); return; } // let KDE take care of the remaining protocols (http, ftp, etc.) new KRun( url, this ); } void ArticleWidget::slotURLPopup( const QString &url, const QPoint &point ) { mCurrentURL = KUrl( url ); QString popupName; if ( url.isEmpty() ) // plain text popupName = "body_popup"; else if ( mCurrentURL.protocol() == "mailto" ) popupName = "mailto_popup"; else if ( mCurrentURL.protocol() == "file" || mCurrentURL.protocol() == "part" ) popupName = "attachment_popup"; // ### news URLS? else if ( mCurrentURL.protocol() == "knode" ) return; // skip else popupName = "url_popup"; // all other URLs QMenu *popup = static_cast( mGuiClient->factory()->container( popupName, mGuiClient ) ); if ( popup ) popup->popup( point ); } void ArticleWidget::slotTimeout() { if ( mArticle && mArticle->type() == KNArticle::ATremote && !mArticle->isOrphant() ) { KNRemoteArticle::List l; l.append( boost::static_pointer_cast( mArticle ) ); knGlobals.articleManager()->setRead( l, true ); } } void ArticleWidget::slotSave() { if ( mArticle ) knGlobals.articleManager()->saveArticleToFile( mArticle, this ); } void ArticleWidget::slotPrint( ) { if ( mArticle ) mViewer->view()->print(); } void ArticleWidget::slotCopySelection( ) { QApplication::clipboard()->setText( mViewer->selectedText() ); } void ArticleWidget::slotSelectAll() { mViewer->selectAll(); } void ArticleWidget::slotFind() { mViewer->findText(); } void ArticleWidget::slotViewSource() { // local article can be shown directly if ( mArticle && mArticle->type() == KNArticle::ATlocal && mArticle->hasContent() ) { new KNSourceViewWindow( mArticle->encodedContent( false ) ); } else { // download remote article if ( mArticle && mArticle->type() == KNArticle::ATremote ) { KNGroup::Ptr g = boost::static_pointer_cast( mArticle->collection() ); KNRemoteArticle::Ptr a = KNRemoteArticle::Ptr( new KNRemoteArticle( g ) ); //we need "g" to access the nntp-account a->messageID( true )->from7BitString( mArticle->messageID()->as7BitString( false ) ); a->lines( true )->from7BitString( mArticle->lines( true )->as7BitString( false ) ); a->setArticleNumber( boost::static_pointer_cast( mArticle )->articleNumber() ); emitJob( new ArticleFetchJob( this, g->account(), a, false ) ); } } } void ArticleWidget::slotReply() { if ( mArticle && mArticle->type() == KNArticle::ATremote ) KNGlobals::self()->articleFactory()->createReply( boost::static_pointer_cast( mArticle ), mViewer->selectedText(), true, false ); } void ArticleWidget::slotRemail() { if ( mArticle && mArticle->type()==KNArticle::ATremote ) KNGlobals::self()->articleFactory()->createReply( boost::static_pointer_cast( mArticle ), mViewer->selectedText(), false, true ); } void ArticleWidget::slotForward() { KNGlobals::self()->articleFactory()->createForward( mArticle ); } void ArticleWidget::slotCancel() { KNGlobals::self()->articleFactory()->createCancel( mArticle ); } void ArticleWidget::slotSupersede() { KNGlobals::self()->articleFactory()->createSupersede( mArticle ); } void ArticleWidget::slotToggleFixedFont() { writeConfig(); updateContents(); } void ArticleWidget::slotToggleFancyFormating( ) { writeConfig(); updateContents(); } void ArticleWidget::slotFancyHeaders() { mHeaderStyle = "fancy"; writeConfig(); updateContents(); } void ArticleWidget::slotStandardHeaders() { mHeaderStyle = "standard"; writeConfig(); updateContents(); } void ArticleWidget::slotAllHeaders() { mHeaderStyle = "all"; writeConfig(); updateContents(); } void ArticleWidget::slotIconAttachments() { mAttachmentStyle = "icon"; writeConfig(); updateContents(); } void ArticleWidget::slotInlineAttachments() { mAttachmentStyle = "inline"; writeConfig(); updateContents(); } void ArticleWidget::slotHideAttachments() { mAttachmentStyle = "hide"; writeConfig(); updateContents(); } void ArticleWidget::slotToggleRot13() { mRot13 = !mRot13; updateContents(); } void ArticleWidget::slotSetCharset( const QString &charset ) { if ( charset.isEmpty() ) return; if ( charset == i18nc( "@item default character set", "Default") ) { mForceCharset = false; mOverrideCharset = KMime::Headers::Latin1; } else { mForceCharset = true; mOverrideCharset = KGlobal::charsets()->encodingForName( charset ).toLatin1(); } if ( mArticle && mArticle->hasContent() ) { mArticle->setDefaultCharset( mOverrideCharset ); // the article will choose the correct default, mArticle->setForceDefaultCharset( mForceCharset ); // when we disable the overdrive updateContents(); } } void ArticleWidget::slotSetCharsetKeyboard( ) { int charset = KNHelper::selectDialog( this, i18n("Select Charset"), mCharsetSelect->items(), mCharsetSelect->currentItem() ); if ( charset != -1 ) { mCharsetSelect->setCurrentItem( charset ); slotSetCharset( mCharsetSelect->items()[charset] ); } } void ArticleWidget::slotOpenURL() { slotURLClicked( mCurrentURL ); } void ArticleWidget::slotCopyURL() { QString address; if ( mCurrentURL.protocol() == "mailto" ) address = mCurrentURL.path(); else address = mCurrentURL.url(); QApplication::clipboard()->setText( address, QClipboard::Clipboard ); QApplication::clipboard()->setText( address, QClipboard::Selection ); } void ArticleWidget::slotAddBookmark() { if ( mCurrentURL.isEmpty() ) return; QString filename = KStandardDirs::locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, "konqueror" ); KBookmarkGroup group = bookManager->root(); group.addBookmark( mCurrentURL.url(), mCurrentURL ); bookManager->save(); } void ArticleWidget::slotAddToAddressBook() { KPIM::AddEmailAddressJob *job = new KPIM::AddEmailAddressJob( mCurrentURL.path(), this, this ); job->start(); } void ArticleWidget::slotOpenInAddressBook() { KPIM::OpenEmailAddressJob *job = new KPIM::OpenEmailAddressJob( mCurrentURL.path(), this, this ); job->start(); } void ArticleWidget::slotOpenAttachment() { slotURLClicked( mCurrentURL, true ); } void ArticleWidget::slotSaveAttachment() { if ( mCurrentURL.protocol() != "file" && mCurrentURL.protocol() != "part" ) return; int partNum = 0; if ( mCurrentURL.protocol() == "file" ) { if ( !mAttachementMap.contains( mCurrentURL.path() ) ) return; partNum = mAttachementMap[mCurrentURL.path()]; } if ( mCurrentURL.protocol() == "part" ) partNum = mCurrentURL.path().toInt(); KMime::Content *c = mAttachments.at( partNum ); if ( !c ) return; knGlobals.articleManager()->saveContentToFile( c, this ); }