• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • 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 #ifndef QT_NO_CURSOR
00177   if ( o == this )
00178     KCursor::autoHideEventFilter( o, e );
00179 #endif
00180   return KRichTextWidget::eventFilter( o, e );
00181 }
00182 
00183 void TextEditPrivate::init()
00184 {
00185   q->setSpellInterface( q );
00186   // We tell the KRichTextWidget to enable spell checking, because only then it will
00187   // call createHighlighter() which will create our own highlighter which also
00188   // does quote highlighting.
00189   // However, *our* spellchecking is still disabled. Our own highlighter only
00190   // cares about our spellcheck status, it will not highlight missspelled words
00191   // if our spellchecking is disabled.
00192   // See also KEMailQuotingHighlighter::highlightBlock().
00193   spellCheckingEnabled = false;
00194   q->setCheckSpellingEnabledInternal( true );
00195 
00196 #ifndef QT_NO_CURSOR
00197   KCursor::setAutoHideCursor( q, true, true );
00198 #endif
00199   q->installEventFilter( q );
00200 }
00201 
00202 QString TextEdit::configFile() const
00203 {
00204    return d->configFile;
00205 }
00206 
00207 
00208 void TextEdit::keyPressEvent ( QKeyEvent * e )
00209 {
00210   if ( e->key() ==  Qt::Key_Return ) {
00211     QTextCursor cursor = textCursor();
00212     int oldPos = cursor.position();
00213     int blockPos = cursor.block().position();
00214 
00215     //selection all the line.
00216     cursor.movePosition( QTextCursor::StartOfBlock );
00217     cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00218     QString lineText = cursor.selectedText();
00219     if ( ( ( oldPos -blockPos )  > 0 ) &&
00220          ( ( oldPos-blockPos ) < int( lineText.length() ) ) ) {
00221       bool isQuotedLine = false;
00222       int bot = 0; // bot = begin of text after quote indicators
00223       while ( bot < lineText.length() ) {
00224         if( ( lineText[bot] == QChar::fromAscii( '>' ) ) ||
00225             ( lineText[bot] == QChar::fromAscii( '|' ) ) ) {
00226           isQuotedLine = true;
00227           ++bot;
00228         }
00229         else if ( lineText[bot].isSpace() ) {
00230           ++bot;
00231         }
00232         else {
00233           break;
00234         }
00235       }
00236       KRichTextWidget::keyPressEvent( e );
00237       // duplicate quote indicators of the previous line before the new
00238       // line if the line actually contained text (apart from the quote
00239       // indicators) and the cursor is behind the quote indicators
00240       if ( isQuotedLine
00241            && ( bot != lineText.length() )
00242            && ( ( oldPos-blockPos ) >= int( bot ) ) ) {
00243         // The cursor position might have changed unpredictably if there was selected
00244         // text which got replaced by a new line, so we query it again:
00245         cursor.movePosition( QTextCursor::StartOfBlock );
00246         cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00247         QString newLine = cursor.selectedText();
00248 
00249         // remove leading white space from the new line and instead
00250         // add the quote indicators of the previous line
00251         int leadingWhiteSpaceCount = 0;
00252         while ( ( leadingWhiteSpaceCount < newLine.length() )
00253                   && newLine[leadingWhiteSpaceCount].isSpace() ) {
00254           ++leadingWhiteSpaceCount;
00255         }
00256         newLine = newLine.replace( 0, leadingWhiteSpaceCount,
00257                                    lineText.left( bot ) );
00258         cursor.insertText( newLine );
00259         //cursor.setPosition( cursor.position() + 2);
00260         cursor.movePosition( QTextCursor::StartOfBlock );
00261         setTextCursor( cursor );
00262       }
00263     }
00264     else
00265       KRichTextWidget::keyPressEvent( e );
00266   }
00267   else
00268   {
00269     KRichTextWidget::keyPressEvent( e );
00270   }
00271 }
00272 
00273 
00274 bool TextEdit::isSpellCheckingEnabled() const
00275 {
00276   return d->spellCheckingEnabled;
00277 }
00278 
00279 void TextEdit::setSpellCheckingEnabled( bool enable )
00280 {
00281   EMailQuoteHighlighter *hlighter =
00282       dynamic_cast<EMailQuoteHighlighter*>( highlighter() );
00283   if ( hlighter )
00284     hlighter->toggleSpellHighlighting( enable );
00285 
00286   d->spellCheckingEnabled = enable;
00287   emit checkSpellingChanged( enable );
00288 }
00289 
00290 bool TextEdit::shouldBlockBeSpellChecked( const QString& block ) const
00291 {
00292   return !isLineQuoted( block );
00293 }
00294 
00295 bool KPIMTextEdit::TextEdit::isLineQuoted( const QString& line ) const
00296 {
00297   return quoteLength( line ) > 0;
00298 }
00299 
00300 int KPIMTextEdit::TextEdit::quoteLength( const QString& line ) const
00301 {
00302   bool quoteFound = false;
00303   int startOfText = -1;
00304   for ( int i = 0; i < line.length(); i++ ) {
00305     if ( line[i] == QLatin1Char( '>' ) || line[i] == QLatin1Char( '|' ) )
00306       quoteFound = true;
00307     else if ( line[i] != QLatin1Char( ' ' ) ) {
00308       startOfText = i;
00309       break;
00310     }
00311   }
00312   if ( quoteFound ) {
00313     if ( startOfText == -1 )
00314       startOfText = line.length() - 1;
00315     return startOfText;
00316   }
00317   else
00318     return 0;
00319 }
00320 
00321 const QString KPIMTextEdit::TextEdit::defaultQuoteSign() const
00322 {
00323   return QLatin1String( "> " );
00324 }
00325 
00326 void TextEdit::createHighlighter()
00327 {
00328   EMailQuoteHighlighter *emailHighLighter =
00329       new EMailQuoteHighlighter( this );
00330 
00331   setHighlighterColors( emailHighLighter );
00332 
00333   //TODO change config
00334   KRichTextWidget::setHighlighter( emailHighLighter );
00335 
00336   if ( !spellCheckingLanguage().isEmpty() )
00337     setSpellCheckingLanguage( spellCheckingLanguage() );
00338   setSpellCheckingEnabled( isSpellCheckingEnabled() );
00339 }
00340 
00341 void TextEdit::setHighlighterColors( EMailQuoteHighlighter *highlighter )
00342 {
00343   Q_UNUSED( highlighter );
00344 }
00345 
00346 QString TextEdit::toWrappedPlainText() const
00347 {
00348   QString temp;
00349   QTextDocument* doc = document();
00350   QTextBlock block = doc->begin();
00351   while ( block.isValid() ) {
00352     QTextLayout* layout = block.layout();
00353     for ( int i = 0; i < layout->lineCount(); i++ ) {
00354       QTextLine line = layout->lineAt( i );
00355       temp += block.text().mid( line.textStart(), line.textLength() ) + QLatin1Char( '\n' );
00356     }
00357     block = block.next();
00358   }
00359 
00360   // Remove the last superfluous newline added above
00361   if ( temp.endsWith( QLatin1Char( '\n' ) ) )
00362     temp.chop( 1 );
00363 
00364   d->fixupTextEditString( temp );
00365   return temp;
00366 }
00367 
00368 QString TextEdit::toCleanPlainText() const
00369 {
00370   QString temp = toPlainText();
00371   d->fixupTextEditString( temp );
00372   return temp;
00373 }
00374 
00375 void TextEdit::createActions( KActionCollection *actionCollection )
00376 {
00377   KRichTextWidget::createActions( actionCollection );
00378 
00379   if ( d->imageSupportEnabled ) {
00380     d->actionAddImage = new KAction( KIcon( QLatin1String( "insert-image" ) ),
00381                                     i18n( "Add Image" ), this );
00382     actionCollection->addAction( QLatin1String( "add_image" ), d->actionAddImage );
00383     connect( d->actionAddImage, SIGNAL(triggered(bool) ), SLOT( _k_slotAddImage() ) );
00384   }
00385 
00386   d->actionDeleteLine = new KAction( i18n( "Delete Line" ), this );
00387   d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
00388   actionCollection->addAction( QLatin1String( "delete_line" ), d->actionDeleteLine );
00389   connect( d->actionDeleteLine, SIGNAL(triggered(bool)), SLOT(_k_slotDeleteLine()) );
00390 }
00391 
00392 void TextEdit::addImage( const KUrl &url )
00393 {
00394   QImage image;
00395   if ( !image.load( url.path() ) ) {
00396     KMessageBox::error( this,
00397                         i18nc( "@info", "Unable to load image <filename>%1</filename>.", url.path() ) );
00398     return;
00399   }
00400   QFileInfo fi( url.path() );
00401   QString imageName = fi.baseName().isEmpty() ? QLatin1String( "image.png" )
00402                                               : fi.baseName() + QLatin1String( ".png" );
00403   d->addImageHelper( imageName, image );
00404 }
00405 
00406 void TextEdit::loadImage ( const QImage& image, const QString& matchName, const QString& resourceName )
00407 {
00408   QSet<int> cursorPositionsToSkip;
00409   QTextBlock currentBlock = document()->begin();
00410   QTextBlock::iterator it;
00411   while ( currentBlock.isValid() ) {
00412     for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
00413       QTextFragment fragment = it.fragment();
00414       if ( fragment.isValid() ) {
00415         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
00416         if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
00417           int pos = fragment.position();
00418           if ( !cursorPositionsToSkip.contains( pos ) ) {
00419             QTextCursor cursor( document() );
00420             cursor.setPosition( pos );
00421             cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
00422             cursor.removeSelectedText();
00423             document()->addResource( QTextDocument::ImageResource, QUrl( resourceName ), QVariant( image ) );
00424             cursor.insertImage( resourceName );
00425 
00426             // The textfragment iterator is now invalid, restart from the beginning
00427             // Take care not to replace the same fragment again, or we would be in an infinite loop.
00428             cursorPositionsToSkip.insert( pos );
00429             it = currentBlock.begin();
00430           }
00431         }
00432       }
00433     }
00434     currentBlock = currentBlock.next();
00435   }
00436 }
00437 
00438 void TextEditPrivate::addImageHelper( const QString &imageName, const QImage &image )
00439 {
00440   QString imageNameToAdd = imageName;
00441   QTextDocument *document = q->document();
00442 
00443   // determine the imageNameToAdd
00444   int imageNumber = 1;
00445   while ( mImageNames.contains( imageNameToAdd ) ) {
00446     QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
00447     if ( qv == image ) {
00448       // use the same name
00449       break;
00450     }
00451     int firstDot = imageName.indexOf( QLatin1Char( '.' ) );
00452     if ( firstDot == -1 )
00453       imageNameToAdd = imageName + QString::number( imageNumber++ );
00454     else
00455       imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
00456                        imageName.mid( firstDot );
00457   }
00458 
00459   if ( !mImageNames.contains( imageNameToAdd ) ) {
00460     document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
00461     mImageNames << imageNameToAdd;
00462   }
00463   q->textCursor().insertImage( imageNameToAdd );
00464   q->enableRichTextMode();
00465 }
00466 
00467 ImageWithNameList TextEdit::imagesWithName() const
00468 {
00469   ImageWithNameList retImages;
00470   QStringList seenImageNames;
00471   QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
00472   foreach( const QTextImageFormat &imageFormat, imageFormats ) {
00473     if ( !seenImageNames.contains( imageFormat.name() ) ) {
00474       QVariant resourceData = document()->resource( QTextDocument::ImageResource, QUrl( imageFormat.name() ) );
00475       QImage image = qvariant_cast<QImage>( resourceData );
00476       QString name = imageFormat.name();
00477       ImageWithNamePtr newImage( new ImageWithName );
00478       newImage->image = image;
00479       newImage->name = name;
00480       retImages.append( newImage );
00481       seenImageNames.append( imageFormat.name() );
00482     }
00483   }
00484   return retImages;
00485 }
00486 
00487 QList< QSharedPointer<EmbeddedImage> > TextEdit::embeddedImages() const
00488 {
00489   ImageWithNameList normalImages = imagesWithName();
00490   QList< QSharedPointer<EmbeddedImage> > retImages;
00491   foreach( const ImageWithNamePtr &normalImage, normalImages ) {
00492     QBuffer buffer;
00493     buffer.open( QIODevice::WriteOnly );
00494     normalImage->image.save( &buffer, "PNG" );
00495 
00496     qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
00497     QSharedPointer<EmbeddedImage> embeddedImage( new EmbeddedImage() );
00498     retImages.append( embeddedImage );
00499     embeddedImage->image = KMime::Codec::codecForName( "base64" )->encode( buffer.buffer() );
00500     embeddedImage->imageName = normalImage->name;
00501     embeddedImage->contentID = QString( QLatin1String( "%1@KDE" ) ).arg( qrand() );
00502   }
00503   return retImages;
00504 }
00505 
00506 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats() const
00507 {
00508   QTextDocument *doc = q->document();
00509   QList<QTextImageFormat> retList;
00510 
00511   QTextBlock currentBlock = doc->begin();
00512   while ( currentBlock.isValid() ) {
00513     QTextBlock::iterator it;
00514     for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
00515       QTextFragment fragment = it.fragment();
00516       if ( fragment.isValid() ) {
00517         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
00518         if ( imageFormat.isValid() ) {
00519           retList.append( imageFormat );
00520         }
00521       }
00522     }
00523     currentBlock = currentBlock.next();
00524   }
00525   return retList;
00526 }
00527 
00528 void TextEditPrivate::_k_slotAddImage()
00529 {
00530   QPointer<KFileDialog> fdlg = new KFileDialog( QString(), QString(), q );
00531   fdlg->setOperationMode( KFileDialog::Other );
00532   fdlg->setCaption( i18n("Add Image") );
00533   fdlg->okButton()->setGuiItem( KGuiItem( i18n("&Add"), QLatin1String( "document-open" ) ) );
00534   fdlg->setMode( KFile::Files );
00535   if ( fdlg->exec() != KDialog::Accepted ) {
00536     delete fdlg;
00537     return;
00538   }
00539 
00540   const KUrl::List files = fdlg->selectedUrls();
00541   foreach ( const KUrl& url, files ) {
00542     q->addImage( url );
00543   }
00544   delete fdlg;
00545 }
00546 
00547 void KPIMTextEdit::TextEdit::enableImageActions()
00548 {
00549   d->imageSupportEnabled = true;
00550 }
00551 
00552 bool KPIMTextEdit::TextEdit::isEnableImageActions() const
00553 {
00554   return d->imageSupportEnabled;
00555 }
00556 
00557 QByteArray KPIMTextEdit::TextEdit::imageNamesToContentIds( const QByteArray &htmlBody, const KPIMTextEdit::ImageList &imageList )
00558 {
00559   QByteArray result = htmlBody;
00560   if ( imageList.size() > 0 ) {
00561     foreach( const QSharedPointer<EmbeddedImage> &image, imageList ) {
00562       const QString newImageName = QLatin1String( "cid:" ) + image->contentID;
00563       QByteArray quote( "\"" );
00564       result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
00565                       QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
00566     }
00567   }
00568   return result;
00569 }
00570 
00571 void TextEdit::insertImage( const QImage &image, const QFileInfo&fileInfo )
00572 {
00573   QString imageName = fileInfo.baseName().isEmpty() ? i18nc( "Start of the filename for an image", "image" ) : fileInfo.baseName();
00574   d->addImageHelper( imageName, image );
00575 }
00576 
00577 void TextEdit::insertFromMimeData( const QMimeData *source )
00578 {
00579   // Add an image if that is on the clipboard
00580   if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
00581     QImage image = qvariant_cast<QImage>( source->imageData() );
00582     QFileInfo fi( source->text() );
00583     insertImage( image, fi );
00584     return;
00585   }
00586 
00587   // Attempt to paste HTML contents into the text edit in plain text mode,
00588   // prevent this and prevent plain text instead.
00589   if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
00590     if ( source->hasText() ) {
00591       insertPlainText( source->text() );
00592       return;
00593     }
00594   }
00595 
00596   KRichTextWidget::insertFromMimeData( source );
00597 }
00598 
00599 bool KPIMTextEdit::TextEdit::canInsertFromMimeData( const QMimeData *source ) const
00600 {
00601   if ( source->hasHtml() && textMode() == KRichTextEdit::Rich )
00602     return true;
00603   if ( source->hasText() )
00604     return true;
00605   if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled )
00606     return true;
00607 
00608   return KRichTextWidget::canInsertFromMimeData( source );
00609 }
00610 
00611 bool TextEdit::isFormattingUsed() const
00612 {
00613   if ( textMode() == Plain )
00614     return false;
00615 
00616   return TextUtils::containsFormatting( document() );
00617 }
00618 
00619 void TextEditPrivate::_k_slotDeleteLine()
00620 {
00621   q->deleteCurrentLine();
00622 }
00623 
00624 void TextEdit::deleteCurrentLine()
00625 {
00626   QTextCursor cursor = textCursor();
00627   QTextBlock block = cursor.block();
00628   const QTextLayout* layout = block.layout();
00629 
00630   // The current text block can have several lines due to word wrapping.
00631   // Search the line the cursor is in, and then delete it.
00632   for ( int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
00633     QTextLine line = layout->lineAt( lineNumber );
00634     const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
00635     const bool oneLineBlock = ( layout->lineCount() == 1 );
00636     const int startOfLine = block.position() + line.textStart();
00637     int endOfLine = block.position() + line.textStart() + line.textLength();
00638     if ( !lastLineInBlock )
00639       endOfLine -= 1;
00640 
00641     // Found the line where the cursor is in
00642     if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
00643       int deleteStart = startOfLine;
00644       int deleteLength = line.textLength();
00645       if ( oneLineBlock )
00646         deleteLength++; // The trailing newline
00647 
00648       // When deleting the last line in the document,
00649       // remove the newline of the line before the last line instead
00650       if ( deleteStart + deleteLength >= document()->characterCount() &&
00651            deleteStart > 0 )
00652         deleteStart--;
00653 
00654       cursor.beginEditBlock();
00655       cursor.setPosition( deleteStart );
00656       cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
00657       cursor.removeSelectedText();
00658       cursor.endEditBlock();
00659       return;
00660     }
00661   }
00662 
00663 }
00664 
00665 
00666 #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
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • 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.3
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