• Skip to content
  • Skip to link menu
KDE 4.5 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KPIMTextedit Library

textedit.cpp

00001 /*
00002     Copyright (c) 2009 Thomas McGuire <mcguire@kde.org>
00003 
00004     Based on KMail and libkdepim code by:
00005     Copyright 2007 Laurent Montel <montel@kde.org>
00006 
00007     This library is free software; you can redistribute it and/or modify it
00008     under the terms of the GNU Library General Public License as published by
00009     the Free Software Foundation; either version 2 of the License, or (at your
00010     option) any later version.
00011 
00012     This library is distributed in the hope that it will be useful, but WITHOUT
00013     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00015     License for more details.
00016 
00017     You should have received a copy of the GNU Library General Public License
00018     along with this library; see the file COPYING.LIB.  If not, write to the
00019     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00020     02110-1301, USA.
00021 */
00022 #include "textedit.h"
00023 
00024 #include "emailquotehighlighter.h"
00025 
00026 #include <kmime/kmime_codecs.h>
00027 
00028 #include <KDE/KAction>
00029 #include <KDE/KActionCollection>
00030 #include <KDE/KCursor>
00031 #include <KDE/KFileDialog>
00032 #include <KDE/KLocalizedString>
00033 #include <KDE/KMessageBox>
00034 #include <KDE/KPushButton>
00035 #include <KDE/KUrl>
00036 
00037 #include <QtCore/QBuffer>
00038 #include <QtCore/QDateTime>
00039 #include <QtCore/QMimeData>
00040 #include <QtCore/QFileInfo>
00041 #include <QtCore/QPointer>
00042 #include <QtGui/QKeyEvent>
00043 #include <QtGui/QTextLayout>
00044 
00045 #include "textutils.h"
00046 #include <QPlainTextEdit>
00047 
00048 namespace KPIMTextEdit {
00049 
00050 class TextEditPrivate
00051 {
00052   public:
00053 
00054     TextEditPrivate( TextEdit *parent )
00055       : actionAddImage( 0 ),
00056         actionDeleteLine( 0 ),
00057         q( parent ),
00058         imageSupportEnabled( false )
00059     {
00060     }
00061 
00070     void addImageHelper( const QString &imageName, const QImage &image );
00071 
00075     QList<QTextImageFormat> embeddedImageFormats() const;
00076 
00081     void fixupTextEditString( QString &text ) const;
00082 
00086     void init();
00087 
00092     void _k_slotAddImage();
00093 
00094     void _k_slotDeleteLine();
00095 
00097     KAction *actionAddImage;
00098 
00100     KAction *actionDeleteLine;
00101 
00103     TextEdit *q;
00104 
00106     bool imageSupportEnabled;
00107 
00113     QStringList mImageNames;
00114 
00126     bool spellCheckingEnabled;
00127 
00128     QString configFile;
00129 };
00130 
00131 } // namespace
00132 
00133 using namespace KPIMTextEdit;
00134 
00135 void TextEditPrivate::fixupTextEditString( QString &text ) const
00136 {
00137   // Remove line separators. Normal \n chars are still there, so no linebreaks get lost here
00138   text.remove( QChar::LineSeparator );
00139 
00140   // Get rid of embedded images, see QTextImageFormat documentation:
00141   // "Inline images are represented by an object replacement character (0xFFFC in Unicode) "
00142   text.remove( 0xFFFC );
00143 
00144   // In plaintext mode, each space is non-breaking.
00145   text.replace( QChar::Nbsp, QChar::fromAscii( ' ' ) );
00146 }
00147 
00148 TextEdit::TextEdit( const QString& text, QWidget *parent )
00149   : KRichTextWidget( text, parent ),
00150     d( new TextEditPrivate( this ) )
00151 {
00152   d->init();
00153 }
00154 
00155 TextEdit::TextEdit( QWidget *parent )
00156   : KRichTextWidget( parent ),
00157     d( new TextEditPrivate( this ) )
00158 {
00159   d->init();
00160 }
00161 
00162 TextEdit::TextEdit( QWidget *parent, const QString& configFile )
00163   : KRichTextWidget( parent ),
00164     d( new TextEditPrivate( this ) )
00165 {
00166   d->init();
00167   d->configFile = configFile;
00168 }
00169 
00170 TextEdit::~TextEdit()
00171 {
00172 }
00173 
00174 bool TextEdit::eventFilter( QObject*o, QEvent* e )
00175 {
00176   if ( o == this )
00177     KCursor::autoHideEventFilter( o, e );
00178   return KRichTextWidget::eventFilter( o, e );
00179 }
00180 
00181 void TextEditPrivate::init()
00182 {
00183   q->setSpellInterface( q );
00184   // We tell the KRichTextWidget to enable spell checking, because only then it will
00185   // call createHighlighter() which will create our own highlighter which also
00186   // does quote highlighting.
00187   // However, *our* spellchecking is still disabled. Our own highlighter only
00188   // cares about our spellcheck status, it will not highlight missspelled words
00189   // if our spellchecking is disabled.
00190   // See also KEMailQuotingHighlighter::highlightBlock().
00191   spellCheckingEnabled = false;
00192   q->setCheckSpellingEnabledInternal( true );
00193 
00194   KCursor::setAutoHideCursor( q, true, true );
00195   q->installEventFilter( q );
00196 }
00197 
00198 QString TextEdit::configFile() const
00199 {
00200    return d->configFile;
00201 }
00202 
00203 
00204 void TextEdit::keyPressEvent ( QKeyEvent * e )
00205 {
00206   if ( e->key() ==  Qt::Key_Return ) {
00207     QTextCursor cursor = textCursor();
00208     int oldPos = cursor.position();
00209     int blockPos = cursor.block().position();
00210 
00211     //selection all the line.
00212     cursor.movePosition( QTextCursor::StartOfBlock );
00213     cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00214     QString lineText = cursor.selectedText();
00215     if ( ( ( oldPos -blockPos )  > 0 ) &&
00216          ( ( oldPos-blockPos ) < int( lineText.length() ) ) ) {
00217       bool isQuotedLine = false;
00218       int bot = 0; // bot = begin of text after quote indicators
00219       while ( bot < lineText.length() ) {
00220         if( ( lineText[bot] == QChar::fromAscii( '>' ) ) ||
00221             ( lineText[bot] == QChar::fromAscii( '|' ) ) ) {
00222           isQuotedLine = true;
00223           ++bot;
00224         }
00225         else if ( lineText[bot].isSpace() ) {
00226           ++bot;
00227         }
00228         else {
00229           break;
00230         }
00231       }
00232       KRichTextWidget::keyPressEvent( e );
00233       // duplicate quote indicators of the previous line before the new
00234       // line if the line actually contained text (apart from the quote
00235       // indicators) and the cursor is behind the quote indicators
00236       if ( isQuotedLine
00237            && ( bot != lineText.length() )
00238            && ( ( oldPos-blockPos ) >= int( bot ) ) ) {
00239         // The cursor position might have changed unpredictably if there was selected
00240         // text which got replaced by a new line, so we query it again:
00241         cursor.movePosition( QTextCursor::StartOfBlock );
00242         cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00243         QString newLine = cursor.selectedText();
00244 
00245         // remove leading white space from the new line and instead
00246         // add the quote indicators of the previous line
00247         int leadingWhiteSpaceCount = 0;
00248         while ( ( leadingWhiteSpaceCount < newLine.length() )
00249                   && newLine[leadingWhiteSpaceCount].isSpace() ) {
00250           ++leadingWhiteSpaceCount;
00251         }
00252         newLine = newLine.replace( 0, leadingWhiteSpaceCount,
00253                                    lineText.left( bot ) );
00254         cursor.insertText( newLine );
00255         //cursor.setPosition( cursor.position() + 2);
00256         cursor.movePosition( QTextCursor::StartOfBlock );
00257         setTextCursor( cursor );
00258       }
00259     }
00260     else
00261       KRichTextWidget::keyPressEvent( e );
00262   }
00263   else
00264   {
00265     KRichTextWidget::keyPressEvent( e );
00266   }
00267 }
00268 
00269 
00270 bool TextEdit::isSpellCheckingEnabled() const
00271 {
00272   return d->spellCheckingEnabled;
00273 }
00274 
00275 void TextEdit::setSpellCheckingEnabled( bool enable )
00276 {
00277   EMailQuoteHighlighter *hlighter =
00278       dynamic_cast<EMailQuoteHighlighter*>( highlighter() );
00279   if ( hlighter )
00280     hlighter->toggleSpellHighlighting( enable );
00281 
00282   d->spellCheckingEnabled = enable;
00283   emit checkSpellingChanged( enable );
00284 }
00285 
00286 bool TextEdit::shouldBlockBeSpellChecked( const QString& block ) const
00287 {
00288   return !isLineQuoted( block );
00289 }
00290 
00291 bool KPIMTextEdit::TextEdit::isLineQuoted( const QString& line ) const
00292 {
00293   return quoteLength( line ) > 0;
00294 }
00295 
00296 int KPIMTextEdit::TextEdit::quoteLength( const QString& line ) const
00297 {
00298   bool quoteFound = false;
00299   int startOfText = -1;
00300   for ( int i = 0; i < line.length(); i++ ) {
00301     if ( line[i] == QLatin1Char( '>' ) || line[i] == QLatin1Char( '|' ) )
00302       quoteFound = true;
00303     else if ( line[i] != QLatin1Char( ' ' ) ) {
00304       startOfText = i;
00305       break;
00306     }
00307   }
00308   if ( quoteFound ) {
00309     if ( startOfText == -1 )
00310       startOfText = line.length() - 1;
00311     return startOfText;
00312   }
00313   else
00314     return 0;
00315 }
00316 
00317 const QString KPIMTextEdit::TextEdit::defaultQuoteSign() const
00318 {
00319   return QLatin1String( "> " );
00320 }
00321 
00322 void TextEdit::createHighlighter()
00323 {
00324   EMailQuoteHighlighter *emailHighLighter =
00325       new EMailQuoteHighlighter( this );
00326 
00327   setHighlighterColors( emailHighLighter );
00328 
00329   //TODO change config
00330   KRichTextWidget::setHighlighter( emailHighLighter );
00331 
00332   if ( !spellCheckingLanguage().isEmpty() )
00333     setSpellCheckingLanguage( spellCheckingLanguage() );
00334   setSpellCheckingEnabled( isSpellCheckingEnabled() );
00335 }
00336 
00337 void TextEdit::setHighlighterColors( EMailQuoteHighlighter *highlighter )
00338 {
00339   Q_UNUSED( highlighter );
00340 }
00341 
00342 QString TextEdit::toWrappedPlainText() const
00343 {
00344   QString temp;
00345   QTextDocument* doc = document();
00346   QTextBlock block = doc->begin();
00347   while ( block.isValid() ) {
00348     QTextLayout* layout = block.layout();
00349     for ( int i = 0; i < layout->lineCount(); i++ ) {
00350       QTextLine line = layout->lineAt( i );
00351       temp += block.text().mid( line.textStart(), line.textLength() ) + QLatin1Char( '\n' );
00352     }
00353     block = block.next();
00354   }
00355 
00356   // Remove the last superfluous newline added above
00357   if ( temp.endsWith( QLatin1Char( '\n' ) ) )
00358     temp.chop( 1 );
00359 
00360   d->fixupTextEditString( temp );
00361   return temp;
00362 }
00363 
00364 QString TextEdit::toCleanPlainText() const
00365 {
00366   QString temp = toPlainText();
00367   d->fixupTextEditString( temp );
00368   return temp;
00369 }
00370 
00371 void TextEdit::createActions( KActionCollection *actionCollection )
00372 {
00373   KRichTextWidget::createActions( actionCollection );
00374 
00375   if ( d->imageSupportEnabled ) {
00376     d->actionAddImage = new KAction( KIcon( QLatin1String( "insert-image" ) ),
00377                                     i18n( "Add Image" ), this );
00378     actionCollection->addAction( QLatin1String( "add_image" ), d->actionAddImage );
00379     connect( d->actionAddImage, SIGNAL(triggered(bool) ), SLOT( _k_slotAddImage() ) );
00380   }
00381 
00382   d->actionDeleteLine = new KAction( i18n( "Delete Line" ), this );
00383   d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
00384   actionCollection->addAction( QLatin1String( "delete_line" ), d->actionDeleteLine );
00385   connect( d->actionDeleteLine, SIGNAL(triggered(bool)), SLOT(_k_slotDeleteLine()) );
00386 }
00387 
00388 void TextEdit::addImage( const KUrl &url )
00389 {
00390   QImage image;
00391   if ( !image.load( url.path() ) ) {
00392     KMessageBox::error( this,
00393                         i18nc( "@info", "Unable to load image <filename>%1</filename>.", url.path() ) );
00394     return;
00395   }
00396   QFileInfo fi( url.path() );
00397   QString imageName = fi.baseName().isEmpty() ? QLatin1String( "image.png" )
00398                                               : fi.baseName() + QLatin1String( ".png" );
00399   d->addImageHelper( imageName, image );
00400 }
00401 
00402 void TextEdit::loadImage ( const QImage& image, const QString& matchName, const QString& resourceName )
00403 {
00404   QSet<int> cursorPositionsToSkip;
00405   QTextBlock currentBlock = document()->begin();
00406   QTextBlock::iterator it;
00407   while ( currentBlock.isValid() ) {
00408     for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
00409       QTextFragment fragment = it.fragment();
00410       if ( fragment.isValid() ) {
00411         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
00412         if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
00413           int pos = fragment.position();
00414           if ( !cursorPositionsToSkip.contains( pos ) ) {
00415             QTextCursor cursor( document() );
00416             cursor.setPosition( pos );
00417             cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
00418             cursor.removeSelectedText();
00419             document()->addResource( QTextDocument::ImageResource, QUrl( resourceName ), QVariant( image ) );
00420             cursor.insertImage( resourceName );
00421 
00422             // The textfragment iterator is now invalid, restart from the beginning
00423             // Take care not to replace the same fragment again, or we would be in an infinite loop.
00424             cursorPositionsToSkip.insert( pos );
00425             it = currentBlock.begin();
00426           }
00427         }
00428       }
00429     }
00430     currentBlock = currentBlock.next();
00431   }
00432 }
00433 
00434 void TextEditPrivate::addImageHelper( const QString &imageName, const QImage &image )
00435 {
00436   QString imageNameToAdd = imageName;
00437   QTextDocument *document = q->document();
00438 
00439   // determine the imageNameToAdd
00440   int imageNumber = 1;
00441   while ( mImageNames.contains( imageNameToAdd ) ) {
00442     QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
00443     if ( qv == image ) {
00444       // use the same name
00445       break;
00446     }
00447     int firstDot = imageName.indexOf( QLatin1Char( '.' ) );
00448     if ( firstDot == -1 )
00449       imageNameToAdd = imageName + QString::number( imageNumber++ );
00450     else
00451       imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
00452                        imageName.mid( firstDot );
00453   }
00454 
00455   if ( !mImageNames.contains( imageNameToAdd ) ) {
00456     document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
00457     mImageNames << imageNameToAdd;
00458   }
00459   q->textCursor().insertImage( imageNameToAdd );
00460   q->enableRichTextMode();
00461 }
00462 
00463 ImageWithNameList TextEdit::imagesWithName() const
00464 {
00465   ImageWithNameList retImages;
00466   QStringList seenImageNames;
00467   QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
00468   foreach( const QTextImageFormat &imageFormat, imageFormats ) {
00469     if ( !seenImageNames.contains( imageFormat.name() ) ) {
00470       QVariant resourceData = document()->resource( QTextDocument::ImageResource, QUrl( imageFormat.name() ) );
00471       QImage image = qvariant_cast<QImage>( resourceData );
00472       QString name = imageFormat.name();
00473       ImageWithNamePtr newImage( new ImageWithName );
00474       newImage->image = image;
00475       newImage->name = name;
00476       retImages.append( newImage );
00477       seenImageNames.append( imageFormat.name() );
00478     }
00479   }
00480   return retImages;
00481 }
00482 
00483 QList< QSharedPointer<EmbeddedImage> > TextEdit::embeddedImages() const
00484 {
00485   ImageWithNameList normalImages = imagesWithName();
00486   QList< QSharedPointer<EmbeddedImage> > retImages;
00487   foreach( const ImageWithNamePtr &normalImage, normalImages ) {
00488     QBuffer buffer;
00489     buffer.open( QIODevice::WriteOnly );
00490     normalImage->image.save( &buffer, "PNG" );
00491 
00492     qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
00493     QSharedPointer<EmbeddedImage> embeddedImage( new EmbeddedImage() );
00494     retImages.append( embeddedImage );
00495     embeddedImage->image = KMime::Codec::codecForName( "base64" )->encode( buffer.buffer() );
00496     embeddedImage->imageName = normalImage->name;
00497     embeddedImage->contentID = QString( QLatin1String( "%1@KDE" ) ).arg( qrand() );
00498   }
00499   return retImages;
00500 }
00501 
00502 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats() const
00503 {
00504   QTextDocument *doc = q->document();
00505   QList<QTextImageFormat> retList;
00506 
00507   QTextBlock currentBlock = doc->begin();
00508   while ( currentBlock.isValid() ) {
00509     QTextBlock::iterator it;
00510     for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
00511       QTextFragment fragment = it.fragment();
00512       if ( fragment.isValid() ) {
00513         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
00514         if ( imageFormat.isValid() ) {
00515           retList.append( imageFormat );
00516         }
00517       }
00518     }
00519     currentBlock = currentBlock.next();
00520   }
00521   return retList;
00522 }
00523 
00524 void TextEditPrivate::_k_slotAddImage()
00525 {
00526   QPointer<KFileDialog> fdlg = new KFileDialog( QString(), QString(), q );
00527   fdlg->setOperationMode( KFileDialog::Other );
00528   fdlg->setCaption( i18n("Add Image") );
00529   fdlg->okButton()->setGuiItem( KGuiItem( i18n("&Add"), QLatin1String( "document-open" ) ) );
00530   fdlg->setMode( KFile::Files );
00531   if ( fdlg->exec() != KDialog::Accepted ) {
00532     delete fdlg;
00533     return;
00534   }
00535 
00536   const KUrl::List files = fdlg->selectedUrls();
00537   foreach ( const KUrl& url, files ) {
00538     q->addImage( url );
00539   }
00540   delete fdlg;
00541 }
00542 
00543 void KPIMTextEdit::TextEdit::enableImageActions()
00544 {
00545   d->imageSupportEnabled = true;
00546 }
00547 
00548 QByteArray KPIMTextEdit::TextEdit::imageNamesToContentIds( const QByteArray &htmlBody, const KPIMTextEdit::ImageList &imageList )
00549 {
00550   QByteArray result = htmlBody;
00551   if ( imageList.size() > 0 ) {
00552     foreach( const QSharedPointer<EmbeddedImage> &image, imageList ) {
00553       const QString newImageName = QLatin1String( "cid:" ) + image->contentID;
00554       QByteArray quote( "\"" );
00555       result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
00556                       QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
00557     }
00558   }
00559   return result;
00560 }
00561 
00562 void TextEdit::insertImage( const QImage &image, const QFileInfo&fileInfo )
00563 {
00564   QString imageName = fileInfo.baseName().isEmpty() ? i18nc( "Start of the filename for an image", "image" ) : fileInfo.baseName();
00565   d->addImageHelper( imageName, image );
00566 }
00567 
00568 void TextEdit::insertFromMimeData( const QMimeData *source )
00569 {
00570   // Add an image if that is on the clipboard
00571   if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
00572     QImage image = qvariant_cast<QImage>( source->imageData() );
00573     QFileInfo fi( source->text() );
00574     insertImage( image, fi );
00575     return;
00576   }
00577 
00578   // Attempt to paste HTML contents into the text edit in plain text mode,
00579   // prevent this and prevent plain text instead.
00580   if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
00581     if ( source->hasText() ) {
00582       insertPlainText( source->text() );
00583       return;
00584     }
00585   }
00586 
00587   KRichTextWidget::insertFromMimeData( source );
00588 }
00589 
00590 bool KPIMTextEdit::TextEdit::canInsertFromMimeData( const QMimeData *source ) const
00591 {
00592   if ( source->hasHtml() && textMode() == KRichTextEdit::Rich )
00593     return true;
00594   if ( source->hasText() )
00595     return true;
00596   if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled )
00597     return true;
00598 
00599   return KRichTextWidget::canInsertFromMimeData( source );
00600 }
00601 
00602 bool TextEdit::isFormattingUsed() const
00603 {
00604   if ( textMode() == Plain )
00605     return false;
00606 
00607   return TextUtils::containsFormatting( document() );
00608 }
00609 
00610 void TextEditPrivate::_k_slotDeleteLine()
00611 {
00612   q->deleteCurrentLine();
00613 }
00614 
00615 void TextEdit::deleteCurrentLine()
00616 {
00617   QTextCursor cursor = textCursor();
00618   QTextBlock block = cursor.block();
00619   const QTextLayout* layout = block.layout();
00620 
00621   // The current text block can have several lines due to word wrapping.
00622   // Search the line the cursor is in, and then delete it.
00623   for ( int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
00624     QTextLine line = layout->lineAt( lineNumber );
00625     const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
00626     const bool oneLineBlock = ( layout->lineCount() == 1 );
00627     const int startOfLine = block.position() + line.textStart();
00628     int endOfLine = block.position() + line.textStart() + line.textLength();
00629     if ( !lastLineInBlock )
00630       endOfLine -= 1;
00631 
00632     // Found the line where the cursor is in
00633     if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
00634       int deleteStart = startOfLine;
00635       int deleteLength = line.textLength();
00636       if ( oneLineBlock )
00637         deleteLength++; // The trailing newline
00638 
00639       // When deleting the last line in the document,
00640       // remove the newline of the line before the last line instead
00641       if ( deleteStart + deleteLength >= document()->characterCount() &&
00642            deleteStart > 0 )
00643         deleteStart--;
00644 
00645       cursor.beginEditBlock();
00646       cursor.setPosition( deleteStart );
00647       cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
00648       cursor.removeSelectedText();
00649       cursor.endEditBlock();
00650       return;
00651     }
00652   }
00653 
00654 }
00655 
00656 
00657 #include "textedit.moc"

KPIMTextedit Library

Skip menu "KPIMTextedit Library"
  • Main Page
  • Namespace List
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal