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

KBlog Client Library

movabletype.cpp

00001 /*
00002   This file is part of the kblog library.
00003 
00004   Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00005   Copyright (c) 2006-2009 Christian Weilbach <christian_weilbach@web.de>
00006   Copyright (c) 2007-2008 Mike Arthur <mike@mikearthur.co.uk>
00007 
00008   This library is free software; you can redistribute it and/or
00009   modify it under the terms of the GNU Library General Public
00010   License as published by the Free Software Foundation; either
00011   version 2 of the License, or (at your option) any later version.
00012 
00013   This library is distributed in the hope that it will be useful,
00014   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016   Library General Public License for more details.
00017 
00018   You should have received a copy of the GNU Library General Public License
00019   along with this library; see the file COPYING.LIB.  If not, write to
00020   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021   Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "movabletype.h"
00025 #include "movabletype_p.h"
00026 #include "blogpost.h"
00027 
00028 #include <kxmlrpcclient/client.h>
00029 #include <kio/job.h>
00030 
00031 #include <KDebug>
00032 #include <KLocale>
00033 #include <KDateTime>
00034 
00035 #include <QtCore/QStringList>
00036 
00037 using namespace KBlog;
00038 
00039 MovableType::MovableType( const KUrl &server, QObject *parent )
00040   : MetaWeblog( server, *new MovableTypePrivate, parent )
00041 {
00042   kDebug();
00043 }
00044 
00045 MovableType::MovableType( const KUrl &server, MovableTypePrivate &dd,
00046                         QObject *parent )
00047   : MetaWeblog( server, dd, parent )
00048 {
00049   kDebug();
00050 }
00051 
00052 MovableType::~MovableType()
00053 {
00054   kDebug();
00055 }
00056 
00057 QString MovableType::interfaceName() const
00058 {
00059   return QLatin1String( "Movable Type" );
00060 }
00061 
00062 void MovableType::listRecentPosts( int number )
00063 {
00064     Q_D( MovableType );
00065     kDebug();
00066     QList<QVariant> args( d->defaultArgs( blogId() ) );
00067     args << QVariant( number );
00068     d->mXmlRpcClient->call(
00069       "metaWeblog.getRecentPosts", args,
00070       this, SLOT(slotListRecentPosts(const QList<QVariant>&,const QVariant&)),
00071       this, SLOT(slotError(int,const QString&,const QVariant&)),
00072       QVariant( number ) );
00073 }
00074 
00075 void MovableType::listTrackBackPings( KBlog::BlogPost *post )
00076 {
00077   Q_D( MovableType );
00078   kDebug();
00079   QList<QVariant> args;
00080   args << QVariant( post->postId() );
00081   unsigned int i = d->mCallCounter++;
00082   d->mCallMap[ i ] = post;
00083   d->mXmlRpcClient->call(
00084     "mt.getTrackbackPings", args,
00085     this, SLOT(slotListTrackbackPings(const QList<QVariant>&,const QVariant&)),
00086     this, SLOT(slotError(int,const QString&,const QVariant&)),
00087     QVariant( i ) );
00088 }
00089 
00090 void MovableType::fetchPost( BlogPost *post )
00091 {
00092   Q_D( MovableType );
00093   kDebug();
00094   d->loadCategories();
00095   if ( d->mCategoriesList.isEmpty() && post->categories( ).count() ) {
00096     d->mFetchPostCache << post;
00097     if ( d->mFetchPostCache.count() ) {
00098       // we are already trying to fetch another post, so we don't need to start
00099       // another listCategories() job
00100       return;
00101     }
00102 
00103     connect( this, SIGNAL( listedCategories( const QList<QMap<QString,QString> >& ) )
00104         , this, SLOT( slotTriggerFetchPost() ) );
00105     listCategories();
00106   } else {
00107     MetaWeblog::fetchPost( post );
00108   }
00109 }
00110 
00111 void MovableType::createPost( BlogPost *post )
00112 {
00113   // reimplemented because we do this:
00114   // http://comox.textdrive.com/pipermail/wp-testers/2005-July/000284.html
00115   kDebug();
00116   Q_D( MovableType );
00117 
00118   // we need mCategoriesList to be loaded first, since we cannot use the post->categories()
00119   // names later, but we need to map them to categoryId of the blog
00120   d->loadCategories();
00121   if(d->mCategoriesList.isEmpty()&&!post->categories().isEmpty()){
00122     kDebug() << "No categories in the cache yet. Have to fetch them first.";
00123     d->mCreatePostCache << post;
00124     connect(this,SIGNAL(listedCategories(const QList<QMap<QString,QString> >&)),
00125             this,SLOT(slotTriggerCreatePost()));
00126     listCategories();
00127   }
00128   else {
00129     bool publish = post->isPrivate();
00130     // If we do setPostCategories() later than we disable publishing first.
00131     if( !post->categories().isEmpty() ){
00132       post->setPrivate( true );
00133       if ( d->mSilentCreationList.contains( post ) ) {
00134         kDebug()<< "Post already in mSilentCreationList, this *should* never happen!";
00135       } else {
00136         d->mSilentCreationList << post;
00137       }
00138     }
00139     MetaWeblog::createPost( post );
00140     // HACK: uuh this a bit ugly now... reenable the original publish argument,
00141     // since createPost should have parsed now
00142     post->setPrivate(publish);
00143   }
00144 }
00145 
00146 void MovableType::modifyPost( BlogPost *post )
00147 {
00148   // reimplemented because we do this:
00149   // http://comox.textdrive.com/pipermail/wp-testers/2005-July/000284.html
00150   kDebug();
00151   Q_D( MovableType );
00152 
00153   // we need mCategoriesList to be loaded first, since we cannot use the post->categories()
00154   // names later, but we need to map them to categoryId of the blog
00155   d->loadCategories();
00156   if(d->mCategoriesList.isEmpty() && !post->categories().isEmpty()){
00157     kDebug() << "No categories in the cache yet. Have to fetch them first.";
00158     d->mModifyPostCache << post;
00159     connect(this,SIGNAL(listedCategories(const QList<QMap<QString,QString> >&)),
00160             this,SLOT(slotTriggerModifyPost()));
00161     listCategories();
00162   }
00163   else {
00164     MetaWeblog::modifyPost( post );
00165   }
00166 }
00167 
00168 void MovableTypePrivate::slotTriggerCreatePost()
00169 {
00170   kDebug();
00171   Q_Q( MovableType );
00172 
00173   q->disconnect(q,SIGNAL(listedCategories(const QList<QMap<QString,QString> >&)),
00174           q,SLOT(slotTriggerCreatePost()));
00175   // now we can recall createPost with the posts from the cache
00176   QList<BlogPost*>::Iterator it = mCreatePostCache.begin();
00177   QList<BlogPost*>::Iterator end = mCreatePostCache.end();
00178   for ( ; it!=end; it++ ) {
00179     q->createPost( *it );
00180   }
00181   mCreatePostCache.clear();
00182 }
00183 
00184 void MovableTypePrivate::slotTriggerModifyPost()
00185 {
00186   kDebug();
00187   Q_Q( MovableType );
00188 
00189   q->disconnect(q,SIGNAL(listedCategories(const QList<QMap<QString,QString> >&)),
00190           q,SLOT(slotTriggerModifyPost()));
00191   // now we can recall createPost with the posts from the cache
00192   QList<BlogPost*>::Iterator it = mModifyPostCache.begin();
00193   QList<BlogPost*>::Iterator end = mModifyPostCache.end();
00194   for ( ; it!=end; it++ ) {
00195     q->modifyPost( *it );
00196   }
00197   mModifyPostCache.clear();
00198 }
00199 
00200 void MovableTypePrivate::slotTriggerFetchPost()
00201 {
00202   kDebug();
00203   Q_Q( MovableType );
00204 
00205   q->disconnect( q,SIGNAL( listedCategories( const QList<QMap<QString,QString> >& ) ),
00206       q,SLOT( slotTriggerFetchPost() ) );
00207   QList<BlogPost*>::Iterator it = mFetchPostCache.begin();
00208   QList<BlogPost*>::Iterator end = mFetchPostCache.end();
00209   for ( ; it!=end; it++ ) {
00210     q->fetchPost( *it );
00211   }
00212   mFetchPostCache.clear();
00213 }
00214 
00215 
00216 MovableTypePrivate::MovableTypePrivate()
00217 {
00218   kDebug();
00219 }
00220 
00221 MovableTypePrivate::~MovableTypePrivate()
00222 {
00223   kDebug();
00224 }
00225 
00226 void MovableTypePrivate::slotCreatePost( const QList<QVariant> &result, const QVariant &id )
00227 {
00228   Q_Q( MovableType );
00229   // reimplement from Blogger1 to chainload the categories stuff before emit()
00230   kDebug();
00231   KBlog::BlogPost *post = mCallMap[ id.toInt() ];
00232   mCallMap.remove( id.toInt() );
00233 
00234   kDebug();
00235   //array of structs containing ISO.8601
00236   // dateCreated, String userid, String postid, String content;
00237   kDebug () << "TOP:" << result[0].typeName();
00238   if ( result[0].type() != QVariant::String && result[0].type() != QVariant::Int ) {
00239     kError() << "Could not read the postId, not a string or an integer.";
00240     emit q->errorPost( Blogger1::ParsingError,
00241                           i18n( "Could not read the postId, not a string or an integer." ),
00242                           post );
00243     return;
00244   }
00245   QString serverID;
00246   if ( result[0].type() == QVariant::String ) {
00247     serverID = result[0].toString();
00248   }
00249   if ( result[0].type() == QVariant::Int ) {
00250     serverID = QString( "%1" ).arg( result[0].toInt() );
00251   }
00252   post->setPostId( serverID );
00253   if ( mSilentCreationList.contains(  post ) )
00254   {
00255     // set the categories and publish afterwards
00256     setPostCategories( post, !post->isPrivate() );
00257   } else {
00258     kDebug() << "emitting createdPost()"
00259                 << "for title: \"" << post->title()
00260                 << "\" server id: " << serverID;
00261     post->setStatus( KBlog::BlogPost::Created );
00262     emit q->createdPost( post );
00263   }
00264 }
00265 
00266 void MovableTypePrivate::slotFetchPost( const QList<QVariant> &result, const QVariant &id )
00267 {
00268   Q_Q( MovableType );
00269   kDebug();
00270 
00271   KBlog::BlogPost *post = mCallMap[ id.toInt() ];
00272   mCallMap.remove( id.toInt() );
00273 
00274   //array of structs containing ISO.8601
00275   // dateCreated, String userid, String postid, String content;
00276   kDebug () << "TOP:" << result[0].typeName();
00277   if ( result[0].type() == QVariant::Map && readPostFromMap( post, result[0].toMap() ) ) {
00278   } else {
00279     kError() << "Could not fetch post out of the result from the server.";
00280     post->setError( i18n( "Could not fetch post out of the result from the server." ) );
00281     post->setStatus( BlogPost::Error );
00282     emit q->errorPost( Blogger1::ParsingError,
00283                        i18n( "Could not fetch post out of the result from the server." ), post );
00284   }
00285   if ( post->categories().isEmpty() ) {
00286     QList<QVariant> args( defaultArgs( post->postId() ) );
00287     unsigned int i= mCallCounter++;
00288     mCallMap[ i ] = post;
00289     mXmlRpcClient->call(
00290       "mt.getPostCategories", args,
00291       q, SLOT(slotGetPostCategories(const QList<QVariant>&,const QVariant&)),
00292       q, SLOT(slotError(int, const QString&,const QVariant&)),
00293       QVariant( i ) );
00294   } else {
00295     kDebug() << "Emitting fetchedPost()";
00296     post->setStatus( KBlog::BlogPost::Fetched );
00297     emit q->fetchedPost( post );
00298   }
00299 }
00300 
00301 void MovableTypePrivate::slotModifyPost( const QList<QVariant> &result, const QVariant &id )
00302 {
00303   Q_Q( MovableType );
00304   // reimplement from Blogger1
00305   kDebug();
00306   KBlog::BlogPost *post = mCallMap[ id.toInt() ];
00307   mCallMap.remove( id.toInt() );
00308 
00309   //array of structs containing ISO.8601
00310   // dateCreated, String userid, String postid, String content;
00311   kDebug() << "TOP:" << result[0].typeName();
00312   if ( result[0].type() != QVariant::Bool && result[0].type() != QVariant::Int ) {
00313     kError() << "Could not read the result, not a boolean.";
00314     emit q->errorPost( Blogger1::ParsingError,
00315                           i18n( "Could not read the result, not a boolean." ),
00316                           post );
00317     return;
00318   }
00319   if ( mSilentCreationList.contains( post ) ) {
00320     post->setStatus( KBlog::BlogPost::Created );
00321     mSilentCreationList.removeOne( post );
00322     emit q->createdPost( post );
00323   } else {
00324     if( !post->categories().isEmpty() ){
00325       setPostCategories( post, false );
00326     }
00327   }
00328 }
00329 
00330 void MovableTypePrivate::setPostCategories( BlogPost *post, bool publishAfterCategories )
00331 {
00332   kDebug();
00333   Q_Q( MovableType );
00334 
00335   unsigned int i = mCallCounter++;
00336   mCallMap[ i ] = post;
00337   mPublishAfterCategories[ i ] = publishAfterCategories;
00338   QList<QVariant> catList;
00339   QList<QVariant> args( defaultArgs( post->postId() ) );
00340 
00341   // map the categoryId of the server to the name
00342   QStringList categories = post->categories();
00343   for( int j=0; j<categories.count(); j++ ){
00344      for( int k=0; k<mCategoriesList.count(); k++ ){
00345        if(mCategoriesList[k]["name"]==categories[j]){
00346          kDebug() << "Matched category with name: " << categories[ j ] << " and id: " << mCategoriesList[ k ][ "categoryId" ];
00347          QMap<QString,QVariant> category;
00348          //the first in the QStringList of post->categories()
00349          // is the primary category
00350          category["categoryId"]=mCategoriesList[k]["categoryId"].toInt();
00351          catList<<QVariant( category );
00352          break;
00353        }
00354        if(k==mCategoriesList.count()){
00355          kDebug() << "Couldn't find categoryId for: " << categories[j];
00356        }
00357      }
00358   }
00359   args<<QVariant( catList );
00360 
00361   mXmlRpcClient->call(
00362     "mt.setPostCategories", args,
00363     q, SLOT(slotSetPostCategories(const QList<QVariant>&,const QVariant&)),
00364     q, SLOT(slotError(int, const QString&,const QVariant&)),
00365     QVariant( i ) );
00366 }
00367 
00368 void MovableTypePrivate::slotGetPostCategories(const QList<QVariant>& result,const QVariant& id)
00369 {
00370   kDebug();
00371   Q_Q( MovableType );
00372 
00373   int i = id.toInt();
00374   BlogPost* post = mCallMap[ i ];
00375   mCallMap.remove(i);
00376 
00377   if ( result[ 0 ].type() != QVariant::List ) {
00378     kError() << "Could not read the result, not a list. Category fetching failed! We will still emit fetched post now.";
00379     emit q->errorPost( Blogger1::ParsingError,
00380         i18n( "Could not read the result - is not a list. Category fetching failed." ), post );
00381 
00382     post->setStatus( KBlog::BlogPost::Fetched );
00383     emit q->fetchedPost( post );
00384   } else {
00385     QList<QVariant> categoryList = result[ 0 ].toList();
00386     QList<QString> newCatList;
00387     QList<QVariant>::ConstIterator it = categoryList.constBegin();
00388     QList<QVariant>::ConstIterator end = categoryList.constEnd();
00389     for ( ;it!=end;it++ ) {
00390       newCatList << ( *it ).toMap()[ "categoryName" ].toString();
00391     }
00392     kDebug()<< "categories list: " << newCatList;
00393     post->setCategories( newCatList );
00394     post->setStatus( KBlog::BlogPost::Fetched );
00395     emit q->fetchedPost( post );
00396   }
00397 }
00398 
00399 void MovableTypePrivate::slotSetPostCategories(const QList<QVariant>& result,const QVariant& id)
00400 {
00401   kDebug();
00402   Q_Q( MovableType );
00403 
00404   int i = id.toInt();
00405   BlogPost* post = mCallMap[ i ];
00406   bool publish = mPublishAfterCategories[ i ];
00407   mCallMap.remove(i);
00408   mPublishAfterCategories.remove(i);
00409 
00410   if ( result[0].type() != QVariant::Bool ) {
00411     kError() << "Could not read the result, not a boolean. Category setting failed! We will still publish if now if necessary. ";
00412     emit q->errorPost( Blogger1::ParsingError,
00413                           i18n( "Could not read the result - is not a boolean value. Category setting failed.  Will still publish now if necessary." ),
00414                           post );
00415   }
00416   // Finally publish now, if the post was meant to be published in the beginning.
00417   // The first boolean is necessary to only publish if the post is created, not
00418   // modified.
00419   if( publish && !post->isPrivate() ){
00420     q->modifyPost( post );
00421   }
00422 
00423   // this is the end of the chain then
00424   if ( !publish ) {
00425     if ( mSilentCreationList.contains( post ) ) {
00426       kDebug() << "emitting createdPost() for title: \""
00427               << post->title() << "\"";
00428       post->setStatus( KBlog::BlogPost::Created );
00429       mSilentCreationList.removeOne( post );
00430       emit q->createdPost( post );
00431     } else {
00432       kDebug() << "emitting modifiedPost() for title: \""
00433               << post->title() << "\"";
00434       post->setStatus( KBlog::BlogPost::Modified );
00435       emit q->modifiedPost( post );
00436     }
00437   }
00438 }
00439 
00440 QList<QVariant> MovableTypePrivate::defaultArgs( const QString &id )
00441 {
00442   Q_Q( MovableType );
00443   QList<QVariant> args;
00444   if( !id.isEmpty() ) {
00445     args << QVariant( id );
00446   }
00447   args << QVariant( q->username() )
00448        << QVariant( q->password() );
00449   return args;
00450 }
00451 
00452 bool MovableTypePrivate::readPostFromMap( BlogPost *post, const QMap<QString, QVariant> &postInfo )
00453 {
00454 
00455   // FIXME: integrate error handling
00456   kDebug() << "readPostFromMap()";
00457   if ( !post ) {
00458     return false;
00459   }
00460   QStringList mapkeys = postInfo.keys();
00461   kDebug() << endl << "Keys:" << mapkeys.join( ", " );
00462   kDebug() << endl;
00463 
00464   KDateTime dt =
00465     KDateTime( postInfo["dateCreated"].toDateTime(), KDateTime::UTC );
00466   if ( dt.isValid() && !dt.isNull() ) {
00467     post->setCreationDateTime( dt.toLocalZone() );
00468   }
00469 
00470   dt =
00471     KDateTime( postInfo["lastModified"].toDateTime(), KDateTime::UTC );
00472   if ( dt.isValid() && !dt.isNull() ) {
00473     post->setModificationDateTime( dt.toLocalZone() );
00474   }
00475 
00476   post->setPostId( postInfo["postid"].toString().isEmpty() ? postInfo["postId"].toString() :
00477                    postInfo["postid"].toString() );
00478 
00479   QString title( postInfo["title"].toString() );
00480   QString description( postInfo["description"].toString() );
00481   QStringList categoryIdList = postInfo["categories"].toStringList();
00482   QStringList categories;
00483   // since the metaweblog definition is ambigious, we try different
00484   // category mappings
00485   for ( int i=0; i<categoryIdList.count(); i++ ) {
00486     for ( int k=0; k<mCategoriesList.count(); k++ ) {
00487       if ( mCategoriesList[ k ][ "name" ]==categoryIdList[ i ] ){
00488         categories << mCategoriesList[ k ][ "name" ];
00489       } else if ( mCategoriesList[ k ][ "categoryId" ]==categoryIdList[ i ]) {
00490         categories << mCategoriesList[ k ][ "name" ];
00491       } 
00492     }
00493   }
00494 
00495   //TODO 2 new keys are:
00496   // String mt_convert_breaks, the value for the convert_breaks field
00497   post->setSlug( postInfo["wp_slug"].toString() );
00498   post->setAdditionalContent( postInfo["mt_text_more"].toString() );
00499   post->setTitle( title );
00500   post->setContent( description );
00501   post->setCommentAllowed( (bool)postInfo["mt_allow_comments"].toInt() );
00502   post->setTrackBackAllowed( (bool)postInfo["mt_allow_pings"].toInt() );
00503   post->setSummary( postInfo["mt_excerpt"].toString() );
00504   post->setTags( postInfo["mt_keywords"].toStringList() );
00505   post->setLink( postInfo["link"].toString() );
00506   post->setPermaLink( postInfo["permaLink"].toString() );
00507   QString postStatus = postInfo["post_status"].toString();
00508   if( postStatus != "publish" && !postStatus.isEmpty() ){
00514     post->setPrivate(true);
00515   }
00516   if ( !categories.isEmpty() ){
00517     kDebug() << "Categories:" << categories;
00518     post->setCategories( categories );
00519   }
00520   return true;
00521 }
00522 
00523 void MovableTypePrivate::slotListTrackBackPings(
00524     const QList<QVariant> &result, const QVariant &id )
00525 {
00526   Q_Q( MovableType );
00527   kDebug() << "slotTrackbackPings()";
00528   BlogPost *post = mCallMap[ id.toInt() ];
00529   mCallMap.remove( id.toInt() );
00530   QList<QMap<QString,QString> > trackBackList;
00531   if ( result[0].type() != QVariant::List ) {
00532     kError() << "Could not fetch list of trackback pings out of the"
00533                  << "result from the server.";
00534     emit q->error( MovableType::ParsingError,
00535                    i18n( "Could not fetch list of trackback pings out of the "
00536                          "result from the server." ) );
00537     return;
00538   }
00539   const QList<QVariant> trackBackReceived = result[0].toList();
00540   QList<QVariant>::ConstIterator it = trackBackReceived.begin();
00541   QList<QVariant>::ConstIterator end = trackBackReceived.end();
00542   for ( ; it != end; ++it ) {
00543     QMap<QString,QString> tping;
00544     kDebug() << "MIDDLE:" << ( *it ).typeName();
00545     const QMap<QString, QVariant> trackBackInfo = ( *it ).toMap();
00546     tping[ "title" ] = trackBackInfo[ "pingTitle"].toString();
00547     tping[ "url" ] = trackBackInfo[ "pingURL"].toString();
00548     tping[ "ip" ] = trackBackInfo[ "pingIP"].toString();
00549     trackBackList << tping;
00550   }
00551   kDebug() << "Emitting listedTrackBackPings()";
00552   emit q->listedTrackBackPings( post, trackBackList );
00553 }
00554 
00555 bool MovableTypePrivate::readArgsFromPost( QList<QVariant> *args, const BlogPost &post )
00556 {
00557   //TODO 2 new keys are:
00558   // String mt_convert_breaks, the value for the convert_breaks field
00559   // array mt_tb_ping_urls, the list of TrackBack ping URLs for this entry
00560   if ( !args ) {
00561     return false;
00562   }
00563   QMap<QString, QVariant> map;
00564   map["categories"] = post.categories();
00565   map["description"] = post.content();
00566   if( !post.additionalContent().isEmpty() )
00567     map["mt_text_more"] = post.additionalContent();
00568   map["title"] = post.title();
00569   map["dateCreated"] = post.creationDateTime().dateTime().toUTC();
00570   map["mt_allow_comments"] = (int)post.isCommentAllowed();
00571   map["mt_allow_pings"] = (int)post.isTrackBackAllowed();
00572   map["mt_excerpt"] = post.summary();
00573   map["mt_keywords"] = post.tags().join(",");
00574   //map["mt_tb_ping_urls"] check for that, i think this should only be done on the server.
00575   *args << map;
00576   *args << QVariant( !post.isPrivate() );
00577   return true;
00578 }
00579 
00580 #include "movabletype.moc"

KBlog Client Library

Skip menu "KBlog Client Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • 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.6.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