Sophie

Sophie

distrib > Mandriva > 2010.1 > x86_64 > by-pkgid > 05ea160c3e539040248c38fe5960670d > files > 12

kdepimlibs4-4.4.5-0.2mdv2010.2.src.rpm

Index: akonadi/contact/customfieldmanager.cpp
===================================================================
--- akonadi/contact/customfieldmanager.cpp	(révision 0)
+++ akonadi/contact/customfieldmanager.cpp	(révision 1101153)
@@ -0,0 +1,65 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfieldmanager_p.h"
+
+#include <kconfig.h>
+#include <kconfiggroup.h>
+
+void CustomFieldManager::setGlobalCustomFieldDescriptions( const CustomField::List &customFields )
+{
+  KConfig config( QLatin1String( "akonadi_contactrc" ) );
+  KConfigGroup group( &config, QLatin1String( "GlobalCustomFields" ) );
+
+  group.deleteGroup();
+  foreach ( const CustomField &field, customFields ) {
+    const QString key = field.key();
+    const QString value = CustomField::typeToString( field.type() ) + QLatin1Char( ':' ) + field.title();
+
+    group.writeEntry( key, value );
+  }
+}
+
+CustomField::List CustomFieldManager::globalCustomFieldDescriptions()
+{
+  KConfig config( QLatin1String( "akonadi_contactrc" ) );
+  const KConfigGroup group( &config, QLatin1String( "GlobalCustomFields" ) );
+
+  CustomField::List customFields;
+
+  const QStringList keys = group.keyList();
+  foreach ( const QString &key, keys ) {
+    CustomField field;
+    field.setKey( key );
+    field.setScope( CustomField::GlobalScope );
+
+    const QString value = group.readEntry( key, QString() );
+    const int pos = value.indexOf( QLatin1Char( ':' ) );
+    if ( pos != -1 ) {
+      field.setType( CustomField::stringToType( value.left( pos - 1 ) ) );
+      field.setTitle( value.mid( pos + 1 ) );
+    }
+
+    customFields << field;
+  }
+
+  return customFields;
+}
Index: akonadi/contact/contactmetadata_p.h
===================================================================
--- akonadi/contact/contactmetadata_p.h	(révision 1101152)
+++ akonadi/contact/contactmetadata_p.h	(révision 1101153)
@@ -23,6 +23,7 @@
 #define AKONADI_CONTACTMETADATA_P_H
 
 #include <QtCore/QStringList>
+#include <QtCore/QVariant>
 
 namespace Akonadi
 {
@@ -67,6 +68,29 @@
      */
     int displayNameMode() const;
 
+    /**
+     * Sets the @p descriptions of the custom fields of that contact.
+     *
+     * The description list contains a QVariantMap for each custom field
+     * with the following keys:
+     *   - key   (string) The identifier of the field
+     *   - title (string) The i18n'ed title of the field
+     *   - type  (string) The type description of the field
+     *     Possible values for type description are
+     *       - text
+     *       - numeric
+     *       - boolean
+     *       - date
+     *       - time
+     *       - datetime
+     */
+    void setCustomFieldDescriptions( const QVariantList &descriptions );
+
+    /**
+     * Returns the descriptions of the custom fields of the contact.
+     */
+    QVariantList customFieldDescriptions() const;
+
   private:
     //@cond PRIVATE
     Q_DISABLE_COPY( ContactMetaData )
Index: akonadi/contact/customfields_p.h
===================================================================
--- akonadi/contact/customfields_p.h	(révision 0)
+++ akonadi/contact/customfields_p.h	(révision 1101153)
@@ -0,0 +1,100 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDS_P_H
+#define CUSTOMFIELDS_P_H
+
+#include <QtCore/QList>
+#include <QtCore/QString>
+#include <QtCore/QVariant>
+
+/**
+ * @short A class that represents non-standard contact fields.
+ *
+ * There exists three scopes of fields. To the local scope belong all
+ * custom fields that are defined by the user and that exists only for one
+ * contact. The description for these fields are stored inside ContactMetaData
+ * as custom attribute of the Akonadi item that represents the contact.
+ * To the global scope belong all custom fields that are defined by the user but
+ * shall be available in all contacts of the address book. Their description
+ * is stored by CustomFieldManager in $HOME/.kde/share/config/akonadi_contactrc.
+ * All other custom fields belong to the external scope, they come with import
+ * of contacts from other PIM applications (e.g. further X- entries in vCards).
+ * Their description is created on the fly when editing the custom fields.
+ *
+ * The description of a custom field covers the key, title and type.
+ */
+class CustomField
+{
+  public:
+    typedef QList<CustomField> List;
+
+    enum Type
+    {
+      TextType,
+      NumericType,
+      BooleanType,
+      DateType,
+      TimeType,
+      DateTimeType
+    };
+
+    enum Scope
+    {
+      LocalScope,   ///< Field has been defined by user for one contact
+      GlobalScope,  ///< Field has been defined by user for all contacts
+      ExternalScope ///< Field has been defined by the external data source (e.g. vCard)
+    };
+
+    CustomField();
+    CustomField( const QString &key, const QString &title, Type type, Scope scope );
+
+    static CustomField fromVariantMap( const QVariantMap &map, Scope scope );
+
+    void setKey( const QString &key );
+    QString key() const;
+
+    void setTitle( const QString &title );
+    QString title() const;
+
+    void setType( Type type );
+    Type type() const;
+
+    void setScope( Scope scope );
+    Scope scope() const;
+
+    void setValue( const QString &value );
+    QString value() const;
+
+    QVariantMap toVariantMap() const;
+
+    static QString typeToString( Type type );
+    static Type stringToType( const QString &type );
+
+  private:
+    QString mKey;
+    QString mTitle;
+    Type mType;
+    Scope mScope;
+    QString mValue;
+};
+
+#endif
Index: akonadi/contact/contactviewer.cpp
===================================================================
--- akonadi/contact/contactviewer.cpp	(révision 1101152)
+++ akonadi/contact/contactviewer.cpp	(révision 1101153)
@@ -21,6 +21,9 @@
 
 #include "contactviewer.h"
 
+#include "contactmetadata_p.h"
+#include "contactmetadataattribute_p.h"
+#include "customfieldmanager_p.h"
 #include "textbrowser_p.h"
 
 #include <akonadi/item.h>
@@ -36,7 +39,7 @@
 
 using namespace Akonadi;
 
-static QString contactAsHtml( const KABC::Addressee &contact );
+static QString contactAsHtml( const KABC::Addressee &contact, const QVariantList &customFieldDescriptions );
 
 class ContactViewer::Private
 {
@@ -103,6 +106,7 @@
 
   // always fetch full payload for contacts
   fetchScope().fetchFullPayload();
+  fetchScope().fetchAttribute<ContactMetaDataAttribute>();
 }
 
 ContactViewer::~ContactViewer()
@@ -141,7 +145,23 @@
                                           defaultPixmap );
   }
 
-  d->mBrowser->setHtml( contactAsHtml( d->mCurrentContact ) );
+  // merge the local...
+  ContactMetaData metaData;
+  metaData.load( contactItem );
+
+  QVariantList customFieldDescriptions = metaData.customFieldDescriptions();
+
+  // ... and global custom field descriptions
+  const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
+  foreach ( const CustomField &field, globalCustomFields ) {
+    QVariantMap description;
+    description.insert( QLatin1String( "key" ), field.key() );
+    description.insert( QLatin1String( "title" ), field.title() );
+
+    customFieldDescriptions << description;
+  }
+
+  d->mBrowser->setHtml( contactAsHtml( d->mCurrentContact, customFieldDescriptions ) );
 }
 
 void ContactViewer::itemRemoved()
@@ -149,7 +169,7 @@
   d->mBrowser->clear();
 }
 
-static QString contactAsHtml( const KABC::Addressee &contact )
+static QString contactAsHtml( const KABC::Addressee &contact, const QVariantList &customFieldDescriptions )
 {
   // We'll be building a table to display the vCard in.
   // Each row of the table will be built using this string for its HTML.
@@ -282,9 +302,20 @@
         if ( blacklistedKeys.contains( key ) )
           continue;
 
+        // check whether we have a mapping for the title
         const QMap<QString, QString>::ConstIterator keyIt = titleMap.constFind( key );
-        if ( keyIt != titleMap.constEnd() )
+        if ( keyIt != titleMap.constEnd() ) {
           key = keyIt.value();
+        } else {
+          // check whether it is a custom local field
+          foreach ( const QVariant &description, customFieldDescriptions ) {
+            const QVariantMap field = description.toMap();
+            if ( field.value( QLatin1String( "key" ) ).toString() == key ) {
+              key = field.value( QLatin1String( "title" ) ).toString();
+              break;
+            }
+          }
+        }
 
         customData += rowFmtStr.arg( key ).arg( value ) ;
       }
Index: akonadi/contact/contactmetadata.cpp
===================================================================
--- akonadi/contact/contactmetadata.cpp	(révision 1101152)
+++ akonadi/contact/contactmetadata.cpp	(révision 1101153)
@@ -36,6 +36,7 @@
     }
 
     int mDisplayNameMode;
+    QVariantList mCustomFieldDescriptions;
 };
 
 ContactMetaData::ContactMetaData()
@@ -60,6 +61,8 @@
     d->mDisplayNameMode = metaData.value( QLatin1String( "DisplayNameMode" ) ).toInt();
   else
     d->mDisplayNameMode = -1;
+
+  d->mCustomFieldDescriptions = metaData.value( QLatin1String( "CustomFieldDescriptions" ) ).toList();
 }
 
 void ContactMetaData::store( Akonadi::Item &contact )
@@ -70,6 +73,9 @@
   if ( d->mDisplayNameMode != -1 )
     metaData.insert( QLatin1String( "DisplayNameMode" ), QVariant( d->mDisplayNameMode ) );
 
+  if ( !d->mCustomFieldDescriptions.isEmpty() )
+    metaData.insert( QLatin1String( "CustomFieldDescriptions" ), d->mCustomFieldDescriptions );
+
   attribute->setMetaData( metaData );
 }
 
@@ -82,3 +88,13 @@
 {
   return d->mDisplayNameMode;
 }
+
+void ContactMetaData::setCustomFieldDescriptions( const QVariantList &descriptions )
+{
+  d->mCustomFieldDescriptions = descriptions;
+}
+
+QVariantList ContactMetaData::customFieldDescriptions() const
+{
+  return d->mCustomFieldDescriptions;
+}
Index: akonadi/contact/customfields.cpp
===================================================================
--- akonadi/contact/customfields.cpp	(révision 0)
+++ akonadi/contact/customfields.cpp	(révision 1101153)
@@ -0,0 +1,143 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfields_p.h"
+
+CustomField::CustomField()
+  : mType( TextType ), mScope( LocalScope )
+{
+}
+
+CustomField::CustomField( const QString &key, const QString &title, Type type, Scope scope )
+  : mKey( key ), mTitle( title ), mType( type ), mScope( scope )
+{
+}
+
+CustomField CustomField::fromVariantMap( const QVariantMap &map, Scope scope )
+{
+  return CustomField( map.value( QLatin1String( "key" ) ).toString(),
+                      map.value( QLatin1String( "title" ) ).toString(),
+                      stringToType( map.value( QLatin1String( "type" ) ).toString() ),
+                      scope );
+}
+
+void CustomField::setKey( const QString &key )
+{
+  mKey = key;
+}
+
+QString CustomField::key() const
+{
+  return mKey;
+}
+
+void CustomField::setTitle( const QString &title )
+{
+  mTitle = title;
+}
+
+QString CustomField::title() const
+{
+  return mTitle;
+}
+
+void CustomField::setType( Type type )
+{
+  mType = type;
+}
+
+CustomField::Type CustomField::type() const
+{
+  return mType;
+}
+
+void CustomField::setScope( Scope scope )
+{
+  mScope = scope;
+}
+
+CustomField::Scope CustomField::scope() const
+{
+  return mScope;
+}
+
+void CustomField::setValue( const QString &value )
+{
+  mValue = value;
+}
+
+QString CustomField::value() const
+{
+  return mValue;
+}
+
+QVariantMap CustomField::toVariantMap() const
+{
+  QVariantMap map;
+  map.insert( QLatin1String( "key" ), mKey );
+  map.insert( QLatin1String( "title" ), mTitle );
+  map.insert( QLatin1String( "type" ), typeToString( mType ) );
+
+  return map;
+}
+
+CustomField::Type CustomField::stringToType( const QString &type )
+{
+  if ( type == QLatin1String( "text" ) )
+    return CustomField::TextType;
+  if ( type == QLatin1String( "numeric" ) )
+    return CustomField::NumericType;
+  if ( type == QLatin1String( "boolean" ) )
+    return CustomField::BooleanType;
+  if ( type == QLatin1String( "date" ) )
+    return CustomField::DateType;
+  if ( type == QLatin1String( "time" ) )
+    return CustomField::TimeType;
+  if ( type == QLatin1String( "datetime" ) )
+    return CustomField::DateTimeType;
+
+  return CustomField::TextType;
+}
+
+QString CustomField::typeToString( CustomField::Type type )
+{
+  switch ( type ) {
+    case CustomField::TextType:
+    default:
+      return QLatin1String( "text" );
+      break;
+    case CustomField::NumericType:
+      return QLatin1String( "numeric" );
+      break;
+    case CustomField::BooleanType:
+      return QLatin1String( "boolean" );
+      break;
+    case CustomField::DateType:
+      return QLatin1String( "date" );
+      break;
+    case CustomField::TimeType:
+      return QLatin1String( "time" );
+      break;
+    case CustomField::DateTimeType:
+      return QLatin1String( "datetime" );
+      break;
+  }
+}
Index: akonadi/contact/customfieldmanager_p.h
===================================================================
--- akonadi/contact/customfieldmanager_p.h	(révision 0)
+++ akonadi/contact/customfieldmanager_p.h	(révision 1101153)
@@ -0,0 +1,37 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDMANAGER_P_H
+#define CUSTOMFIELDMANAGER_P_H
+
+#include "customfields_p.h"
+
+/**
+ * @short A class that manages the descriptions of all custom fields with global scope.
+ */
+class CustomFieldManager
+{
+  public:
+    static void setGlobalCustomFieldDescriptions( const CustomField::List &customFields );
+    static CustomField::List globalCustomFieldDescriptions();
+};
+
+#endif
Index: akonadi/contact/editor/customfieldsdelegate.h
===================================================================
--- akonadi/contact/editor/customfieldsdelegate.h	(révision 0)
+++ akonadi/contact/editor/customfieldsdelegate.h	(révision 1101153)
@@ -0,0 +1,40 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDSDELEGATE_H
+#define CUSTOMFIELDSDELEGATE_H
+
+#include <QtGui/QStyledItemDelegate>
+
+class CustomFieldsDelegate : public QStyledItemDelegate
+{
+  public:
+    explicit CustomFieldsDelegate( QObject *parent = 0 );
+    ~CustomFieldsDelegate();
+
+    virtual QWidget* createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
+
+    virtual void setEditorData( QWidget *editor, const QModelIndex &index ) const;
+    virtual void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const;
+    virtual void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
+};
+
+#endif
Index: akonadi/contact/editor/customfieldeditordialog.cpp
===================================================================
--- akonadi/contact/editor/customfieldeditordialog.cpp	(révision 0)
+++ akonadi/contact/editor/customfieldeditordialog.cpp	(révision 1101153)
@@ -0,0 +1,93 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfieldeditordialog.h"
+
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <klocale.h>
+
+#include <QtGui/QCheckBox>
+#include <QtGui/QFormLayout>
+#include <QtGui/QRegExpValidator>
+
+CustomFieldEditorDialog::CustomFieldEditorDialog( QWidget *parent )
+  : KDialog( parent )
+{
+  setCaption( i18n( "Edit Custom Field" ) );
+  setButtons( Ok | Cancel | Details );
+
+  QWidget *widget = new QWidget( this );
+  setMainWidget( widget );
+
+  QFormLayout *layout = new QFormLayout( widget );
+
+  mKey = new KLineEdit;
+  mTitle = new KLineEdit;
+  mType = new KComboBox;
+  mScope = new QCheckBox( i18n( "Use field for all contacts" ) );
+
+  layout->addRow( i18n( "Title" ), mTitle );
+  layout->addRow( i18n( "Type" ), mType );
+  layout->addRow( QString(), mScope );
+
+  QWidget *detailsWidget = new QWidget;
+  QFormLayout *detailsLayout = new QFormLayout( detailsWidget );
+  detailsLayout->addRow( i18n( "Key" ), mKey );
+
+  setDetailsWidget( detailsWidget );
+  setButtonText( Details, i18n( "Advanced" ) );
+
+  mType->addItem( i18n( "Text" ), CustomField::TextType );
+  mType->addItem( i18n( "Numeric" ), CustomField::NumericType );
+  mType->addItem( i18n( "Boolean" ), CustomField::BooleanType );
+  mType->addItem( i18n( "Date" ), CustomField::DateType );
+  mType->addItem( i18n( "Time" ), CustomField::TimeType );
+  mType->addItem( i18n( "DateTime" ), CustomField::DateTimeType );
+
+  mKey->setValidator( new QRegExpValidator( QRegExp( QLatin1String( "[a-zA-Z0-9\\-]+" ) ), this ) );
+}
+
+void CustomFieldEditorDialog::setCustomField( const CustomField &field )
+{
+  mCustomField = field;
+
+  mKey->setText( mCustomField.key() );
+  mTitle->setText( mCustomField.title() );
+  mType->setCurrentIndex( mType->findData( mCustomField.type() ) );
+  mScope->setChecked( (mCustomField.scope() == CustomField::GlobalScope) );
+}
+
+CustomField CustomFieldEditorDialog::customField() const
+{
+  CustomField customField( mCustomField );
+
+  customField.setKey( mKey->text() );
+  customField.setTitle( mTitle->text() );
+  customField.setType( static_cast<CustomField::Type>( mType->itemData( mType->currentIndex() ).toInt() ) );
+
+  if ( customField.scope() != CustomField::ExternalScope ) {
+    // do not change the scope for externally defined custom fields
+    customField.setScope( mScope->isChecked() ? CustomField::GlobalScope : CustomField::LocalScope );
+  }
+
+  return customField;
+}
Index: akonadi/contact/editor/customfieldseditwidget.h
===================================================================
--- akonadi/contact/editor/customfieldseditwidget.h	(révision 0)
+++ akonadi/contact/editor/customfieldseditwidget.h	(révision 1101153)
@@ -0,0 +1,70 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDSEDITWIDGET_H
+#define CUSTOMFIELDSEDITWIDGET_H
+
+#include <QtGui/QWidget>
+
+#include "customfieldsmodel.h"
+
+namespace KABC {
+class Addressee;
+}
+
+class QPushButton;
+class QTreeView;
+
+class CustomFieldsEditWidget : public QWidget
+{
+  Q_OBJECT
+
+  public:
+    CustomFieldsEditWidget( QWidget *parent = 0 );
+    ~CustomFieldsEditWidget();
+
+    void loadContact( const KABC::Addressee &contact );
+    void storeContact( KABC::Addressee &contact ) const;
+
+    void setReadOnly( bool readOnly );
+
+    void setLocalCustomFieldDescriptions( const QVariantList &descriptions );
+    QVariantList localCustomFieldDescriptions() const;
+
+  private Q_SLOTS:
+    void slotAdd();
+    void slotEdit();
+    void slotRemove();
+    void slotUpdateButtons();
+
+  private:
+    QTreeView *mView;
+
+    QPushButton *mAddButton;
+    QPushButton *mEditButton;
+    QPushButton *mRemoveButton;
+
+    bool mReadOnly;
+    CustomFieldsModel *mModel;
+    CustomField::List mLocalCustomFields;
+};
+
+#endif
Index: akonadi/contact/editor/customfieldsmodel.cpp
===================================================================
--- akonadi/contact/editor/customfieldsmodel.cpp	(révision 0)
+++ akonadi/contact/editor/customfieldsmodel.cpp	(révision 1101153)
@@ -0,0 +1,250 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfieldsmodel.h"
+
+#include <kglobal.h>
+#include <kicon.h>
+#include <klocale.h>
+
+#include <QtCore/QDateTime>
+
+CustomFieldsModel::CustomFieldsModel( QObject *parent )
+  : QAbstractItemModel( parent )
+{
+}
+
+CustomFieldsModel::~CustomFieldsModel()
+{
+}
+
+void CustomFieldsModel::setCustomFields( const CustomField::List &customFields )
+{
+  emit layoutAboutToBeChanged();
+
+  mCustomFields = customFields;
+
+  emit layoutChanged();
+}
+
+CustomField::List CustomFieldsModel::customFields() const
+{
+  return mCustomFields;
+}
+
+QModelIndex CustomFieldsModel::index( int row, int column, const QModelIndex& ) const
+{
+  return createIndex( row, column, 0 );
+}
+
+QModelIndex CustomFieldsModel::parent( const QModelIndex& ) const
+{
+  return QModelIndex();
+}
+
+QVariant CustomFieldsModel::data( const QModelIndex &index, int role ) const
+{
+  if ( !index.isValid() )
+    return QVariant();
+
+  if ( index.row() < 0 || index.row() >= mCustomFields.count() )
+    return QVariant();
+
+  if ( index.column() < 0 || index.column() > 2 )
+    return QVariant();
+
+  const CustomField &customField = mCustomFields[ index.row() ];
+
+  if ( role == Qt::DecorationRole ) {
+    if ( index.column() == 1 ) {
+      if ( customField.type() == CustomField::BooleanType ) {
+        if ( customField.value() == QLatin1String( "true" ) )
+          return KIcon( QLatin1String( "button_more" ) );
+        else
+          return KIcon( QLatin1String( "button_fewer" ) );
+      }
+    }
+
+    return QVariant();
+  }
+
+  if ( role == Qt::DisplayRole ) {
+    if ( index.column() == 0 )
+      return customField.title();
+    else if ( index.column() == 1 ) {
+      switch ( customField.type() ) {
+        case CustomField::TextType:
+        case CustomField::NumericType:
+          return customField.value();
+          break;
+        case CustomField::BooleanType:
+          return QString(); // we use icons here
+          break;
+        case CustomField::DateType:
+          {
+            const QDate value = QDate::fromString( customField.value(), Qt::ISODate );
+            return KGlobal::locale()->formatDate( value, KLocale::ShortDate );
+          }
+          break;
+        case CustomField::TimeType:
+          {
+            const QTime value = QTime::fromString( customField.value(), Qt::ISODate );
+            return KGlobal::locale()->formatTime( value );
+          }
+          break;
+        case CustomField::DateTimeType:
+          {
+            const QDateTime value = QDateTime::fromString( customField.value(), Qt::ISODate );
+            return KGlobal::locale()->formatDateTime( value );
+          }
+          break;
+      }
+      return customField.value();
+    } else
+      return customField.key();
+  }
+
+  if ( role == Qt::EditRole ) {
+    if ( index.column() == 0 )
+      return customField.title();
+    else if ( index.column() == 1 )
+      return customField.value();
+    else
+      return customField.key();
+  }
+
+  if ( role == TypeRole )
+    return customField.type();
+
+  if ( role == ScopeRole )
+    return customField.scope();
+
+  return QVariant();
+}
+
+bool CustomFieldsModel::setData( const QModelIndex &index, const QVariant &value, int role )
+{
+  if ( !index.isValid() )
+    return false;
+
+  if ( index.row() < 0 || index.row() >= mCustomFields.count() )
+    return false;
+
+  if ( index.column() < 0 || index.column() > 2 )
+    return false;
+
+  CustomField &customField = mCustomFields[ index.row() ];
+
+  if ( role == Qt::EditRole ) {
+    if ( index.column() == 0 )
+      customField.setTitle( value.toString() );
+    else if ( index.column() == 1 )
+      customField.setValue( value.toString() );
+    else
+      customField.setKey( value.toString() );
+
+    emit dataChanged( index, index );
+    return true;
+  }
+
+  if ( role == TypeRole ) {
+    customField.setType( (CustomField::Type)value.toInt() );
+    emit dataChanged( index, index );
+    return true;
+  }
+
+  if ( role == ScopeRole ) {
+    customField.setScope( (CustomField::Scope)value.toInt() );
+    emit dataChanged( index, index );
+    return true;
+  }
+
+  return false;
+}
+
+QVariant CustomFieldsModel::headerData( int section, Qt::Orientation orientation, int role ) const
+{
+  if ( section < 0 || section > 1 )
+    return QVariant();
+
+  if ( orientation != Qt::Horizontal )
+    return QVariant();
+
+  if ( role != Qt::DisplayRole )
+    return QVariant();
+
+  if ( section == 0 )
+    return i18nc( "custom field title", "Title" );
+  else
+    return i18nc( "custom field value", "Value" );
+}
+
+Qt::ItemFlags CustomFieldsModel::flags( const QModelIndex &index ) const
+{
+  if ( !index.isValid() || index.row() < 0 || index.row() >= mCustomFields.count() )
+    return QAbstractItemModel::flags( index );
+
+  const Qt::ItemFlags parentFlags = QAbstractItemModel::flags( index );
+  return (parentFlags | Qt::ItemIsEnabled | Qt::ItemIsEditable);
+}
+
+int CustomFieldsModel::columnCount( const QModelIndex &parent ) const
+{
+  if ( !parent.isValid() )
+    return 3;
+  else
+    return 0;
+}
+
+int CustomFieldsModel::rowCount( const QModelIndex &parent ) const
+{
+  if ( !parent.isValid() )
+    return mCustomFields.count();
+  else
+    return 0;
+}
+
+bool CustomFieldsModel::insertRows( int row, int count, const QModelIndex &parent )
+{
+  if ( parent.isValid() )
+    return false;
+
+  beginInsertRows( parent, row, row + count - 1 );
+  for ( int i = 0; i < count; ++i )
+    mCustomFields.insert( row, CustomField() );
+  endInsertRows();
+
+  return true;
+}
+
+bool CustomFieldsModel::removeRows( int row, int count, const QModelIndex &parent )
+{
+  if ( parent.isValid() )
+    return false;
+
+  beginRemoveRows( parent, row, row + count - 1 );
+  for ( int i = 0; i < count; ++i )
+    mCustomFields.removeAt( row );
+  endRemoveRows();
+
+  return true;
+}
+
Index: akonadi/contact/editor/customfieldsdelegate.cpp
===================================================================
--- akonadi/contact/editor/customfieldsdelegate.cpp	(révision 0)
+++ akonadi/contact/editor/customfieldsdelegate.cpp	(révision 1101153)
@@ -0,0 +1,194 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfieldsdelegate.h"
+
+#include "customfieldsmodel.h"
+
+#include <kicon.h>
+#include <klocale.h>
+
+#include <QtGui/QDateEdit>
+#include <QtGui/QDateTimeEdit>
+#include <QtGui/QCheckBox>
+#include <QtGui/QSpinBox>
+#include <QtGui/QTimeEdit>
+
+CustomFieldsDelegate::CustomFieldsDelegate( QObject *parent )
+  : QStyledItemDelegate( parent )
+{
+}
+
+CustomFieldsDelegate::~CustomFieldsDelegate()
+{
+}
+
+QWidget* CustomFieldsDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &item, const QModelIndex &index ) const
+{
+  if ( index.column() == 1 ) {
+    const CustomField::Type type = static_cast<CustomField::Type>( index.data( CustomFieldsModel::TypeRole ).toInt() );
+
+    switch ( type ) {
+      case CustomField::TextType:
+      default:
+        return QStyledItemDelegate::createEditor( parent, item, index );
+        break;
+      case CustomField::NumericType:
+        {
+          QSpinBox *editor = new QSpinBox( parent );
+          editor->setFrame( false );
+          editor->setAutoFillBackground( true );
+          return editor;
+        }
+        break;
+      case CustomField::BooleanType:
+        {
+          QCheckBox *editor = new QCheckBox( parent );
+          return editor;
+        }
+        break;
+      case CustomField::DateType:
+        {
+          QDateEdit *editor = new QDateEdit( parent );
+          editor->setFrame( false );
+          editor->setAutoFillBackground( true );
+          return editor;
+        }
+        break;
+      case CustomField::TimeType:
+        {
+          QTimeEdit *editor = new QTimeEdit( parent );
+          editor->setFrame( false );
+          editor->setAutoFillBackground( true );
+          return editor;
+        }
+        break;
+      case CustomField::DateTimeType:
+        {
+          QDateTimeEdit *editor = new QDateTimeEdit( parent );
+          editor->setFrame( false );
+          editor->setAutoFillBackground( true );
+          return editor;
+        }
+        break;
+    }
+  } else {
+    return QStyledItemDelegate::createEditor( parent, item, index );
+  }
+}
+
+void CustomFieldsDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
+{
+  if ( index.column() == 1 ) {
+    const CustomField::Type type = static_cast<CustomField::Type>( index.data( CustomFieldsModel::TypeRole ).toInt() );
+
+    switch ( type ) {
+      case CustomField::TextType:
+        QStyledItemDelegate::setEditorData( editor, index );
+        break;
+      case CustomField::NumericType:
+        {
+          QSpinBox *widget = qobject_cast<QSpinBox*>( editor );
+          widget->setValue( index.data( Qt::EditRole ).toInt() );
+        }
+        break;
+      case CustomField::BooleanType:
+        {
+          QCheckBox *widget = qobject_cast<QCheckBox*>( editor );
+          widget->setChecked( index.data( Qt::EditRole ).toString() == QLatin1String( "true" ) );
+        }
+        break;
+      case CustomField::DateType:
+        {
+          QDateEdit *widget = qobject_cast<QDateEdit*>( editor );
+          widget->setDate( QDate::fromString( index.data( Qt::EditRole ).toString(), Qt::ISODate ) );
+        }
+        break;
+      case CustomField::TimeType:
+        {
+          QTimeEdit *widget = qobject_cast<QTimeEdit*>( editor );
+          widget->setTime( QTime::fromString( index.data( Qt::EditRole ).toString(), Qt::ISODate ) );
+        }
+        break;
+      case CustomField::DateTimeType:
+        {
+          QDateTimeEdit *widget = qobject_cast<QDateTimeEdit*>( editor );
+          widget->setDateTime( QDateTime::fromString( index.data( Qt::EditRole ).toString(), Qt::ISODate ) );
+        }
+        break;
+    }
+  } else {
+    QStyledItemDelegate::setEditorData( editor, index );
+  }
+}
+
+void CustomFieldsDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
+{
+  if ( index.column() == 1 ) {
+    const CustomField::Type type = static_cast<CustomField::Type>( index.data( CustomFieldsModel::TypeRole ).toInt() );
+
+    switch ( type ) {
+      case CustomField::TextType:
+        QStyledItemDelegate::setModelData( editor, model, index );
+        break;
+      case CustomField::NumericType:
+        {
+          QSpinBox *widget = qobject_cast<QSpinBox*>( editor );
+          model->setData( index, QString::number( widget->value() ) );
+        }
+        break;
+      case CustomField::BooleanType:
+        {
+          QCheckBox *widget = qobject_cast<QCheckBox*>( editor );
+          model->setData( index, widget->isChecked() ? QLatin1String( "true" ) : QLatin1String( "false" ) );
+        }
+        break;
+      case CustomField::DateType:
+        {
+          QDateEdit *widget = qobject_cast<QDateEdit*>( editor );
+          model->setData( index, widget->date().toString( Qt::ISODate ) );
+        }
+        break;
+      case CustomField::TimeType:
+        {
+          QTimeEdit *widget = qobject_cast<QTimeEdit*>( editor );
+          model->setData( index, widget->time().toString( Qt::ISODate ) );
+        }
+        break;
+      case CustomField::DateTimeType:
+        {
+          QDateTimeEdit *widget = qobject_cast<QDateTimeEdit*>( editor );
+          model->setData( index, widget->dateTime().toString( Qt::ISODate ) );
+        }
+        break;
+    }
+  } else {
+    QStyledItemDelegate::setModelData( editor, model, index );
+  }
+}
+
+void CustomFieldsDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
+{
+  //TODO: somehow mark local/global/external fields
+  QStyledItemDelegate::paint( painter, option, index );
+}
+
+#include "customfieldsdelegate.moc"
Index: akonadi/contact/editor/customfieldeditordialog.h
===================================================================
--- akonadi/contact/editor/customfieldeditordialog.h	(révision 0)
+++ akonadi/contact/editor/customfieldeditordialog.h	(révision 1101153)
@@ -0,0 +1,51 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDEDITORDIALOG_H
+#define CUSTOMFIELDEDITORDIALOG_H
+
+#include <kdialog.h>
+
+#include "customfieldsmodel.h"
+
+class KComboBox;
+class KLineEdit;
+class QCheckBox;
+
+class CustomFieldEditorDialog : public KDialog
+{
+  Q_OBJECT
+
+  public:
+    CustomFieldEditorDialog( QWidget *parent = 0 );
+
+    void setCustomField( const CustomField &field );
+    CustomField customField() const;
+
+  private:
+    KLineEdit *mTitle;
+    KComboBox *mType;
+    QCheckBox *mScope;
+    KLineEdit *mKey;
+    CustomField mCustomField;
+};
+
+#endif
Index: akonadi/contact/editor/customfieldseditwidget.cpp
===================================================================
--- akonadi/contact/editor/customfieldseditwidget.cpp	(révision 0)
+++ akonadi/contact/editor/customfieldseditwidget.cpp	(révision 1101153)
@@ -0,0 +1,318 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "customfieldseditwidget.h"
+
+#include "customfieldeditordialog.h"
+#include "customfieldmanager_p.h"
+#include "customfieldsdelegate.h"
+#include "customfieldsmodel.h"
+
+#include <kabc/addressee.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <QtCore/QUuid>
+#include <QtGui/QGridLayout>
+#include <QtGui/QPushButton>
+#include <QtGui/QTreeView>
+
+static void splitCustomField( const QString &str, QString &app, QString &name, QString &value )
+{
+  const int colon = str.indexOf( QLatin1Char( ':' ) );
+  if ( colon != -1 ) {
+    const QString tmp = str.left( colon );
+    value = str.mid( colon + 1 );
+
+    const int dash = tmp.indexOf( QLatin1Char( '-' ) );
+    if ( dash != -1 ) {
+      app = tmp.left( dash );
+      name = tmp.mid( dash + 1 );
+    }
+  }
+}
+
+CustomFieldsEditWidget::CustomFieldsEditWidget( QWidget *parent )
+  : QWidget( parent ), mReadOnly( false )
+{
+  QGridLayout *layout = new QGridLayout( this );
+  layout->setMargin( 0 );
+
+  mView = new QTreeView;
+  mView->setRootIsDecorated( false );
+  mView->setItemDelegate( new CustomFieldsDelegate( this ) );
+
+  mAddButton = new QPushButton( i18n( "Add..." ) );
+  mEditButton = new QPushButton( i18n( "Edit..." ) );
+  mRemoveButton = new QPushButton( i18n( "Remove" ) );
+
+  layout->addWidget( mView, 0, 0, 4, 1 );
+  layout->addWidget( mAddButton, 0, 1 );
+  layout->addWidget( mEditButton, 1, 1 );
+  layout->addWidget( mRemoveButton, 2, 1 );
+
+  mModel = new CustomFieldsModel( this );
+  mView->setModel( mModel );
+  mView->setColumnHidden( 2, true ); // hide the 'key' column
+
+  connect( mView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
+           this, SLOT( slotUpdateButtons() ) );
+  connect( mAddButton, SIGNAL( clicked() ), this, SLOT( slotAdd() ) );
+  connect( mEditButton, SIGNAL( clicked() ), this, SLOT( slotEdit() ) );
+  connect( mRemoveButton, SIGNAL( clicked() ), this, SLOT( slotRemove() ) );
+}
+
+CustomFieldsEditWidget::~CustomFieldsEditWidget()
+{
+}
+
+void CustomFieldsEditWidget::loadContact( const KABC::Addressee &contact )
+{
+  CustomField::List externalCustomFields;
+
+  CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
+
+  const QStringList customs = contact.customs();
+  foreach ( const QString &custom, customs ) {
+
+    QString app, name, value;
+    splitCustomField( custom, app, name, value );
+
+    // skip all well-known fields that have separated editor widgets
+    if ( custom.startsWith( QLatin1String( "messaging/" ) ) ) // IM addresses
+      continue;
+
+    if ( app == QLatin1String( "KADDRESSBOOK" ) ) {
+      static QSet<QString> blacklist;
+      if ( blacklist.isEmpty() ) {
+        blacklist << QLatin1String( "BlogFeed" )
+                  << QLatin1String( "X-IMAddress" )
+                  << QLatin1String( "X-Profession" )
+                  << QLatin1String( "X-Office" )
+                  << QLatin1String( "X-ManagersName" )
+                  << QLatin1String( "X-AssistantsName" )
+                  << QLatin1String( "X-Anniversary" )
+                  << QLatin1String( "X-SpousesName" )
+                  << QLatin1String( "X-Profession" );
+      }
+
+      if ( blacklist.contains( name ) ) // several KAddressBook specific fields
+        continue;
+    }
+
+    // check whether it correspond to a local custom field
+    bool isLocalCustomField = false;
+    for ( int i = 0; i < mLocalCustomFields.count(); ++i ) {
+      if ( mLocalCustomFields[ i ].key() == name ) {
+        mLocalCustomFields[ i ].setValue( value );
+        isLocalCustomField = true;
+        break;
+      }
+    }
+
+    // check whether it correspond to a global custom field
+    bool isGlobalCustomField = false;
+    for ( int i = 0; i < globalCustomFields.count(); ++i ) {
+      if ( globalCustomFields[ i ].key() == name ) {
+        globalCustomFields[ i ].setValue( value );
+        isGlobalCustomField = true;
+        break;
+      }
+    }
+
+    // if not local and not global it must be external
+    if ( !isLocalCustomField && !isGlobalCustomField ) {
+      if ( app == QLatin1String( "KADDRESSBOOK" ) ) {
+        // however if it starts with our prefix it might be that this is an outdated
+        // global custom field, in this case treat it as local field of type text
+        CustomField customField( name, name, CustomField::TextType, CustomField::LocalScope );
+        customField.setValue( value );
+
+        mLocalCustomFields << customField;
+      } else {
+        // it is really an external custom field
+        const QString key = app + QLatin1Char( '-' ) + name;
+        CustomField customField( key, key, CustomField::TextType, CustomField::ExternalScope );
+        customField.setValue( value );
+
+        externalCustomFields << customField;
+      }
+    }
+  }
+
+  mModel->setCustomFields( CustomField::List() << mLocalCustomFields << globalCustomFields << externalCustomFields );
+}
+
+void CustomFieldsEditWidget::storeContact( KABC::Addressee &contact ) const
+{
+  const CustomField::List customFields = mModel->customFields();
+  foreach ( const CustomField &customField, customFields ) {
+    // write back values for local and global scope, leave external untouched
+    if ( customField.scope() != CustomField::ExternalScope ) {
+      if ( !customField.value().isEmpty() )
+        contact.insertCustom( QLatin1String( "KADDRESSBOOK" ), customField.key(), customField.value() );
+      else
+        contact.removeCustom( QLatin1String( "KADDRESSBOOK" ), customField.key() );
+    }
+  }
+
+  // Now remove all fields that were available in loadContact (these are stored in mLocalCustomFields)
+  // but are not part of customFields now, which means they have been removed or renamed by the user
+  // in the editor dialog.
+  foreach ( const CustomField &oldCustomField, mLocalCustomFields ) {
+    if ( oldCustomField.scope() != CustomField::ExternalScope ) {
+
+      bool fieldStillExists = false;
+      foreach ( const CustomField &newCustomField, customFields ) {
+        if ( newCustomField.scope() != CustomField::ExternalScope ) {
+          if ( newCustomField.key() == oldCustomField.key() ) {
+            fieldStillExists = true;
+            break;
+          }
+        }
+      }
+
+      if ( !fieldStillExists )
+        contact.removeCustom( QLatin1String( "KADDRESSBOOK" ), oldCustomField.key() );
+    }
+  }
+
+  // And store the global custom fields descriptions as well
+  CustomField::List globalCustomFields;
+  foreach ( const CustomField &customField, customFields ) {
+    if ( customField.scope() == CustomField::GlobalScope ) {
+      globalCustomFields << customField;
+    }
+  }
+
+  CustomFieldManager::setGlobalCustomFieldDescriptions( globalCustomFields );
+}
+
+void CustomFieldsEditWidget::setReadOnly( bool readOnly )
+{
+  mReadOnly = readOnly;
+
+  mView->setEnabled( !mReadOnly );
+
+  slotUpdateButtons();
+}
+
+void CustomFieldsEditWidget::setLocalCustomFieldDescriptions( const QVariantList &descriptions )
+{
+  mLocalCustomFields.clear();
+
+  foreach ( const QVariant &description, descriptions )
+    mLocalCustomFields.append( CustomField::fromVariantMap( description.toMap(), CustomField::LocalScope ) );
+}
+
+QVariantList CustomFieldsEditWidget::localCustomFieldDescriptions() const
+{
+  const CustomField::List customFields = mModel->customFields();
+
+  QVariantList descriptions;
+  foreach ( const CustomField &field, customFields ) {
+    if ( field.scope() == CustomField::LocalScope )
+      descriptions.append( field.toVariantMap() );
+  }
+
+  return descriptions;
+}
+
+void CustomFieldsEditWidget::slotAdd()
+{
+  CustomField field;
+
+  // We use a Uuid as default key, so we won't have any duplicated keys,
+  // the user can still change it to something else in the editor dialog.
+  // Since the key only allows [A-Za-z0-9\-]*, we have to remove the curly
+  // braces as well.
+  QString key = QUuid::createUuid().toString();
+  key.remove( QLatin1Char( '{' ) );
+  key.remove( QLatin1Char( '}' ) );
+
+  field.setKey( key );
+
+  CustomFieldEditorDialog dlg( this );
+  dlg.setCustomField( field );
+
+  if ( dlg.exec() ) {
+    const int lastRow = mModel->rowCount();
+    mModel->insertRow( lastRow );
+
+    field = dlg.customField();
+    mModel->setData( mModel->index( lastRow, 2 ), field.key(), Qt::EditRole );
+    mModel->setData( mModel->index( lastRow, 0 ), field.title(), Qt::EditRole );
+    mModel->setData( mModel->index( lastRow, 0 ), field.type(), CustomFieldsModel::TypeRole );
+    mModel->setData( mModel->index( lastRow, 0 ), field.scope(), CustomFieldsModel::ScopeRole );
+  }
+}
+
+void CustomFieldsEditWidget::slotEdit()
+{
+  const QModelIndex currentIndex = mView->currentIndex();
+  if ( !currentIndex.isValid() )
+    return;
+
+  CustomField field;
+  field.setKey( mModel->index( currentIndex.row(), 2 ).data( Qt::DisplayRole ).toString() );
+  field.setTitle( mModel->index( currentIndex.row(), 0 ).data( Qt::DisplayRole ).toString() );
+  field.setType( static_cast<CustomField::Type>( currentIndex.data( CustomFieldsModel::TypeRole ).toInt() ) );
+  field.setScope( static_cast<CustomField::Scope>( currentIndex.data( CustomFieldsModel::ScopeRole ).toInt() ) );
+
+  CustomFieldEditorDialog dlg( this );
+  dlg.setCustomField( field );
+
+  if ( dlg.exec() ) {
+    field = dlg.customField();
+    mModel->setData( mModel->index( currentIndex.row(), 2 ), field.key(), Qt::EditRole );
+    mModel->setData( mModel->index( currentIndex.row(), 0 ), field.title(), Qt::EditRole );
+    mModel->setData( currentIndex, field.type(), CustomFieldsModel::TypeRole );
+    mModel->setData( currentIndex, field.scope(), CustomFieldsModel::ScopeRole );
+  }
+}
+
+void CustomFieldsEditWidget::slotRemove()
+{
+  const QModelIndex currentIndex = mView->currentIndex();
+  if ( !currentIndex.isValid() )
+    return;
+
+  if ( KMessageBox::warningContinueCancel( this,
+                                           i18nc( "Custom Fields", "Do you really want to delete the selected custom field?" ),
+                                           i18n( "Confirm Delete" ), KStandardGuiItem::del() ) != KMessageBox::Continue ) {
+    return;
+  }
+
+  mModel->removeRow( currentIndex.row() );
+}
+
+void CustomFieldsEditWidget::slotUpdateButtons()
+{
+  const bool hasCurrent = mView->currentIndex().isValid();
+  const bool isExternal = (hasCurrent && 
+                           (static_cast<CustomField::Scope>( mView->currentIndex().data( CustomFieldsModel::ScopeRole ).toInt() ) == CustomField::ExternalScope) );
+
+  mAddButton->setEnabled( !mReadOnly );
+  mEditButton->setEnabled( !mReadOnly && hasCurrent && !isExternal );
+  mRemoveButton->setEnabled( !mReadOnly && hasCurrent && !isExternal );
+}
+
+#include "customfieldseditwidget.moc"
Index: akonadi/contact/editor/customfieldsmodel.h
===================================================================
--- akonadi/contact/editor/customfieldsmodel.h	(révision 0)
+++ akonadi/contact/editor/customfieldsmodel.h	(révision 1101153)
@@ -0,0 +1,61 @@
+/*
+    This file is part of Akonadi Contact.
+
+    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef CUSTOMFIELDSMODEL_H
+#define CUSTOMFIELDSMODEL_H
+
+#include "../customfields_p.h"
+
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QList>
+
+class CustomFieldsModel : public QAbstractItemModel
+{
+  public:
+    enum Role
+    {
+      TypeRole = Qt::UserRole,
+      ScopeRole
+    };
+
+    CustomFieldsModel( QObject *parent = 0 );
+    ~CustomFieldsModel();
+
+    void setCustomFields( const CustomField::List &addresses );
+    CustomField::List customFields() const;
+
+    virtual QModelIndex index( int row, int col, const QModelIndex &parent = QModelIndex() ) const;
+    virtual QModelIndex parent( const QModelIndex &child ) const;
+    virtual QVariant data( const QModelIndex &index, int role ) const;
+    virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole );
+    virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
+    virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
+    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+
+    virtual bool insertRows( int row, int count, const QModelIndex &parent = QModelIndex() );
+    virtual bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() );
+
+  private:
+    CustomField::List mCustomFields;
+};
+
+#endif
Index: akonadi/contact/CMakeLists.txt
===================================================================
--- akonadi/contact/CMakeLists.txt
+++ akonadi/contact/CMakeLists.txt	2010-04-03 18:27:57.000000000 +0200
@@ -22,6 +22,10 @@
 set(akonadicontact_editor_SRCS
   editor/addresseditwidget.cpp
   editor/contacteditorwidget.cpp
+  editor/customfieldeditordialog.cpp
+  editor/customfieldsdelegate.cpp
+  editor/customfieldseditwidget.cpp
+  editor/customfieldsmodel.cpp
   editor/dateeditwidget.cpp
   editor/displaynameeditwidget.cpp
   editor/emaileditwidget.cpp
@@ -58,6 +62,8 @@
   contactsearchjob.cpp
   contactviewer.cpp
   contactviewerdialog.cpp
+  customfields.cpp
+  customfieldmanager.cpp
   recentcontactscollections.cpp
   recentcontactscollectionrequestjob.cpp
   waitingoverlay.cpp
Index: akonadi/contact/editor/contacteditorwidget.cpp
===================================================================
--- akonadi/contact/editor/contacteditorwidget.cpp
+++ akonadi/contact/editor/contacteditorwidget.cpp	2010-04-03 18:33:01.000000000 +0200
@@ -24,6 +24,7 @@
 #include "addresseditwidget.h"
 #include "contacteditorpageplugin.h"
 #include "contactmetadata_p.h"
+#include "customfieldseditwidget.h"
 #include "dateeditwidget.h"
 #include "displaynameeditwidget.h"
 #include "emaileditwidget.h"
@@ -63,6 +64,7 @@
     void initGuiLocationTab();
     void initGuiBusinessTab();
     void initGuiPersonalTab();
+    void initGuiCustomFieldsTab();
 
     void loadCustomPages();
 
@@ -117,6 +119,9 @@
     // widgets from family group
     KLineEdit *mPartnerWidget;
 
+    // widgets from custom fields group
+    CustomFieldsEditWidget *mCustomFieldsWidget;
+
     // custom editor pages
     QList<Akonadi::ContactEditorPagePlugin*> mCustomPages;
 };
@@ -133,6 +138,7 @@
   initGuiLocationTab();
   initGuiBusinessTab();
   initGuiPersonalTab();
+  initGuiCustomFieldsTab();
 
   loadCustomPages();
 }
@@ -416,6 +422,17 @@
   familyLayout->setRowStretch( 1, 1 );
 }
 
+void ContactEditorWidget::Private::initGuiCustomFieldsTab()
+{
+  QWidget *widget = new QWidget;
+  QVBoxLayout *layout = new QVBoxLayout( widget );
+
+  mTabWidget->addTab( widget, i18n( "Custom Fields" ) );
+
+  mCustomFieldsWidget = new CustomFieldsEditWidget;
+  layout->addWidget( mCustomFieldsWidget );
+}
+
 void ContactEditorWidget::Private::loadCustomPages()
 {
   qDeleteAll( mCustomPages );
@@ -518,6 +535,10 @@
 
   d->mDisplayNameWidget->setDisplayType( (DisplayNameEditWidget::DisplayType)metaData.displayNameMode() );
 
+  // custom fields group
+  d->mCustomFieldsWidget->setLocalCustomFieldDescriptions( metaData.customFieldDescriptions() );
+  d->mCustomFieldsWidget->loadContact( contact );
+
   // custom pages
   foreach ( Akonadi::ContactEditorPagePlugin *plugin, d->mCustomPages )
     plugin->loadContact( contact );
@@ -570,6 +591,10 @@
   // family group
   d->storeCustom( contact, QLatin1String( "X-SpousesName" ), d->mPartnerWidget->text().trimmed() );
 
+  // custom fields group
+  d->mCustomFieldsWidget->storeContact( contact );
+  metaData.setCustomFieldDescriptions( d->mCustomFieldsWidget->localCustomFieldDescriptions() );
+
   metaData.setDisplayNameMode( d->mDisplayNameWidget->displayType() );
 
   // custom pages
@@ -624,6 +649,9 @@
   // widgets from family group
   d->mPartnerWidget->setReadOnly( readOnly );
 
+  // widgets from custom fields group
+  d->mCustomFieldsWidget->setReadOnly( readOnly );
+
   // custom pages
   foreach ( Akonadi::ContactEditorPagePlugin *plugin, d->mCustomPages )
     plugin->setReadOnly( readOnly );