diff options
Diffstat (limited to 'knode/articlewidget.cpp')
-rw-r--r-- | knode/articlewidget.cpp | 1474 |
1 files changed, 1474 insertions, 0 deletions
diff --git a/knode/articlewidget.cpp b/knode/articlewidget.cpp new file mode 100644 index 000000000..805632c24 --- /dev/null +++ b/knode/articlewidget.cpp @@ -0,0 +1,1474 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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 <unistd.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <qbuffer.h> +#include <qclipboard.h> +#include <qdir.h> +#include <qfile.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpaintdevicemetrics.h> +#include <qpopupmenu.h> +#include <qstringlist.h> +#include <qtextcodec.h> +#include <qtimer.h> + +#include <kaction.h> +#include <kaddrbook.h> +#include <kapplication.h> +#include <kbookmarkmanager.h> +#include <kcharsets.h> +#include <khtml_part.h> +#include <khtmlview.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <krun.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +#include <kurl.h> + +#include <libemailfunctions/email.h> +#include <libemailfunctions/kasciistringtools.h> + +#include <libkpgp/kpgp.h> +#include <libkpgp/kpgpblock.h> + +#include <libkdepim/kfileio.h> +#include <libkdepim/kxface.h> +#include <libkdepim/linklocator.h> + +#include "articlewidget.h" +#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" + +using namespace KNode; + +QValueList<ArticleWidget*> ArticleWidget::mInstances; + +ArticleWidget::ArticleWidget( QWidget *parent, + KXMLGUIClient *guiClient, + KActionCollection *actionCollection, + const char *name ) : + QWidget( parent, name ), + mArticle( 0 ), + mViewer( 0 ), + mCSSHelper( 0 ), + mHeaderStyle( "fancy" ), + mAttachmentStyle( "inline" ), + mShowHtml( false ), + mRot13( false ), + mForceCharset( false ), + mOverrideCharset( KMime::Headers::Latin1 ), + mTimer( 0 ), + mGuiClient( guiClient ), + mActionCollection( actionCollection ) +{ + mInstances.append( this ); + + QHBoxLayout *box = new QHBoxLayout( this ); + mViewer = new KHTMLPart( this, "mViewer" ); + box->addWidget( mViewer->widget() ); + mViewer->widget()->setFocusPolicy( WheelFocus ); + mViewer->setPluginsEnabled( false ); + mViewer->setJScriptEnabled( false ); + mViewer->setJavaEnabled( false ); + mViewer->setMetaRefreshEnabled( false ); + mViewer->setOnlyLocalReferences( true ); + mViewer->view()->setFocusPolicy( QWidget::WheelFocus ); + connect( mViewer->browserExtension(), SIGNAL(openURLRequestDelayed(const KURL&, const KParts::URLArgs&)), + SLOT(slotURLClicked(const KURL&)) ); + connect( mViewer, SIGNAL(popupMenu(const QString&, const QPoint&)), + SLOT(slotURLPopup(const QString&, const QPoint&)) ); + + mTimer = new QTimer( this ); + connect( mTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); + + initActions(); + readConfig(); + clear(); + + installEventFilter( this ); +} + + +ArticleWidget::~ArticleWidget() +{ + mInstances.remove( this ); + delete mTimer; + delete mCSSHelper; + if ( mArticle && mArticle->isOrphant() ) + delete mArticle; + removeTempFiles(); +} + + +void ArticleWidget::initActions() +{ + mSaveAction = KStdAction::save( this, SLOT(slotSave()), mActionCollection ); + mSaveAction->setText( KStdGuiItem::saveAs().text() ); + mPrintAction = KStdAction::print( this, SLOT(slotPrint()), mActionCollection ); + mCopySelectionAction = KStdAction::copy( this, SLOT(slotCopySelection()), mActionCollection ); + mSelectAllAction = KStdAction::selectAll( this, SLOT(slotSelectAll()), mActionCollection ); + mFindAction = KStdAction::find( this, SLOT(slotFind()), mActionCollection, "find_in_article" ); + mFindAction->setText( i18n("F&ind in Article...") ); + mViewSourceAction = new KAction( i18n("&View Source"), Key_V , this, + SLOT(slotViewSource()), mActionCollection, "article_viewSource" ); + mReplyAction = new KAction( i18n("&Followup to Newsgroup..."), "message_reply", + Key_R, this, SLOT(slotReply()), mActionCollection, "article_postReply" ); + mRemailAction = new KAction( i18n("Reply by E&mail..."), "mail_reply", + Key_A, this, SLOT(slotRemail()), mActionCollection, "article_mailReply" ); + mForwardAction = new KAction( i18n("Forw&ard by Email..."), "mail_forward", + Key_F, this, SLOT(slotForward()), mActionCollection, "article_forward" ); + mCancelAction = new KAction( i18n("article","&Cancel Article"), + 0, this, SLOT(slotCancel()), mActionCollection, "article_cancel" ); + mSupersedeAction = new KAction(i18n("S&upersede Article"), + 0, this, SLOT(slotSupersede()), mActionCollection, "article_supersede" ); + mFixedFontToggle = new KToggleAction( i18n("U&se Fixed Font"), + Key_X ,this, SLOT(slotToggleFixedFont()), mActionCollection, "view_useFixedFont" ); + mFancyToggle = new KToggleAction( i18n("Fancy Formating"), + Key_Y, this, SLOT(slotToggleFancyFormating()), mActionCollection, "view_fancyFormating" ); + mRot13Toggle = new KToggleAction( i18n("&Unscramble (Rot 13)"), "decrypted", 0 , this, + SLOT(slotToggleRot13()), mActionCollection, "view_rot13" ); + mRot13Toggle->setChecked( false ); + + KRadioAction *ra; + mHeaderStyleMenu = new KActionMenu( i18n("&Headers"), mActionCollection, "view_headers" ); + ra = new KRadioAction( i18n("&Fancy Headers"), 0, this, SLOT(slotFancyHeaders()), + mActionCollection, "view_headers_fancy" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Standard Headers"), 0, this, SLOT(slotStandardHeaders()), + mActionCollection, "view_headers_standard" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&All Headers"), 0, this, SLOT(slotAllHeaders()), + mActionCollection, "view_headers_all" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + + mAttachmentStyleMenu = new KActionMenu( i18n("&Attachments"), mActionCollection, "view_attachments" ); + ra = new KRadioAction( i18n("&As Icon"), 0, this, SLOT(slotIconAttachments()), + mActionCollection, "view_attachments_icon" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Inline"), 0, this, SLOT(slotInlineAttachments()), + mActionCollection, "view_attachments_inline" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Hide"), 0, this, SLOT(slotHideAttachments()), + mActionCollection, "view_attachments_hide" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + + mCharsetSelect = new KSelectAction( i18n("Chars&et"), 0, mActionCollection, "set_charset" ); + mCharsetSelect->setShortcutConfigurable( false ); + QStringList cs = KGlobal::charsets()->descriptiveEncodingNames(); + cs.prepend( i18n("Automatic") ); + mCharsetSelect->setItems( cs ); + mCharsetSelect->setCurrentItem( 0 ); + connect( mCharsetSelect, SIGNAL(activated(const QString&)),SLOT(slotSetCharset(const QString&)) ); + mCharsetSelectKeyb = new KAction( i18n("Charset"), Key_C, this, + SLOT(slotSetCharsetKeyboard()), mActionCollection, "set_charset_keyboard" ); + + new KAction( i18n("&Open URL"), "fileopen", 0, this, SLOT(slotOpenURL()), + mActionCollection, "open_url" ); + new KAction( i18n("&Copy Link Address"), "editcopy", 0, this, SLOT( slotCopyURL()), + mActionCollection, "copy_url" ); + new KAction( i18n("&Bookmark This Link"), "bookmark_add", 0, this, SLOT(slotAddBookmark()), + mActionCollection, "add_bookmark" ); + new KAction( i18n("&Add to Address Book"), 0, this, SLOT(slotAddToAddressBook()), + mActionCollection, "add_addr_book" ); + new KAction( i18n("&Open in Address Book"), 0, this, SLOT(slotOpenInAddressBook()), + mActionCollection, "openin_addr_book" ); + new KAction( i18n("&Open Attachment"), "fileopen", 0, this, SLOT(slotOpenAttachment()), + mActionCollection, "open_attachment" ); + new KAction( i18n("&Save Attachment As..."), "filesaveas", 0, this, SLOT(slotSaveAttachment()), + mActionCollection, "save_attachment" ); +} + + + +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() == KMime::Base::ATremote ); + mReplyAction->setEnabled( enabled ); + mRemailAction->setEnabled( enabled ); + + enabled = ( mArticle->type() == KMime::Base::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.configManager()->readNewsViewer()->useFixedFont() ); + mFancyToggle->setChecked( knGlobals.configManager()->readNewsViewer()->interpretFormatTags() ); + + mShowHtml = knGlobals.configManager()->readNewsViewer()->alwaysShowHTML(); + + KConfig *conf = knGlobals.config(); + conf->setGroup( "READNEWS" ); + mAttachmentStyle = conf->readEntry( "attachmentStyle", "inline" ); + mHeaderStyle = conf->readEntry( "headerStyle", "fancy" ); + KRadioAction *ra = 0; + ra = static_cast<KRadioAction*>( mActionCollection->action( QString("view_attachments_%1").arg(mAttachmentStyle).latin1() ) ); + ra->setChecked( true ); + ra = static_cast<KRadioAction*>( mActionCollection->action( QString("view_headers_%1").arg(mHeaderStyle).latin1() ) ); + ra->setChecked( true ); + + delete mCSSHelper; + mCSSHelper = new CSSHelper( QPaintDeviceMetrics( mViewer->view() ) ); + + if ( !knGlobals.configManager()->readNewsGeneral()->autoMark() ) + mTimer->stop(); +} + + +void ArticleWidget::writeConfig() +{ + // main viewer determines the settings + if ( knGlobals.artWidget != this ) + return; + + KConfig *conf = knGlobals.config(); + conf->setGroup( "READNEWS" ); + conf->writeEntry( "attachmentStyle", mAttachmentStyle ); + conf->writeEntry( "headerStyle", mHeaderStyle ); + + knGlobals.configManager()->readNewsViewer()->setUseFixedFont( mFixedFontToggle->isChecked() ); + knGlobals.configManager()->readNewsViewer()->setInterpretFormatTags( mFancyToggle->isChecked() ); +} + + + +void ArticleWidget::setArticle( KNArticle *article ) +{ + // don't leak orphant articles + if ( mArticle && mArticle->isOrphant() ) + delete mArticle; + + mShowHtml = knGlobals.configManager()->readNewsViewer()->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() == KMime::Base::ATremote ) ) + displayArticle(); + } + } +} + + +void ArticleWidget::clear() +{ + disableActions(); + mViewer->begin(); + mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); + mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); + mViewer->write( "</body></html>" ); + 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->forceDefaultCS() + || ( mForceCharset && mArticle->defaultCharset() != mOverrideCharset ) ) { + mArticle->setDefaultCharset( mOverrideCharset ); + mArticle->setForceDefaultCS( mForceCharset ); + } + + KNConfig::ReadNewsViewer *rnv = knGlobals.configManager()->readNewsViewer(); + removeTempFiles(); + + mViewer->begin(); + mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); + mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); + + // headers + displayHeader(); + + // body + QString html; + KMime::Content *text = mArticle->textContent(); + + // check if codec is available + if ( text && !canDecodeText( text->contentType()->charset() ) ) { + html += QString("<table width=\"100%\" border=\"0\"><tr><td bgcolor=\"#FF0000\">%1</td></tr></table>") + .arg( i18n("Unknown charset. Default charset is used instead.") ); + kdDebug(5003) << k_funcinfo << "unknown charset = " << text->contentType()->charset() << endl; + } + + // if the article is pgp signed and the user asked for verifying the + // signature, we show a nice header: + QPtrList<Kpgp::Block> pgpBlocks; + QStrList nonPgpBlocks; + bool containsPGP = Kpgp::Module::prepareMessageForDecryption( mArticle->body(), pgpBlocks, nonPgpBlocks ); + + mViewer->write ( html ); + html = QString(); + + if ( containsPGP ) { + QPtrListIterator<Kpgp::Block> pbit( pgpBlocks ); + QStrListIterator npbit( nonPgpBlocks ); + QTextCodec *codec; + if ( text ) + codec = KGlobal::charsets()->codecForName( text->contentType()->charset() ); + else + codec = KGlobal::locale()->codecForEncoding(); + + for( ; *pbit != 0; ++pbit, ++npbit ) { + // handle non-pgp block + QCString str( *npbit ); + if( !str.isEmpty() ) { + QStringList lines = QStringList::split( '\n', codec->toUnicode( str ), true ); + displayBodyBlock( lines ); + } + // handle pgp block + Kpgp::Block* block = *pbit; + if ( block->type() == Kpgp::ClearsignedBlock ) + block->verify(); + QStringList lines = QStringList::split( '\n', codec->toUnicode( block->text() ), true ); + if ( block->isSigned() ) { + QString signClass = displaySigHeader( block ); + displayBodyBlock( lines ); + displaySigFooter( signClass ); + } else { + displayBodyBlock( lines ); + } + } + // deal with the last non-pgp block + QCString str( *npbit ); + if( !str.isEmpty() ) { + QStringList lines = QStringList::split( '\n', codec->toUnicode( str ), true ); + displayBodyBlock( lines ); + } + } + + KMime::Headers::ContentType *ct = mArticle->contentType(); + + // get attachments + mAttachments.clear(); + mAttachementMap.clear(); + if( !text || ct->isMultipart() ) + mArticle->attachments( &mAttachments, rnv->showAlternativeContents() ); + + // partial message + if(ct->isPartial()) { + mViewer->write( i18n("<br/><b>This article has the MIME type "message/partial", which KNode cannot handle yet.<br>Meanwhile you can save the article as a text file and reassemble it by hand.</b>") ); + } + + // display body text + if ( text && text->hasContent() && !ct->isPartial() ) { + // handle HTML messages + if ( text->contentType()->isHTMLText() ) { + QString htmlTxt; + text->decodedText( htmlTxt, true, knGlobals.configManager()->readNewsViewer()->removeTrailingNewlines() ); + if ( mShowHtml ) { + // strip </html> & </body> + int i = kMin( htmlTxt.findRev( "</html>", -1, false ), htmlTxt.findRev( "</body>", -1, false ) ); + if ( i >= 0 ) + htmlTxt.truncate( i ); + html += htmlTxt; + } else { + html += "<div class=\"htmlWarn\">\n"; + html += i18n("<b>Note:</b> 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 " + "<a href=\"knode:showHTML\">by clicking here</a>."); + html += "</div><br><br>"; + html += toHtmlString( htmlTxt ); + } + } + else { + if ( !containsPGP ) { + QStringList lines; + text->decodedText( lines, true, knGlobals.configManager()->readNewsViewer()->removeTrailingNewlines() ); + displayBodyBlock( lines ); + } + } + + } + mViewer->write( html ); + + // display attachments + if( !mAttachments.isEmpty() && !ct->isPartial() ) { + int attCnt = 0; + for( KMime::Content *var = mAttachments.first(); var; var = mAttachments.next() ) { + displayAttachment( var, attCnt ); + attCnt++; + } + } + + mViewer->write("</body></html>"); + mViewer->end(); + + enableActions(); + if( mArticle->type() == KMime::Base::ATremote && knGlobals.configManager()->readNewsGeneral()->autoMark() ) + mTimer->start( knGlobals.configManager()->readNewsGeneral()->autoMarkSeconds() * 1000, true ); +} + + +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( "<b><font size=\"+1\" color=\"red\">" ); + mViewer->write( i18n("An error occurred.") ); + mViewer->write( "</font></b><hr/><br/>" ); + mViewer->write( errMsg.replace( "\n", "<br/>" ) ); + mViewer->write( "</body></html>"); + mViewer->end(); + + // mark article as read if there is a negative reply from the server + if ( knGlobals.configManager()->readNewsGeneral()->autoMark() && + mArticle && mArticle->type() == KMime::Base::ATremote && !mArticle->isOrphant() && + ( msg.find("430") != -1 || msg.find("423") != -1 ) ) { + KNRemoteArticle::List l; + l.append( static_cast<KNRemoteArticle*>( mArticle ) ); + knGlobals.articleManager()->setRead( l, true ); + } + + disableActions(); +} + + + +void ArticleWidget::displayHeader() +{ + QString headerHtml; + + // full header style + if ( mHeaderStyle == "all" ) { + QCString head = mArticle->head(); + KMime::Headers::Generic *header = 0; + + while ( !head.isEmpty() ) { + header = mArticle->getNextHeader( head ); + if ( header ) { + headerHtml += "<tr>"; + headerHtml+=QString( "<td align=\"right\" valign=\"top\"><b>%1</b></td><td width=\"100%\">%2</td></tr>" ) + .arg( toHtmlString( header->type(), None ) + ": " ) + .arg( toHtmlString( header->asUnicodeString() , ParseURL ) ); + delete header; + } + } + + mViewer->write( "<div class=\"header\">" ); + mViewer->write( "<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"> " ); + mViewer->write( headerHtml ); + mViewer->write( "</table></div>" ); + return; + } + + // standard & fancy header style + KMime::Headers::Base *hb; + QValueList<KNDisplayedHeader*> dhs = knGlobals.configManager()->displayedHeaders()->headers(); + for ( QValueList<KNDisplayedHeader*>::Iterator it = dhs.begin(); it != dhs.end(); ++it ) { + KNDisplayedHeader *dh = (*it); + hb = mArticle->getHeaderByType(dh->header().latin1()); + if ( !hb || hb->is("Subject") || hb->is("Organization") ) + continue; + + if ( dh->hasName() ) { + headerHtml += "<tr>"; + if ( mHeaderStyle == "fancy" ) + headerHtml += "<th>"; + else + headerHtml += "<th align=\"right\">"; + headerHtml += toHtmlString( dh->translatedName(), None ); + headerHtml += ":</th><td width=\"100%\">"; + } + else + headerHtml+="<tr><td colspan=\"2\">"; + + if ( hb->is("From") ) { + headerHtml += QString( "<a href=\"mailto:%1\">%2</a>") + .arg( KPIM::getEmailAddress( hb->asUnicodeString() ) ) + .arg( toHtmlString( hb->asUnicodeString(), None ) ); + KMime::Headers::Base *orgHdr = mArticle->getHeaderByType( "Organization" ); + if ( orgHdr && !orgHdr->isEmpty() ) { + headerHtml += " ("; + headerHtml += toHtmlString( orgHdr->asUnicodeString() ); + headerHtml += ")"; + } + } else if ( hb->is("Date") ) { + KMime::Headers::Date *date=static_cast<KMime::Headers::Date*>(hb); + headerHtml += toHtmlString( KGlobal::locale()->formatDateTime(date->qdt(), false, true), None ); + } else if ( hb->is("Newsgroups") ) { + QString groups = hb->asUnicodeString(); + groups.replace( ',', ", " ); + headerHtml += toHtmlString( groups, ParseURL ); + } else + headerHtml += toHtmlString( hb->asUnicodeString(), ParseURL ); + + headerHtml += "</td></tr>"; + } + + // standard header style + if ( mHeaderStyle == "standard" ) { + mViewer->write( "<b style=\"font-size:130%\">" + toHtmlString( mArticle->subject()->asUnicodeString() ) + "</b>" ); + mViewer->write( "<div class=\"header\"" ); + mViewer->write( "<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>" + headerHtml ); + mViewer->write( "</tr></table></div>" ); + return; + } + + // X-Face support + QString xfhead; + KMime::Headers::Base *temp = mArticle->getHeaderByType("X-Face"); + if (temp) + xfhead = temp->asUnicodeString(); + QString xface = ""; + if ( !xfhead.isEmpty() ) { + KPIM::KXFace xf; + xface = QString::fromLatin1( "<div class=\"senderpic\"><img src=\"%1\" width=\"48\" height=\"48\"/></div>" ) + .arg( imgToDataUrl( xf.toImage( xfhead ), "PNG" ) ); + } + + // fancy header style + mViewer->write( "<div class=\"fancy header\"" ); + mViewer->write( QString("<div>") ); + mViewer->write( toHtmlString( mArticle->subject()->asUnicodeString(), ParseURL | FancyFormatting ) ); + mViewer->write( QString("</div>") ); + + QString html = QString("<table class=\"outer\"><tr><td width=\"100%\"><table>"); + + html += headerHtml; + html+="</td></tr></table></td>"; + html += "<td align=\"center\">" + xface + "</td>"; + html += "</tr></table>"; + + // references + KMime::Headers::References *refs = mArticle->references( false ); + if ( mArticle->type() == KMime::Base::ATremote && refs + && knGlobals.configManager()->readNewsViewer()->showRefBar() ) { + html += "<div class=\"spamheader\">"; + int refCnt = refs->count(), i = 1; + QCString id = refs->first(); + id = id.mid( 1, id.length() - 2 ); // remove <> + html += QString( "<b>%1</b>" ).arg( i18n("References:") ); + + while ( i <= refCnt ) { + html += " <a href=\"news:" + QString::fromLatin1( id ) + "\">" + QString::number( i ) + "</a>"; + id = refs->next(); + id = id.mid( 1, id.length() - 2 ); // remove <> + i++; + } + html += "</div>"; + } + + mViewer->write( html ); + mViewer->write( "</div>" ); +} + + +void ArticleWidget::displayBodyBlock( const QStringList &lines ) +{ + int oldLevel = -2, newLevel = -2; + bool isSig = false; + QString line, html; + KNConfig::ReadNewsViewer *rnv = knGlobals.configManager()->readNewsViewer(); + QString quoteChars = rnv->quoteCharacters().simplifyWhiteSpace(); + if (quoteChars.isEmpty()) + quoteChars = ">"; + + for ( QStringList::const_iterator it = lines.begin(); it != lines.end(); ++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 += "</div>"; + html += mCSSHelper->nonQuotedFontTag(); + newLevel = -1; + if ( rnv->showSignature() ) { + html += "<hr size=\"1\"/>"; + 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 += "</div>"; // 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 ) + "<br/>"; + } else { + // signature + html += toHtmlString( line, ParseURL | AllowROT13 ) + "<br/>"; + } + } else { + // empty line + html += "<br/>"; + } + } + // close body quoting level tags + if ( newLevel != -2 ) + html += "</div>"; + + mViewer->write( html ); +} + + +QString ArticleWidget::displaySigHeader( Kpgp::Block* block ) +{ + QString signClass = "signErr"; + QString signer = block->signatureUserId(); + QCString signerKey = block->signatureKeyId(); + QString message; + if ( signer.isEmpty() ) { + message = i18n( "Message was signed with unknown key 0x%1." ) + .arg( signerKey ); + message += "<br/>"; + message += i18n( "The validity of the signature cannot be verified." ); + signClass = "signWarn"; + } else { + // determine the validity of the key + Kpgp::Module *pgp = knGlobals.pgp; + 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 = "<a href=\"mailto:" + KPIM::getEmailAddress( signer ) + "\">" + signer + "</a>"; + + if( !signerKey.isEmpty() ) + message += i18n( "Message was signed by %1 (Key ID: 0x%2)." ) + .arg( signer ) + .arg( signerKey ); + else + message += i18n( "Message was signed by %1." ).arg( signer ); + message += "<br/>"; + + 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 = "<table cellspacing=\"1\" cellpadding=\"1\" class=\"" + signClass + "\">"; + html += "<tr class=\"" + signClass + "H\"><td>"; + html += message; + html += "</td></tr><tr class=\"" + signClass + "B\"><td>"; + mViewer->write( html ); + return signClass; +} + + +void ArticleWidget::displaySigFooter( const QString &signClass ) +{ + QString html = "</td></tr><tr class=\"" + signClass + "H\">"; + html += "<td>" + i18n( "End of signed message" ) + "</td></tr></table>"; + 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 ( (uint)label.contains( ' ' ) == label.length() ) + label.replace( QRegExp( " ", true, true ), "_" ); + label = toHtmlString( label, None ); + + // attachment comment + QString comment = att->contentDescription()->asUnicodeString(); + comment = toHtmlString( comment, ParseURL | FancyFormatting ); + + QString href; + QString fileName = writeAttachmentToTempFile( att, partNum ); + if ( fileName.isEmpty() ) { + href = "part://" + QString::number( partNum ); + } else { + href = "file:" + KURL::encode_string( fileName ); + mAttachementMap[fileName] = partNum; + } + + if ( mAttachmentStyle == "inline" && inlinePossible( att ) ) { + if ( ct->isImage() ) { + html += "<div><a href=\"" + href + "\">" + "<img src=\"" + fileName + "\" border=\"0\"></a>" + "</div><div><a href=\"" + href + "\">" + label + "</a>" + "</div><div>" + comment + "</div><br>"; + } else { //text + // frame + html += "<table cellspacing=\"1\" class=\"textAtm\">" + "<tr class=\"textAtmH\"><td>" + "<a href=\"" + href + "\">" + label + "</a>"; + if ( !comment.isEmpty() ) + html += "<br>" + comment; + html += "</td></tr><tr class=\"textAtmB\"><td>"; + // content + QString tmp; + att->decodedText( tmp ); + /*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 += "</td></tr></table>"; + } + } else { // icon + QCString mimetype = ct->mimeType(); + KPIM::kAsciiToLower( mimetype.data() ); + QString iconName = KMimeType::mimeType( mimetype )->icon( QString::null, false ); + QString iconFile = KGlobal::instance()->iconLoader()->iconPath( iconName, KIcon::Desktop ); + html += "<div><a href=\"" + href + "\"><img src=\"" + + iconFile + "\" border=\"0\">" + label + + "</a></div><div>" + comment + "</div><br>"; + } + mViewer->write( html ); +} + + +QString ArticleWidget::toHtmlString( const QString &line, int flags ) +{ + int llflags = LinkLocator::PreserveSpaces; + if ( !(flags & ArticleWidget::ParseURL) ) + llflags |= LinkLocator::IgnoreUrls; + if ( mFancyToggle->isChecked() && (flags & ArticleWidget::FancyFormatting) ) + llflags |= LinkLocator::ReplaceSmileys | LinkLocator::HighlightText; + QString text = line; + if ( flags & ArticleWidget::AllowROT13 ) { + if ( mRot13 ) + text = KNHelper::rot13( line ); + } + return LinkLocator::convertToHtml( text, llflags ); +} + + +// from KMail headerstyle.cpp +QString ArticleWidget::imgToDataUrl( const QImage &image, const char* fmt ) +{ + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + image.save( &buffer, fmt ); + return QString::fromLatin1("data:image/%1;base64,%2") + .arg( fmt, KCodecs::base64Encode( ba ) ); +} + + + +int ArticleWidget::quotingDepth( const QString &line, const QString "eChars ) +{ + int level = -1; + for ( uint i = 0; i < line.length(); ++i ) { + // skip spaces + if ( line[i].isSpace() ) + continue; + if ( quoteChars.find( 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 QCString &charset ) const +{ + 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 ) ); +} + + + +QString ArticleWidget::writeAttachmentToTempFile( KMime::Content *att, int partNum ) +{ + // more or less KMail code + KTempFile *tempFile = new KTempFile( QString::null, "." + QString::number( partNum ) ); + tempFile->setAutoDelete( true ); + QString fname = tempFile->name(); + delete tempFile; + + if( ::access( QFile::encodeName( fname ), W_OK ) != 0 ) + // Not there or not writable + if( ::mkdir( QFile::encodeName( fname ), 0 ) != 0 + || ::chmod( QFile::encodeName( fname ), S_IRWXU ) != 0 ) + return QString::null; //failed create + + Q_ASSERT( !fname.isNull() ); + + mTempDirs.append( fname ); + // strip off a leading path + KMime::Headers::ContentType* ct = att->contentType(); + QString attName = ct->name(); + int slashPos = attName.findRev( '/' ); + if( -1 != slashPos ) + attName = attName.mid( slashPos + 1 ); + if( attName.isEmpty() ) + attName = "unnamed"; + fname += "/" + attName; + + QByteArray data = att->decodedContent(); + size_t size = data.size(); + // ### KMail does crlf2lf conversion here before writing the file + if( !KPIM::kBytesToFile( data.data(), size, fname, false, false, false ) ) + return QString::null; + + mTempFiles.append( fname ); + // make file read-only so that nobody gets the impression that he might + // edit attached files + ::chmod( QFile::encodeName( fname ), S_IRUSR ); + + return fname; +} + + +void ArticleWidget::removeTempFiles( ) +{ + for ( QStringList::Iterator it = mTempFiles.begin(); it != mTempFiles.end(); ++it ) + QFile::remove(*it); + mTempFiles.clear(); + for ( QStringList::Iterator it = mTempDirs.begin(); it != mTempDirs.end(); ++it ) + QDir(*it).rmdir(*it); + mTempDirs.clear(); +} + + + +void ArticleWidget::processJob( KNJobData * job ) +{ + if ( job->type() == KNJobData::JTfetchSource ) { + KNRemoteArticle *a = static_cast<KNRemoteArticle*>( job->data() ); + if ( !job->canceled() ) { + if ( !job->success() ) + KMessageBox::error( this, i18n("An error occurred while downloading the article source:\n") + .arg( job->errorString() ) ); + else + new KNSourceViewWindow( a->head() + "\n" + a->body() ); + } + delete job; + delete a; + } + else + delete job; +} + + + +typedef QValueList<ArticleWidget*>::ConstIterator InstanceIterator; + +void ArticleWidget::configChanged() +{ + for( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) { + (*it)->readConfig(); + (*it)->updateContents(); + } +} + + +bool ArticleWidget::articleVisible( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + return true; + return false; +} + + +void ArticleWidget::articleRemoved( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->setArticle( 0 ); +} + + +void ArticleWidget::articleChanged( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->displayArticle(); +} + + +void ArticleWidget::articleLoadError( KNArticle *article, const QString &error ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->displayErrorMessage( error ); +} + + +void ArticleWidget::collectionRemoved( KNArticleCollection *coll ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() && (*it)->article()->collection() == coll ) + (*it)->setArticle( 0 ); +} + + +void ArticleWidget::cleanup() +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + (*it)->setArticle( 0 ); //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::Headers::AddressField addr( mArticle ); + addr.fromUnicodeString( url.path(), "" ); + knGlobals.artFactory->createMail( &addr ); + return; + } + // handle news URL's + if ( url.protocol() == "news" ) { + kdDebug( 5003 ) << k_funcinfo << url << endl; + 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.configManager()->readNewsViewer()->openAttachmentsOnClick() ) + knGlobals.articleManager()->openContent( c ); + else + knGlobals.articleManager()->saveContentToFile( c, this ); + return; + } + // let KDE take care of the remaing protocols (http, ftp, etc.) + new KRun( url ); +} + + +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 + QPopupMenu *popup = static_cast<QPopupMenu*>( mGuiClient->factory()->container( popupName, mGuiClient ) ); + if ( popup ) + popup->popup( point ); +} + + + +void ArticleWidget::slotTimeout() +{ + if ( mArticle && mArticle->type() == KMime::Base::ATremote && !mArticle->isOrphant() ) { + KNRemoteArticle::List l; + l.append( static_cast<KNRemoteArticle*>( 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( ) +{ + kapp->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() == KMime::Base::ATlocal && mArticle->hasContent() ) { + new KNSourceViewWindow( mArticle->encodedContent( false ) ); + } else { + // download remote article + if ( mArticle && mArticle->type() == KMime::Base::ATremote ) { + KNGroup *g = static_cast<KNGroup*>( mArticle->collection() ); + KNRemoteArticle *a = 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( static_cast<KNRemoteArticle*>( mArticle)->articleNumber() ); + emitJob( new KNJobData( KNJobData::JTfetchSource, this, g->account(), a) ); + } + } +} + + +void ArticleWidget::slotReply() +{ + if ( mArticle && mArticle->type() == KMime::Base::ATremote ) + knGlobals.artFactory->createReply( static_cast<KNRemoteArticle*>( mArticle ), + mViewer->selectedText(), true, false ); +} + + +void ArticleWidget::slotRemail() +{ + if ( mArticle && mArticle->type()==KMime::Base::ATremote ) + knGlobals.artFactory->createReply( static_cast<KNRemoteArticle*>( mArticle ), + mViewer->selectedText(), false, true ); +} + + +void ArticleWidget::slotForward() +{ + knGlobals.artFactory->createForward( mArticle ); +} + + +void ArticleWidget::slotCancel() +{ + knGlobals.artFactory->createCancel( mArticle ); +} + + +void ArticleWidget::slotSupersede() +{ + knGlobals.artFactory->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 == i18n("Automatic") ) { + mForceCharset = false; + mOverrideCharset = KMime::Headers::Latin1; + } else { + mForceCharset = true; + mOverrideCharset = KGlobal::charsets()->encodingForName( charset ).latin1(); + } + + if ( mArticle && mArticle->hasContent() ) { + mArticle->setDefaultCharset( mOverrideCharset ); // the article will choose the correct default, + mArticle->setForceDefaultCS( 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().at( 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 = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); + KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, false ); + KBookmarkGroup group = bookManager->root(); + group.addBookmark( bookManager, mCurrentURL.url(), mCurrentURL ); + bookManager->save(); +} + +void ArticleWidget::slotAddToAddressBook() +{ + KAddrBookExternal::addEmail( mCurrentURL.path(), this ); +} + +void ArticleWidget::slotOpenInAddressBook() +{ + KAddrBookExternal::openEmail( mCurrentURL.path(), this ); +} + +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 ); +} + + + +void ArticleWidget::focusInEvent( QFocusEvent *e ) +{ + emit focusChanged(e); + QWidget::focusInEvent(e); +} + +void ArticleWidget::focusOutEvent( QFocusEvent *e ) +{ + emit focusChanged(e); + QWidget::focusOutEvent(e); +} + +bool ArticleWidget::eventFilter( QObject *o, QEvent *e ) +{ + if ( e->type() == QEvent::KeyPress && (static_cast<QKeyEvent*>(e)->key() == Key_Tab) ) { + emit focusChangeRequest( this ); + if ( !hasFocus() ) // focusChangeRequest was successful + return true; + } + return QWidget::eventFilter(o, e); +} + +#include "articlewidget.moc" |