Branch 'kolab/integration/4.13.0' - 3 commits - calendarviews/agenda incidenceeditor-ng/CMakeLists.txt incidenceeditor-ng/conflictresolver.cpp incidenceeditor-ng/conflictresolver.h incidenceeditor-ng/freebusyganttproxymodel.cpp incidenceeditor-ng/freebusyitem.cpp incidenceeditor-ng/freebusyitem.h incidenceeditor-ng/freebusyitemmodel.cpp incidenceeditor-ng/freebusyitemmodel.h incidenceeditor-ng/freeperiodmodel.cpp incidenceeditor-ng/freeperiodmodel.h incidenceeditor-ng/incidenceattendee.cpp incidenceeditor-ng/resourcemanagement.cpp incidenceeditor-ng/resourcemanagement.h incidenceeditor-ng/schedulingdialog.cpp incidenceeditor-ng/schedulingdialog.h incidenceeditor-ng/tests incidenceeditor-ng/visualfreebusywidget.cpp incidenceeditor-ng/visualfreebusywidget.h korganizer/akonadicollectionview.cpp korganizer/CMakeLists.txt korganizer/views libkdepim/CMakeLists.txt libkdepim/freebusymodel

Sandro Knauß knauss at kolabsys.com
Thu Oct 9 13:57:03 CEST 2014


 calendarviews/agenda/viewcalendar.cpp                    |    2 
 incidenceeditor-ng/CMakeLists.txt                        |    3 
 incidenceeditor-ng/conflictresolver.cpp                  |    2 
 incidenceeditor-ng/conflictresolver.h                    |    6 
 incidenceeditor-ng/freebusyganttproxymodel.cpp           |    2 
 incidenceeditor-ng/freebusyitem.cpp                      |   84 -
 incidenceeditor-ng/freebusyitem.h                        |   76 -
 incidenceeditor-ng/freebusyitemmodel.cpp                 |  427 ---------
 incidenceeditor-ng/freebusyitemmodel.h                   |  114 --
 incidenceeditor-ng/freeperiodmodel.cpp                   |  208 ----
 incidenceeditor-ng/freeperiodmodel.h                     |   66 -
 incidenceeditor-ng/incidenceattendee.cpp                 |    2 
 incidenceeditor-ng/resourcemanagement.cpp                |  106 --
 incidenceeditor-ng/resourcemanagement.h                  |    8 
 incidenceeditor-ng/schedulingdialog.cpp                  |    2 
 incidenceeditor-ng/schedulingdialog.h                    |    3 
 incidenceeditor-ng/tests/CMakeLists.txt                  |    3 
 incidenceeditor-ng/tests/conflictresolvertest.h          |    4 
 incidenceeditor-ng/tests/testfreebusyganttproxymodel.cpp |    4 
 incidenceeditor-ng/tests/testfreebusyitemmodel.cpp       |  241 -----
 incidenceeditor-ng/tests/testfreebusyitemmodel.h         |   33 
 incidenceeditor-ng/tests/testfreeperiodmodel.cpp         |   88 --
 incidenceeditor-ng/tests/testfreeperiodmodel.h           |   32 
 incidenceeditor-ng/visualfreebusywidget.cpp              |    2 
 incidenceeditor-ng/visualfreebusywidget.h                |    3 
 korganizer/CMakeLists.txt                                |    2 
 korganizer/akonadicollectionview.cpp                     |   27 
 korganizer/views/collectionview/calendardelegate.cpp     |   32 
 korganizer/views/collectionview/calendardelegate.h       |    3 
 korganizer/views/collectionview/controller.cpp           |   27 
 korganizer/views/collectionview/controller.h             |    3 
 korganizer/views/collectionview/quickview.cpp            |  183 ++++
 korganizer/views/collectionview/quickview.h              |   62 +
 korganizer/views/collectionview/quickview.ui             |  112 ++
 libkdepim/CMakeLists.txt                                 |    8 
 libkdepim/freebusymodel/freebusycalendar.cpp             |  160 +++
 libkdepim/freebusymodel/freebusycalendar.h               |   55 +
 libkdepim/freebusymodel/freebusyitem.cpp                 |   82 +
 libkdepim/freebusymodel/freebusyitem.h                   |   73 +
 libkdepim/freebusymodel/freebusyitemmodel.cpp            |  419 +++++++++
 libkdepim/freebusymodel/freebusyitemmodel.h              |  111 ++
 libkdepim/freebusymodel/freeperiodmodel.cpp              |  206 ++++
 libkdepim/freebusymodel/freeperiodmodel.h                |   62 +
 libkdepim/freebusymodel/tests/CMakeLists.txt             |   22 
 libkdepim/freebusymodel/tests/modeltest.cpp              |  643 +++++++++++++++
 libkdepim/freebusymodel/tests/modeltest.h                |   80 +
 libkdepim/freebusymodel/tests/testfreebusyitemmodel.cpp  |  240 +++++
 libkdepim/freebusymodel/tests/testfreebusyitemmodel.h    |   33 
 libkdepim/freebusymodel/tests/testfreeperiodmodel.cpp    |   87 ++
 libkdepim/freebusymodel/tests/testfreeperiodmodel.h      |   32 
 50 files changed, 2764 insertions(+), 1521 deletions(-)

New commits:
commit 8722a631dcd70b329f52d1060d0e1cebcb0f68ce
Author: Sandro Knauß <knauss at kolabsys.com>
Date:   Wed Oct 8 17:06:28 2014 +0200

    better errormessages

diff --git a/calendarviews/agenda/viewcalendar.cpp b/calendarviews/agenda/viewcalendar.cpp
index 595c783..aa1555d 100644
--- a/calendarviews/agenda/viewcalendar.cpp
+++ b/calendarviews/agenda/viewcalendar.cpp
@@ -134,7 +134,7 @@ Akonadi::Item AkonadiViewCalendar::item(const KCalCore::Incidence::Ptr &incidenc
 
         if (id == -1) {
             // Ok, we really don't know the ID, give up.
-            kWarning() << "Agenda::removeIncidence() Item to remove is invalid. uid = "
+            kDebug() << "Item is invalid. uid = "
                        << incidence->instanceIdentifier();
             return Akonadi::Item();
         }


commit e252dae1d9ef3d07ebd3fee788142f3f6a85f89e
Author: Sandro Knauß <knauss at kolabsys.com>
Date:   Wed Oct 8 17:04:49 2014 +0200

    Korganizer: Add quickview for person collections
    
    KOLAB: #3054

diff --git a/korganizer/CMakeLists.txt b/korganizer/CMakeLists.txt
index b1e0bb3..5c7645e 100644
--- a/korganizer/CMakeLists.txt
+++ b/korganizer/CMakeLists.txt
@@ -176,6 +176,7 @@ set(korganizerprivate_LIB_SRCS
     views/collectionview/reparentingmodel.cpp
     views/collectionview/controller.cpp
     views/collectionview/calendardelegate.cpp
+    views/collectionview/quickview.cpp
     calendarview.cpp
     datechecker.cpp
     datenavigator.cpp
@@ -224,6 +225,7 @@ set(korganizerprivate_LIB_SRCS
     publishdialog_base.ui
     searchdialog_base.ui
     timescaleedit_base.ui
+    views/collectionview/quickview.ui
   )
 
   qt4_add_resources(korganizerprivate_LIB_SRCS
diff --git a/korganizer/akonadicollectionview.cpp b/korganizer/akonadicollectionview.cpp
index c562255..0de456e 100644
--- a/korganizer/akonadicollectionview.cpp
+++ b/korganizer/akonadicollectionview.cpp
@@ -33,6 +33,8 @@
 #include "koglobals.h"
 #include "views/collectionview/reparentingmodel.h"
 #include "views/collectionview/calendardelegate.h"
+#include "views/collectionview/quickview.h"
+
 
 #include <calendarsupport/kcalprefs.h>
 #include <calendarsupport/utils.h>
@@ -349,8 +351,8 @@ class CollectionFilter : public QSortFilterProxyModel
         const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
         CollectionIdentificationAttribute *attr = col.attribute<CollectionIdentificationAttribute>();
         //We filter the user folders because we insert person nodes for user folders.
-        if (attr && ((attr->collectionNamespace() == "usertoplevel") || (attr->collectionNamespace() == "usertoplevel"))
-                || (col.name().contains(QLatin1String("Other Users")))) {
+        if ( (attr && attr->collectionNamespace().startsWith("user"))
+                || col.name().contains(QLatin1String("Other Users"))) {
             return false;
         }
         return true;
@@ -1018,7 +1020,7 @@ void AkonadiCollectionView::onAction(const QModelIndex &index, int a)
     const StyledCalendarDelegate::Action action = static_cast<StyledCalendarDelegate::Action>(a);
     switch (action) {
         case StyledCalendarDelegate::AddToList: {
-            const Akonadi::Collection col = index.data(CollectionRole).value<Akonadi::Collection>();
+            const Akonadi::Collection col = CalendarSupport::collectionFromIndex(index);
             if (col.isValid()) {
                 mController->setCollectionState(col, Controller::Referenced);
             } else {
@@ -1053,6 +1055,25 @@ void AkonadiCollectionView::onAction(const QModelIndex &index, int a)
             }
         }
         break;
+        case StyledCalendarDelegate::Quickview: {
+            QVariant person = index.data(PersonRole);
+            Akonadi::Collection col = CalendarSupport::collectionFromIndex(index);
+            QModelIndex i = index;
+            while (!person.isValid()) {
+                i = i.parent();
+                if (!i.isValid()) {
+                    break;
+                }
+                person = i.data(PersonRole);
+            }
+            if (person.isValid()) {
+                Quickview *quickview = new Quickview(person.value<Person>(), col);
+                quickview->show();
+            } else {
+                kWarning() << "No valid person found for" << index;
+            }
+        }
+        break;
     }
 }
 
diff --git a/korganizer/views/collectionview/calendardelegate.cpp b/korganizer/views/collectionview/calendardelegate.cpp
index ee29a3e..e77c9c1 100644
--- a/korganizer/views/collectionview/calendardelegate.cpp
+++ b/korganizer/views/collectionview/calendardelegate.cpp
@@ -28,6 +28,7 @@
 #include <calendarsupport/utils.h>
 #include <kohelper.h>
 #include "controller.h"
+#include <akonadi/collectionidentificationattribute.h>
 
 StyledCalendarDelegate::StyledCalendarDelegate(QObject * parent)
     : QStyledItemDelegate(parent)
@@ -35,6 +36,7 @@ StyledCalendarDelegate::StyledCalendarDelegate(QObject * parent)
     mPixmap.insert(Enable, KIconLoader().loadIcon(QLatin1String("bookmarks"), KIconLoader::Small));
     mPixmap.insert(RemoveFromList, KIconLoader().loadIcon(QLatin1String("list-remove"), KIconLoader::Small));
     mPixmap.insert(AddToList, KIconLoader().loadIcon(QLatin1String("list-add"), KIconLoader::Small));
+    mPixmap.insert(Quickview, KIconLoader().loadIcon(QLatin1String("quickview"), KIconLoader::Small));
 }
 
 StyledCalendarDelegate::~StyledCalendarDelegate()
@@ -94,6 +96,11 @@ QList<StyledCalendarDelegate::Action> StyledCalendarDelegate::getActions(const Q
     // kDebug() << index.data().toString() << enabled;
 
     QList<Action> buttons;
+
+    CollectionIdentificationAttribute *attr = col.attribute<CollectionIdentificationAttribute>();
+    if (attr && attr->collectionNamespace().startsWith("user")) {
+        buttons << Quickview;
+    }
     if (isSearchResult) {
         buttons << AddToList;
     } else {
@@ -181,14 +188,11 @@ bool StyledCalendarDelegate::editorEvent(QEvent *event,
 
         QMouseEvent *me = static_cast<QMouseEvent*>(event);
 
-        if (enableButtonRect(option.rect, 1).contains(me->pos())) {
-            button = 1;
-        }
-        if (enableButtonRect(option.rect, 2).contains(me->pos())) {
-            button = 2;
-        }
-        if (enableButtonRect(option.rect, 3).contains(me->pos())) {
-            button = 3;
+        for (int i = 1; i < 4; i++) {
+            if (enableButtonRect(option.rect, i).contains(me->pos())) {
+                button = i;
+                break;
+            }
         }
         if (me->button() != Qt::LeftButton || button < 0) {
             return QStyledItemDelegate::editorEvent(event, model, option, index);
@@ -206,11 +210,13 @@ bool StyledCalendarDelegate::editorEvent(QEvent *event,
     QStyleOptionViewItem opt = option;
     opt.state |= QStyle::State_MouseOver;
 
-    const Action a = getActions(opt, index).at(button - 1);
-    // kDebug() << "Button clicked: " << a;
-    emit action(index, a);
-
-    return true;
+    QList<StyledCalendarDelegate::Action> actions = getActions(opt, index);
+    if (actions.count() >= button) {
+        const Action a = actions.at(button - 1);
+        emit action(index, a);
+        return true;
+    }
+    return QStyledItemDelegate::editorEvent(event, model, option, index);
 }
 
 QSize StyledCalendarDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
diff --git a/korganizer/views/collectionview/calendardelegate.h b/korganizer/views/collectionview/calendardelegate.h
index 788a1d3..0be37f6 100644
--- a/korganizer/views/collectionview/calendardelegate.h
+++ b/korganizer/views/collectionview/calendardelegate.h
@@ -35,7 +35,8 @@ public:
     enum Action {
         AddToList,
         RemoveFromList,
-        Enable
+        Enable,
+        Quickview
     };
 
 Q_SIGNALS:
diff --git a/korganizer/views/collectionview/controller.cpp b/korganizer/views/collectionview/controller.cpp
index 8f7808c..7c83a3c 100644
--- a/korganizer/views/collectionview/controller.cpp
+++ b/korganizer/views/collectionview/controller.cpp
@@ -111,15 +111,17 @@ bool CollectionNode::isDuplicateOf(const QModelIndex& sourceIndex)
 }
 
 
-PersonNode::PersonNode(ReparentingModel& personModel, const Person& person)
+PersonNode::PersonNode(ReparentingModel& personModel, const Person& person, const QModelIndex &colIndex)
 :   Node(personModel),
     mPerson(person),
     mCheckState(Qt::Unchecked),
-    isSearchNode(false)
+    isSearchNode(false),
+    mCollectionIndex(colIndex)
 {
 
 }
 
+
 PersonNode::~PersonNode()
 {
 
@@ -180,7 +182,12 @@ QVariant PersonNode::data(int role) const
     if (role == NodeTypeRole) {
         return PersonNodeRole;
     }
-    return QVariant();
+
+    if (mCollectionIndex.isValid()) {
+        return mCollectionIndex.data(role);
+    } else {
+        return QVariant();
+    }
 }
 
 bool PersonNode::setData(const QVariant& value, int role)
@@ -213,7 +220,7 @@ bool PersonNode::adopts(const QModelIndex& sourceIndex)
 
 bool PersonNode::isDuplicateOf(const QModelIndex& sourceIndex)
 {
-    return (sourceIndex.data(PersonRole).value<Person>().name == mPerson.name);
+    return (sourceIndex.data(PersonRole).value<Person>().uid == mPerson.uid);
 }
 
 void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
@@ -231,7 +238,7 @@ void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
             person.uid = col.name();
             person.rootCollection = col.id();
 
-            model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, person)));
+            model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, person, sourceIndex)));
         }
     }
 }
@@ -250,7 +257,7 @@ void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex)
             person.ou = QString::fromUtf8(attr->ou());
             person.uid = col.name();
             person.rootCollection = col.id();
-            model.removeNode(PersonNode(model, person));
+            model.removeNode(PersonNode(model, person, sourceIndex));
         }
     }
 }
@@ -649,7 +656,7 @@ void Controller::onCollectionsFound(KJob* job)
 void Controller::onPersonsFound(const QList<Person> &persons)
 {
     Q_FOREACH(const Person &p, persons) {
-        PersonNode *personNode = new PersonNode(*mSearchModel, p);
+        PersonNode *personNode = new PersonNode(*mSearchModel, p, QModelIndex());
         personNode->isSearchNode = true;
         //toggled by the checkbox, results in person getting added to main model
         // connect(&personNode->emitter, SIGNAL(enabled(bool, Person)), this, SLOT(onPersonEnabled(bool, Person)));
@@ -659,7 +666,7 @@ void Controller::onPersonsFound(const QList<Person> &persons)
 
 void Controller::onPersonUpdate(const Person &person)
 {
-    PersonNode *personNode = new PersonNode(*mSearchModel, person);
+    PersonNode *personNode = new PersonNode(*mSearchModel, person, QModelIndex());
     personNode->isSearchNode = true;
     mSearchModel->updateNode(ReparentingModel::Node::Ptr(personNode));
 }
@@ -755,7 +762,7 @@ void Controller::addPerson(const Person &person)
         //TODO: use the found collection and update attribute
     }
 
-    PersonNode *personNode = new PersonNode(*mPersonModel, person);
+    PersonNode *personNode = new PersonNode(*mPersonModel, person, QModelIndex());
     personNode->setChecked(true);
     mPersonModel->addNode(ReparentingModel::Node::Ptr(personNode));
 
@@ -770,7 +777,7 @@ void Controller::addPerson(const Person &person)
 void Controller::removePerson(const Person &person)
 {
     kDebug() << person.uid << person.name << person.rootCollection;
-    mPersonModel->removeNode(PersonNode(*mPersonModel, person));
+    mPersonModel->removeNode(PersonNode(*mPersonModel, person, QModelIndex()));
 
     if (person.rootCollection > -1) {
         setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true);
diff --git a/korganizer/views/collectionview/controller.h b/korganizer/views/collectionview/controller.h
index 916078e..0f404a0 100644
--- a/korganizer/views/collectionview/controller.h
+++ b/korganizer/views/collectionview/controller.h
@@ -89,7 +89,7 @@ public:
 class PersonNode : public ReparentingModel::Node
 {
 public:
-    PersonNode(ReparentingModel &personModel, const Person &person);
+    PersonNode(ReparentingModel &personModel, const Person &person, const QModelIndex &colIndex);
     virtual ~PersonNode();
     virtual bool operator==(const Node &) const;
 
@@ -107,6 +107,7 @@ private:
 
     Person mPerson;
     Qt::CheckState mCheckState;
+    QModelIndex mCollectionIndex;
 };
 
 class CollectionNode : public ReparentingModel::Node
diff --git a/korganizer/views/collectionview/quickview.cpp b/korganizer/views/collectionview/quickview.cpp
new file mode 100644
index 0000000..f9a7963
--- /dev/null
+++ b/korganizer/views/collectionview/quickview.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2014  Sandro Knauß <knauss at kolabsys.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//http://stackoverflow.com/questions/18831242/qt-start-editing-of-cell-after-one-click
+
+#include "quickview.h"
+#include "ui_quickview.h"
+
+#include <KCalCore/Event>
+#include <KCalCore/FreeBusy>
+#include <KCalCore/MemoryCalendar>
+#include <akonadi/entitydisplayattribute.h>
+#include <akonadi/changerecorder.h>
+#include <akonadi/itemfetchscope.h>
+
+#include <calendarviews/agenda/agendaview.h>
+#include <calendarviews/agenda/viewcalendar.h>
+#include <calendarsupport/calendarsingleton.h>
+
+#include <freebusymodel/freebusycalendar.h>
+
+#include <KDebug>
+#include <KSystemTimeZones>
+#include <KCheckableProxyModel>
+
+class FreebusyViewCalendar : public EventViews::ViewCalendar
+{
+public:
+    virtual ~FreebusyViewCalendar() {};
+    virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const
+    {
+        return incidence->uid().startsWith(QLatin1String("fb-"));
+    }
+
+    virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const
+    {
+        Q_UNUSED(incidence);
+        return i18n("Freebusycalendar from %1").arg(name);
+    }
+
+    virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const
+    {
+        bool ok = false;
+        int status = incidence->customProperty("FREEBUSY", "STATUS").toInt(&ok);
+
+        if (!ok) {
+            return QColor("#555");
+        }
+
+        switch (status) {
+        case KCalCore::FreeBusyPeriod::Busy:
+            return QColor("#f00");
+        case KCalCore::FreeBusyPeriod::BusyTentative:
+        case KCalCore::FreeBusyPeriod::BusyUnavailable:
+            return QColor("#f70");
+        case KCalCore::FreeBusyPeriod::Free:
+            return QColor("#0f0");
+        default:
+            return QColor("#555");
+        }
+    }
+
+    virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const
+    {
+        return QString();
+    }
+
+    virtual KCalCore::Calendar::Ptr getCalendar() const
+    {
+        return mCalendar;
+    }
+
+    KCalCore::Calendar::Ptr mCalendar;
+    QString name;
+};
+
+Quickview::Quickview(const Person &person, const Akonadi::Collection &col)
+    : mPerson(person)
+    , mCollection(col)
+    , mDayRange(7)
+{
+    mUi = new Ui_quickview;
+
+    QWidget *w = new QWidget( this );
+    mUi->setupUi( w );
+    setMainWidget( w );
+
+    mAgendaView = new EventViews::AgendaView(QDate(), QDate(), false,  false);
+
+    //show fbcalendar for person in quickview
+    if (!person.mail.isEmpty()) {
+        FreeBusyItemModel *model = new FreeBusyItemModel(this);
+        FreeBusyCalendar *fbCal = new FreeBusyCalendar(this);
+        FreebusyViewCalendar *fbCalendar = new FreebusyViewCalendar();
+        KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(person.name,  person.mail));
+        FreeBusyItem::Ptr freebusy( new FreeBusyItem( attendee, this ));
+
+        fbCal->setModel(model);
+        model->addItem(freebusy);
+        fbCalendar->mCalendar = fbCal->calendar();
+        fbCalendar->name = attendee->fullName();
+        mAgendaView->addCalendar(EventViews::ViewCalendar::Ptr(fbCalendar));
+    }
+
+    if (mCollection.isValid()) {
+        //create etm for mCollection
+        Akonadi::ChangeRecorder *monitor = new Akonadi::ChangeRecorder(this);
+        Akonadi::ItemFetchScope scope;
+        QStringList allMimeTypes;
+
+        allMimeTypes << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType()
+                     << KCalCore::Journal::journalMimeType();
+
+        scope.fetchFullPayload(true);
+        scope.fetchAttribute<Akonadi::EntityDisplayAttribute>();
+
+        monitor->setCollectionMonitored(mCollection);
+        monitor->fetchCollection(true);
+        monitor->setItemFetchScope(scope);
+        monitor->setAllMonitored(true);
+
+        foreach(const QString &mimetype, allMimeTypes) {
+            monitor->setMimeTypeMonitored(mimetype, true);
+        }
+
+        Akonadi::ETMCalendar::Ptr calendar = Akonadi::ETMCalendar::Ptr(new Akonadi::ETMCalendar(monitor));
+
+        calendar->setCollectionFilteringEnabled(false);
+        mAgendaView->setCalendar(calendar);
+    }
+    mUi->calender->addWidget( mAgendaView );
+
+    connect(mUi->mTodayBtn, SIGNAL(clicked(bool)), SLOT(onTodayClicked()));
+    connect(mUi->mNextBtn, SIGNAL(clicked(bool)), SLOT(onNextClicked()));
+    connect(mUi->mPreviousBtn, SIGNAL(clicked(bool)), SLOT(onPreviousClicked()));
+
+    onTodayClicked();
+}
+
+Quickview::~Quickview()
+{
+
+}
+
+void Quickview::onNextClicked()
+{
+    QDate start = mAgendaView->startDate().addDays(mDayRange);
+    mAgendaView->showDates(start, start.addDays(mDayRange-1));
+}
+
+void Quickview::onPreviousClicked()
+{
+    QDate start = mAgendaView->startDate().addDays(-mDayRange);
+    mAgendaView->showDates(start, start.addDays(mDayRange-1));
+}
+
+void Quickview::onTodayClicked()
+{
+    QDate start = QDate::currentDate();
+    start = start.addDays(-QDate::currentDate().dayOfWeek()+1);
+    mAgendaView->showDates(start, start.addDays(mDayRange-1));
+}
+
+
+#include "quickview.moc"
diff --git a/korganizer/views/collectionview/quickview.h b/korganizer/views/collectionview/quickview.h
new file mode 100644
index 0000000..d296887
--- /dev/null
+++ b/korganizer/views/collectionview/quickview.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014  Sandro Knauß <knauss at kolabsys.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef KORG_QUICKVIEW_H
+#define KORG_QUICKVIEW_H
+
+#include "controller.h"
+
+#include <calendarviews/agenda/viewcalendar.h>
+
+#include <KCalCore/FreeBusy>
+#include <KDialog>
+
+#include <QStringList>
+#include <QStringListModel>
+
+class  Ui_quickview;
+
+namespace EventViews
+{
+    class AgendaView;
+}
+
+class Quickview : public KDialog
+{
+    Q_OBJECT
+public:
+    Quickview(const Person &person, const Akonadi::Collection &col);
+    virtual ~Quickview();
+
+private slots:
+    void onTodayClicked();
+    void onNextClicked();
+    void onPreviousClicked();
+
+private:
+    Ui_quickview *mUi;
+    EventViews::AgendaView *mAgendaView;
+    Person mPerson;
+    Akonadi::Collection mCollection;
+    int mDayRange;
+};
+
+#endif // QUICKVIEW_H
diff --git a/korganizer/views/collectionview/quickview.ui b/korganizer/views/collectionview/quickview.ui
new file mode 100644
index 0000000..8854b60
--- /dev/null
+++ b/korganizer/views/collectionview/quickview.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>quickview</class>
+ <widget class="QWidget" name="quickview">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>552</width>
+    <height>566</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="mDayBtn">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Day</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mWeekBtn">
+       <property name="text">
+        <string>Week</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mMothBtn">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Month</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mAgendaBtn">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Agenda</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mPreviousBtn">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="text">
+        <string><</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mTodayBtn">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="text">
+        <string>Today</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="mNextBtn">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="text">
+        <string>></string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="calender">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>


commit 17d17a084b69bbae1a2f1421c19fd2f5adabb981
Author: Sandro Knauß <knauss at kolabsys.com>
Date:   Wed Oct 8 16:58:16 2014 +0200

    Moving freebusy model to libkdepim to reuse it for quickview.
    
    KOLAB: #3122

diff --git a/incidenceeditor-ng/CMakeLists.txt b/incidenceeditor-ng/CMakeLists.txt
index eb53e5b..0659bf0 100644
--- a/incidenceeditor-ng/CMakeLists.txt
+++ b/incidenceeditor-ng/CMakeLists.txt
@@ -47,10 +47,7 @@ set(incidenceeditors_ng_shared_LIB_SRCS
   incidenceresource.cpp
   incidencesecrecy.cpp
 
-  freebusyitem.cpp
-  freebusyitemmodel.cpp
   freebusyganttproxymodel.cpp
-  freeperiodmodel.cpp
   conflictresolver.cpp
   schedulingdialog.cpp
   groupwareuidelegate.cpp
diff --git a/incidenceeditor-ng/conflictresolver.cpp b/incidenceeditor-ng/conflictresolver.cpp
index 38b7c56..e8691a1 100644
--- a/incidenceeditor-ng/conflictresolver.cpp
+++ b/incidenceeditor-ng/conflictresolver.cpp
@@ -21,7 +21,7 @@
 */
 
 #include "conflictresolver.h"
-#include "freebusyitemmodel.h"
+#include "freebusymodel/freebusyitemmodel.h"
 
 #include <KCalendarSystem>
 #include <KDebug>
diff --git a/incidenceeditor-ng/conflictresolver.h b/incidenceeditor-ng/conflictresolver.h
index 82e3708..d01f202 100644
--- a/incidenceeditor-ng/conflictresolver.h
+++ b/incidenceeditor-ng/conflictresolver.h
@@ -24,16 +24,16 @@
 #define INCIDENCEEDITOR_CONFLICTRESOLVER_H
 
 #include "incidenceeditors-ng_export.h"
-#include "freebusyitem.h"
+#include "freebusymodel/freebusyitem.h"
 
 #include <QBitArray>
 #include <QSet>
 #include <QTimer>
 
-namespace IncidenceEditorNG {
-
 class FreeBusyItemModel;
 
+namespace IncidenceEditorNG {
+
 /**
  * Takes a list of attendees and event info (e.g., min time start, max time end)
  * fetches their freebusy information, then identifies conflicts and periods of non-conflict.
diff --git a/incidenceeditor-ng/freebusyganttproxymodel.cpp b/incidenceeditor-ng/freebusyganttproxymodel.cpp
index 7b5bc21..25cbc03 100644
--- a/incidenceeditor-ng/freebusyganttproxymodel.cpp
+++ b/incidenceeditor-ng/freebusyganttproxymodel.cpp
@@ -19,7 +19,7 @@
 */
 
 #include "freebusyganttproxymodel.h"
-#include "freebusyitemmodel.h"
+#include "freebusymodel/freebusyitemmodel.h"
 
 #include <kdgantt2/kdganttgraphicsview.h>
 
diff --git a/incidenceeditor-ng/freebusyitem.cpp b/incidenceeditor-ng/freebusyitem.cpp
deleted file mode 100644
index 8752a51..0000000
--- a/incidenceeditor-ng/freebusyitem.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-  Copyright (c) 2000,2001,2004 Cornelius Schumacher <schumacher at kde.org>
-  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info at kdab.com
-  Copyright (c) 2010 Andras Mantia <andras at kdab.com>
-  Copyright (C) 2010 Casey Link <casey at kdab.com>
-
-  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 "freebusyitem.h"
-
-#include <akonadi/calendar/freebusymanager.h>
-
-using namespace IncidenceEditorNG;
-
-FreeBusyItem::FreeBusyItem( const KCalCore::Attendee::Ptr &attendee, QWidget *parentWidget )
-  : mAttendee( attendee ), mTimerID( 0 ), mIsDownloading( false ), mParentWidget( parentWidget )
-{
-  Q_ASSERT( attendee );
-  setFreeBusy( KCalCore::FreeBusy::Ptr() );
-}
-
-KCalCore::Attendee::Ptr FreeBusyItem::attendee() const
-{
-  return mAttendee;
-}
-
-void FreeBusyItem::setFreeBusy( const KCalCore::FreeBusy::Ptr &fb )
-{
-  mFreeBusy = fb;
-  mIsDownloading = false;
-}
-
-KCalCore::FreeBusy::Ptr FreeBusyItem::freeBusy() const
-{
-  return mFreeBusy;
-}
-
-QString FreeBusyItem::email() const
-{
-  return mAttendee->email();
-}
-
-void FreeBusyItem::setUpdateTimerID( int id )
-{
-  mTimerID = id;
-}
-
-int FreeBusyItem::updateTimerID() const
-{
-  return mTimerID;
-}
-
-void FreeBusyItem::startDownload( bool forceDownload )
-{
-  mIsDownloading = true;
-  Akonadi::FreeBusyManager *m = Akonadi::FreeBusyManager::self();
-  if ( !m->retrieveFreeBusy( attendee()->email(), forceDownload, mParentWidget ) ) {
-    mIsDownloading = false;
-  }
-}
-
-void FreeBusyItem::setIsDownloading( bool d )
-{
-  mIsDownloading = d;
-}
-
-bool FreeBusyItem::isDownloading() const
-{
-  return mIsDownloading;
-}
diff --git a/incidenceeditor-ng/freebusyitem.h b/incidenceeditor-ng/freebusyitem.h
deleted file mode 100644
index 036adbb..0000000
--- a/incidenceeditor-ng/freebusyitem.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-  Copyright (c) 2000,2001,2004 Cornelius Schumacher <schumacher at kde.org>
-  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info at kdab.com
-  Copyright (c) 2010 Andras Mantia <andras at kdab.com>
-  Copyright (C) 2010 Casey Link <casey at kdab.com>
-
-  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 INCIDENCEEDITOR_FREEBUSYITEM_H
-#define INCIDENCEEDITOR_FREEBUSYITEM_H
-
-#include "incidenceeditors-ng_export.h"
-
-#include <KCalCore/FreeBusy>
-
-namespace IncidenceEditorNG {
-
-/**
- * The FreeBusyItem is the whole line for a given attendee..
- */
-class INCIDENCEEDITORS_NG_EXPORT FreeBusyItem
-{
-  public:
-    typedef QSharedPointer<FreeBusyItem> Ptr;
-
-    /**
-     * @param parentWidget is passed to Akonadi when fetching free/busy data.
-     */
-    FreeBusyItem( const KCalCore::Attendee::Ptr &attendee, QWidget *parentWidget );
-    ~FreeBusyItem() {}
-
-    KCalCore::Attendee::Ptr attendee() const;
-    void setFreeBusy( const KCalCore::FreeBusy::Ptr &fb );
-    KCalCore::FreeBusy::Ptr freeBusy() const;
-
-    QString email() const;
-    void setUpdateTimerID( int id );
-    int updateTimerID() const;
-
-    void startDownload( bool forceDownload );
-    void setIsDownloading( bool d );
-    bool isDownloading() const;
-
-  signals:
-    void attendeeChanged( const KCalCore::Attendee::Ptr &attendee );
-    void freebusyChanged( const KCalCore::FreeBusy::Ptr fb );
-
-  private:
-    KCalCore::Attendee::Ptr mAttendee;
-    KCalCore::FreeBusy::Ptr mFreeBusy;
-
-    // This is used for the update timer
-    int mTimerID;
-
-    // Only run one download job at a time
-    bool mIsDownloading;
-
-    QWidget *mParentWidget;
-};
-
-}
-#endif
diff --git a/incidenceeditor-ng/freebusyitemmodel.cpp b/incidenceeditor-ng/freebusyitemmodel.cpp
deleted file mode 100644
index 62141f9..0000000
--- a/incidenceeditor-ng/freebusyitemmodel.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 "freebusyitemmodel.h"
-
-#include <akonadi/calendar/freebusymanager.h>
-
-#include <KDebug>
-#include <KGlobal>
-#include <KLocalizedString>
-#include <KLocale>
-
-#include <QTimerEvent>
-
-using namespace IncidenceEditorNG;
-
-namespace IncidenceEditorNG {
-
-class ItemPrivateData
-{
-  public:
-    ItemPrivateData( ItemPrivateData *parent ) : parentItem( parent )
-    {
-    }
-
-    ~ItemPrivateData()
-    {
-      qDeleteAll( childItems );
-    }
-
-    ItemPrivateData *child( int row )
-    {
-      return childItems.value( row );
-    }
-
-    void appendChild( ItemPrivateData *item )
-    {
-      childItems.append(item);
-    }
-
-    ItemPrivateData *removeChild( int row )
-    {
-      return childItems.takeAt( row );
-    }
-
-    int childCount() const
-    {
-      return childItems.count();
-    }
-
-    int row() const
-    {
-      if ( parentItem ) {
-        return parentItem->childItems.indexOf( const_cast<ItemPrivateData*>(this) );
-      }
-      return 0;
-    }
-
-    ItemPrivateData *parent()
-    {
-      return parentItem;
-    }
-
-  private:
-    QList<ItemPrivateData*> childItems;
-    ItemPrivateData *parentItem;
-};
-
-}
-
-FreeBusyItemModel::FreeBusyItemModel( QObject *parent )
-  : QAbstractItemModel( parent ), mForceDownload( false )
-{
-  qRegisterMetaType<KCalCore::Attendee::Ptr>( "KCalCore::Attendee::Ptr" );
-  qRegisterMetaType<KCalCore::FreeBusy::Ptr>( "KCalCore::FreeBusy::Ptr" );
-  qRegisterMetaType<KCalCore::Period>( "KCalCore::Period" );
-
-  Akonadi::FreeBusyManager *m = Akonadi::FreeBusyManager::self();
-  connect( m, SIGNAL(freeBusyRetrieved(KCalCore::FreeBusy::Ptr,QString)),
-           SLOT(slotInsertFreeBusy(KCalCore::FreeBusy::Ptr,QString)) );
-
-  connect( &mReloadTimer, SIGNAL(timeout()), SLOT(autoReload()) );
-  mReloadTimer.setSingleShot( true );
-
-  mRootData = new ItemPrivateData( 0 );
-}
-
-FreeBusyItemModel::~FreeBusyItemModel()
-{
-  delete mRootData;
-}
-
-QVariant FreeBusyItemModel::data( const QModelIndex &index, int role ) const
-{
-  if ( !index.isValid() ) {
-    return QVariant();
-  }
-
-  ItemPrivateData *data = (ItemPrivateData*) index.internalPointer();
-
-  if ( data->parent() == mRootData ) {
-    int row = index.row();
-    if ( row >= mFreeBusyItems.size() ) {
-      return QVariant();
-    }
-
-    switch( role ) {
-    case Qt::DisplayRole:
-      return mFreeBusyItems.at( row )->attendee()->fullName();
-    case FreeBusyItemModel::AttendeeRole:
-      return QVariant::fromValue( mFreeBusyItems.at( row )->attendee() );
-    case FreeBusyItemModel::FreeBusyRole:
-      if ( mFreeBusyItems.at( row )->freeBusy() ) {
-        return QVariant::fromValue( mFreeBusyItems.at( row )->freeBusy() );
-      } else {
-        return QVariant();
-      }
-    default:
-      return QVariant();
-    }
-  }
-
-  FreeBusyItem::Ptr fbitem = mFreeBusyItems.at( data->parent()->row() );
-  if ( !fbitem->freeBusy() || index.row() >= fbitem->freeBusy()->busyPeriods().size() ) {
-    return QVariant();
-  }
-
-  KCalCore::FreeBusyPeriod period = fbitem->freeBusy()->fullBusyPeriods().at( index.row() );
-  switch( role ) {
-  case Qt::DisplayRole: // return something to make modeltest happy
-    return QString( "%1 - %2" ).
-      arg( KGlobal::locale()->formatDateTime( period.start() ) ).
-      arg( KGlobal::locale()->formatDateTime( period.end() ) );
-  case FreeBusyItemModel::FreeBusyPeriodRole:
-    return QVariant::fromValue( period );
-  default:
-    return QVariant();
-  }
-}
-
-int FreeBusyItemModel::rowCount( const QModelIndex &parent ) const
-{
-  ItemPrivateData *parentData;
-  if ( parent.column() > 0 ) {
-    return 0;
-  }
-
-  if ( !parent.isValid() ) {
-    parentData = mRootData;
-  } else {
-    parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
-  }
-
-  return parentData->childCount();
-}
-
-int FreeBusyItemModel::columnCount( const QModelIndex &parent ) const
-{
-  Q_UNUSED( parent );
-  return 1;
-}
-
-QModelIndex FreeBusyItemModel::index( int row, int column, const QModelIndex &parent ) const
-{
-  if ( !hasIndex( row, column, parent ) ) {
-    return QModelIndex();
-  }
-
-  ItemPrivateData *parentData;
-  if ( !parent.isValid() ) {
-    parentData = mRootData;
-  } else {
-    parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
-  }
-
-  ItemPrivateData *childData = parentData->child( row );
-  if ( childData ) {
-    return createIndex( row, column, childData );
-  } else {
-    return QModelIndex();
-  }
-//  FreeBusyItem::Ptr item = mFreeBusyItems.at( parent.row() );
-//  KCalCore::FreeBusy::Ptr fb = item->freeBusy();
-//  if( !fb )
-//    return QModelIndex();
-//
-//  QList<KCalCore::FreeBusyPeriod> busyPeriods = fb->fullBusyPeriods();
-//  if( row < busyPeriods.size() )
-//    return createIndex( row, column, new ItemPrivateData( parent.row() ) );
-//  else
-//    return QModelIndex();
-//  }
-}
-
-QModelIndex FreeBusyItemModel::parent( const QModelIndex &child ) const
-{
-  if ( !child.isValid() ) {
-    return QModelIndex();
-  }
-
-  ItemPrivateData *childData = static_cast<ItemPrivateData*>( child.internalPointer() );
-  ItemPrivateData *parentData = childData->parent();
-  if ( parentData == mRootData ) {
-    return QModelIndex();
-  }
-
-  return createIndex( parentData->row(), 0, parentData );
-}
-
-QVariant FreeBusyItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
-{
-  if ( role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0 ) {
-    return i18n( "Attendee" );
-  }
-  return QVariant();
-}
-
-void FreeBusyItemModel::addItem( const IncidenceEditorNG::FreeBusyItem::Ptr &freebusy )
-{
-  kDebug() << freebusy->attendee()->fullName();
-  int row = mFreeBusyItems.size();
-  beginInsertRows( QModelIndex(), row, row );
-  mFreeBusyItems.append( freebusy );
-  ItemPrivateData *data = new ItemPrivateData( mRootData );
-  mRootData->appendChild( data );
-  endInsertRows();
-
-  if ( freebusy->freeBusy() && freebusy->freeBusy()->fullBusyPeriods().size() > 0 ) {
-    QModelIndex parent = index( row, 0 );
-    setFreeBusyPeriods( parent, freebusy->freeBusy()->fullBusyPeriods() );
-  }
-  updateFreeBusyData( freebusy );
-}
-
-void FreeBusyItemModel::setFreeBusyPeriods( const QModelIndex &parent,
-                                            const KCalCore::FreeBusyPeriod::List &list )
-{
-  if(!parent.isValid())
-    return;
-
-  ItemPrivateData *parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
-  QModelIndex first = index( 0, 0, parent );
-  QModelIndex last = index( parentData->childCount() - 1, 0, parent );
-  int fb_count = list.size();
-  int childCount = parentData->childCount();
-
-  if ( childCount > 0 && fb_count < childCount ) {
-      beginRemoveRows( parent, fb_count-1<0 ? 0 : fb_count-1 , childCount - 1 );
-    for ( int i = childCount - 1; i > fb_count; --i ) {
-      delete parentData->removeChild( i );
-    }
-    endRemoveRows();
-    if (fb_count > 0) {
-        last = index(fb_count-1, 0, parent);
-        emit dataChanged(first, last);
-    }
-  } else if (fb_count > childCount) {
-      beginInsertRows( parent, childCount-1 < 0 ? 0 : childCount-1, fb_count - 1 );
-      for ( int i=childCount-1; i < fb_count; ++i ) {
-        ItemPrivateData *childData= new ItemPrivateData( parentData );
-        parentData->appendChild( childData );
-      }
-      endInsertRows();
-      if (childCount > 0) {
-          last = index(childCount-1, 0, parent);
-          emit dataChanged(first, last);
-      }
-  } else if (fb_count == childCount && fb_count > 0) {
-      emit dataChanged( first, last );
-  }
-}
-
-void FreeBusyItemModel::clear()
-{
-  beginResetModel();
-  mFreeBusyItems.clear();
-  delete mRootData;
-  mRootData = new ItemPrivateData( 0 );
-  endResetModel();
-}
-
-void IncidenceEditorNG::FreeBusyItemModel::removeRow( int row )
-{
-  beginRemoveRows( QModelIndex(), row, row );
-  mFreeBusyItems.removeAt( row );
-  ItemPrivateData *data = mRootData->removeChild( row );
-  delete data;
-  endRemoveRows();
-}
-
-void FreeBusyItemModel::removeItem( const IncidenceEditorNG::FreeBusyItem::Ptr &freebusy )
-{
-  int row = mFreeBusyItems.indexOf( freebusy );
-  if( row >= 0 ) {
-    removeRow( row );
-  }
-}
-
-void FreeBusyItemModel::removeAttendee( const KCalCore::Attendee::Ptr &attendee )
-{
-  FreeBusyItem::Ptr anItem;
-  for ( int i = 0; i < mFreeBusyItems.count(); ++i ) {
-    anItem = mFreeBusyItems[i];
-    if ( *anItem->attendee() == *attendee ) {
-      if ( anItem->updateTimerID() != 0 ) {
-        killTimer( anItem->updateTimerID() );
-      }
-      removeRow( i );
-      break;
-    }
-  }
-}
-
-bool FreeBusyItemModel::containsAttendee( const KCalCore::Attendee::Ptr &attendee )
-{
-  FreeBusyItem::Ptr anItem;
-  for ( int i = 0; i < mFreeBusyItems.count(); ++i ) {
-    anItem = mFreeBusyItems[i];
-    if ( *anItem->attendee() == *attendee ) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void FreeBusyItemModel::updateFreeBusyData( const FreeBusyItem::Ptr &item )
-{
-  if ( item->isDownloading() ) {
-    // This item is already in the process of fetching the FB list
-    return;
-  }
-
-  if ( item->updateTimerID() != 0 ) {
-    // An update timer is already running. Reset it
-    killTimer( item->updateTimerID() );
-  }
-
-  // This item does not have a download running, and no timer is set
-  // Do the download in one second
-  item->setUpdateTimerID( startTimer( 1000 ) );
-}
-
-void FreeBusyItemModel::timerEvent( QTimerEvent *event )
-{
-  killTimer( event->timerId() );
-  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
-    if ( item->updateTimerID() == event->timerId() ) {
-      item->setUpdateTimerID( 0 );
-      item->startDownload( mForceDownload );
-      return;
-    }
-  }
-}
-
-void FreeBusyItemModel::slotInsertFreeBusy( const KCalCore::FreeBusy::Ptr &fb,
-                                            const QString &email )
-{
-  if ( !fb ) {
-    return;
-  }
-
-  if ( fb->fullBusyPeriods().isEmpty() ) {
-    return;
-  }
-
-  fb->sortList();
-
-  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
-    if ( item->email() == email ) {
-      item->setFreeBusy( fb );
-      const int row = mFreeBusyItems.indexOf( item );
-      const QModelIndex parent = index( row, 0 );
-      emit dataChanged(parent, parent);
-      setFreeBusyPeriods( parent, fb->fullBusyPeriods() );
-    }
-  }
-}
-
-void FreeBusyItemModel::autoReload()
-{
-  mForceDownload = false;
-  reload();
-}
-
-void FreeBusyItemModel::reload()
-{
-  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
-    if ( mForceDownload ) {
-      item->startDownload( mForceDownload );
-    } else {
-      updateFreeBusyData( item );
-    }
-  }
-}
-
-void FreeBusyItemModel::triggerReload()
-{
-  mReloadTimer.start( 1000 );
-}
-
-void FreeBusyItemModel::cancelReload()
-{
-  mReloadTimer.stop();
-}
-
-void FreeBusyItemModel::manualReload()
-{
-  mForceDownload = true;
-  reload();
-}
diff --git a/incidenceeditor-ng/freebusyitemmodel.h b/incidenceeditor-ng/freebusyitemmodel.h
deleted file mode 100644
index 8123b8e..0000000
--- a/incidenceeditor-ng/freebusyitemmodel.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 INCIDENCEEDITOR_FREEBUSYITEMMODEL_H
-#define INCIDENCEEDITOR_FREEBUSYITEMMODEL_H
-
-#include "incidenceeditors-ng_export.h"
-#include "freebusyitem.h"
-
-#include <QAbstractItemModel>
-#include <QTimer>
-
-namespace IncidenceEditorNG {
-
-class ItemPrivateData;
-
-/**
- * The FreeBusyItemModel is a 2-level tree structure.
- *
- * The top level parent nodes represent the freebusy items, and
- * the 2nd-level child nodes represent the FreeBusyPeriods of the parent
- * freebusy item.
- */
-class INCIDENCEEDITORS_NG_EXPORT FreeBusyItemModel : public QAbstractItemModel
-{
-  Q_OBJECT
-  public:
-    enum Roles {
-        AttendeeRole = Qt::UserRole,
-        FreeBusyRole,
-        FreeBusyPeriodRole
-    };
-
-    explicit FreeBusyItemModel( QObject *parent = 0 );
-    virtual ~FreeBusyItemModel();
-
-    virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
-    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
-    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
-    virtual QModelIndex index( int row, int column = 0,
-                               const QModelIndex &parent = QModelIndex() ) const;
-    virtual QModelIndex parent( const QModelIndex &child ) const;
-    virtual QVariant headerData( int section, Qt::Orientation orientation,
-                                 int role = Qt::DisplayRole ) const;
-
-    void addItem( const FreeBusyItem::Ptr &freebusy );
-
-    void clear();
-    void removeAttendee( const KCalCore::Attendee::Ptr &attendee );
-    void removeItem( const FreeBusyItem::Ptr &freebusy );
-    void removeRow( int row );
-
-    bool containsAttendee( const KCalCore::Attendee::Ptr &attendee );
-
-    /**
-     * Queues a reload of free/busy data.
-     * All current attendees will have their free/busy data
-     * redownloaded from Akonadi.
-     */
-    void triggerReload();
-
-    /**
-     * cancel reloading
-     */
-    void cancelReload();
-
-    /**
-     * Reload FB items
-     */
-    void reload();
-
-  public slots:
-    void slotInsertFreeBusy( const KCalCore::FreeBusy::Ptr &fb, const QString &email );
-
-  protected:
-    void timerEvent( QTimerEvent * );
-
-  private slots:
-    // Force the download of FB information
-    void manualReload();
-    // Only download FB if the auto-download option is set in config
-    void autoReload();
-
-  private:
-    void setFreeBusyPeriods( const QModelIndex &parent,
-                             const KCalCore::FreeBusyPeriod::List &list );
-    void updateFreeBusyData( const FreeBusyItem::Ptr & );
-
-    QTimer mReloadTimer;
-    bool mForceDownload;
-    QList<FreeBusyItem::Ptr> mFreeBusyItems;
-    ItemPrivateData *mRootData;
-};
-
-}
-
-#endif
diff --git a/incidenceeditor-ng/freeperiodmodel.cpp b/incidenceeditor-ng/freeperiodmodel.cpp
deleted file mode 100644
index 2446a9f..0000000
--- a/incidenceeditor-ng/freeperiodmodel.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 "freeperiodmodel.h"
-
-#include <KCalCore/Period>
-
-#include <KCalendarSystem>
-#include <KGlobal>
-#include <KSystemTimeZones>
-
-#include <QSet>
-
-using namespace IncidenceEditorNG;
-
-FreePeriodModel::FreePeriodModel( QObject *parent ): QAbstractTableModel( parent )
-{
-}
-
-IncidenceEditorNG::FreePeriodModel::~FreePeriodModel()
-{
-}
-
-QVariant FreePeriodModel::data( const QModelIndex &index, int role ) const
-{
-  if ( !index.isValid() || !hasIndex( index.row(), index.column() ) ) {
-    return QVariant();
-  }
-
-  if( index.column() == 0 ) { //day
-    switch( role ) {
-    case Qt::DisplayRole:
-      return day( index.row() );
-    case Qt::ToolTipRole:
-      return tooltipify( index.row() );
-    case FreePeriodModel::PeriodRole:
-      return QVariant::fromValue( mPeriodList.at( index.row() ) );
-    case Qt::TextAlignmentRole:
-      return static_cast<int>( Qt::AlignRight | Qt::AlignVCenter );
-    default:
-      return QVariant();
-    }
-  } else { // everything else
-    switch( role ) {
-    case Qt::DisplayRole:
-      return date( index.row() );
-    case Qt::ToolTipRole:
-      return tooltipify( index.row() );
-    case FreePeriodModel::PeriodRole:
-      return QVariant::fromValue( mPeriodList.at( index.row() ) );
-    case Qt::TextAlignmentRole:
-      return static_cast<int>( Qt::AlignLeft | Qt::AlignVCenter );
-    default:
-      return QVariant();
-    }
-  }
-}
-
-int FreePeriodModel::rowCount( const QModelIndex &parent ) const
-{
-  if ( !parent.isValid() ) {
-    return mPeriodList.size();
-  }
-  return 0;
-}
-
-int FreePeriodModel::columnCount( const QModelIndex &parent ) const
-{
-  Q_UNUSED( parent );
-  return 2;
-}
-
-QVariant FreePeriodModel::headerData( int section, Qt::Orientation orientation, int role ) const
-{
-  return QAbstractItemModel::headerData( section, orientation, role );
-}
-
-void FreePeriodModel::slotNewFreePeriods( const KCalCore::Period::List &freePeriods )
-{
-  beginResetModel();
-  mPeriodList.clear();
-  mPeriodList = splitPeriodsByDay( freePeriods );
-  qSort( mPeriodList );
-  endResetModel();
-}
-
-KCalCore::Period::List FreePeriodModel::splitPeriodsByDay(
-  const KCalCore::Period::List &freePeriods )
-{
-  KCalCore::Period::List splitList;
-  foreach ( const KCalCore::Period &period, freePeriods ) {
-    if ( period.start().date() == period.end().date() )  {
-      splitList << period; // period occurs on the same day
-      continue;
-    }
-
-    const int validPeriodSecs = 5 * 60; // 5 minutes
-    KCalCore::Period tmpPeriod = period;
-    while ( tmpPeriod.start().date() != tmpPeriod.end().date() ) {
-      const KDateTime midnight( tmpPeriod.start().date(), QTime( 23, 59, 59, 999 ),
-                                tmpPeriod.start().timeSpec() );
-      KCalCore::Period firstPeriod( tmpPeriod.start(), midnight );
-      KCalCore::Period secondPeriod( midnight.addMSecs( 1 ), tmpPeriod.end() );
-      if ( firstPeriod.duration().asSeconds() >= validPeriodSecs ) {
-        splitList << firstPeriod;
-      }
-      tmpPeriod = secondPeriod;
-    }
-    if ( tmpPeriod.duration().asSeconds() >= validPeriodSecs ) {
-      splitList << tmpPeriod;
-    }
-  }
-
-  // Perform some jiggery pokery to remove duplicates
-  QList<KCalCore::Period> tmpList = splitList.toList();
-  QSet<KCalCore::Period>set = tmpList.toSet();
-  tmpList = QList<KCalCore::Period>::fromSet( set );
-  return KCalCore::Period::List::fromList( tmpList );
-}
-
-QString FreePeriodModel::day( int index ) const
-{
-  KCalCore::Period period = mPeriodList.at( index );
-  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
-  const QDate startDate = period.start().date();
-  return ki18nc( "@label Day of the week name, example: Monday,", "%1," ).
-    subs( calSys->weekDayName( startDate.dayOfWeek(), KCalendarSystem::LongDayName ) ).toString();
-}
-
-QString FreePeriodModel::date( int index ) const
-{
-  KCalCore::Period period = mPeriodList.at( index );
-  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
-
-  const QDate startDate = period.start().date();
-  const QString startTime = KGlobal::locale()->formatTime( period.start().time() );
-  const QString endTime = KGlobal::locale()->formatTime( period.end().time() );
-  const QString longMonthName = calSys->monthName( startDate );
-  return ki18nc( "@label A time period duration. It is preceded/followed (based on the "
-                 "orientation) by the name of the week, see the message above. "
-                 "example: 12 June, 8:00am to 9:30am",
-                 "%1 %2, %3 to %4" ).
-    subs( startDate.day() ).
-    subs( longMonthName ).
-    subs( startTime ).
-    subs( endTime ).toString();
-}
-
-QString FreePeriodModel::stringify( int index ) const
-{
-  KCalCore::Period period = mPeriodList.at( index );
-  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
-
-  const QDate startDate = period.start().date();
-  const QString startTime = KGlobal::locale()->formatTime( period.start().time(), false, true );
-  const QString endTime = KGlobal::locale()->formatTime( period.end().time(), false, true );
-  const QString longMonthName = calSys->monthName( startDate );
-  const QString dayofWeek = calSys->weekDayName( startDate.dayOfWeek(),
-                                                 KCalendarSystem::LongDayName );
-
-  // TODO i18n, ping chusslove
-  return ki18nc( "@label A time period duration. KLocale is used to format the components. "
-                 "example: Monday, 12 June, 8:00am to 9:30am",
-                 "%1, %2 %3, %4 to %5" ).
-    subs( dayofWeek ).
-    subs( startDate.day() ).
-    subs( longMonthName ).
-    subs( startTime ).
-    subs( endTime ).toString();
-}
-
-QString FreePeriodModel::tooltipify( int index ) const
-{
-  KDateTime::Spec timeSpec = KSystemTimeZones::local();
-  KCalCore::Period period = mPeriodList.at( index );
-  unsigned long duration = period.duration().asSeconds() * 1000; // we want milliseconds
-  QString toolTip = "<qt>";
-  toolTip += "<b>" + i18nc( "@info:tooltip", "Free Period" ) + "</b>";
-  toolTip += "<hr>";
-  toolTip += "<i>" + i18nc( "@info:tooltip period start time", "Start:" ) + "</i>" + " ";
-  toolTip += KGlobal::locale()->formatDateTime( period.start().toTimeSpec( timeSpec ).dateTime() );
-  toolTip += "<br>";
-  toolTip += "<i>" + i18nc( "@info:tooltip period end time", "End:" ) + "</i>" + " ";
-  toolTip += KGlobal::locale()->formatDateTime( period.end().toTimeSpec( timeSpec ).dateTime() );
-  toolTip += "<br>";
-  toolTip += "<i>" + i18nc( "@info:tooltip period duration", "Duration:" ) + "</i>" + " ";
-  toolTip += KGlobal::locale()->prettyFormatDuration( duration );
-  toolTip += "</qt>";
-  return toolTip;
-}
-
diff --git a/incidenceeditor-ng/freeperiodmodel.h b/incidenceeditor-ng/freeperiodmodel.h
deleted file mode 100644
index c3eee9c..0000000
--- a/incidenceeditor-ng/freeperiodmodel.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 INCIDENCEEDITOR_FREEPERIODMODEL_H
-#define INCIDENCEEDITOR_FREEPERIODMODEL_H
-
-#include "incidenceeditors-ng_export.h"
-
-#include <KCalCore/Period>
-
-#include <QAbstractTableModel>
-
-namespace IncidenceEditorNG {
-
-class INCIDENCEEDITORS_NG_EXPORT FreePeriodModel : public QAbstractTableModel
-{
-  Q_OBJECT
-  public:
-    enum Roles {
-      PeriodRole = Qt::UserRole
-    };
-    explicit FreePeriodModel( QObject *parent = 0 );
-    virtual ~FreePeriodModel();
-
-    virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
-    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
-    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
-    virtual QVariant headerData( int section, Qt::Orientation orientation,
-                                 int role = Qt::DisplayRole ) const;
-
-  public slots:
-    void slotNewFreePeriods( const KCalCore::Period::List &freePeriods );
-
-  private:
-    /** Splits period blocks in the provided list, so that each period occurs on one day */
-    KCalCore::Period::List splitPeriodsByDay( const KCalCore::Period::List &freePeriods );
-
-    QString day( int index ) const;
-    QString date( int index ) const;
-    QString stringify( int index ) const;
-    QString tooltipify( int index ) const;
-
-    KCalCore::Period::List mPeriodList;
-    friend class FreePeriodModelTest;
-};
-
-}
-
-#endif
diff --git a/incidenceeditor-ng/incidenceattendee.cpp b/incidenceeditor-ng/incidenceattendee.cpp
index bab0c00..1c859be 100644
--- a/incidenceeditor-ng/incidenceattendee.cpp
+++ b/incidenceeditor-ng/incidenceattendee.cpp
@@ -30,7 +30,7 @@
 #include "incidencedatetime.h"
 #include "schedulingdialog.h"
 #include "attendeecomboboxdelegate.h"
-#include "freebusyitemmodel.h"
+#include "freebusymodel/freebusyitemmodel.h"
 #ifdef KDEPIM_MOBILE_UI
 #include "ui_dialogmoremobile.h"
 #else
diff --git a/incidenceeditor-ng/resourcemanagement.cpp b/incidenceeditor-ng/resourcemanagement.cpp
index b69d2bb..b75c9d3 100644
--- a/incidenceeditor-ng/resourcemanagement.cpp
+++ b/incidenceeditor-ng/resourcemanagement.cpp
@@ -24,7 +24,8 @@
 #include "resourcemanagement.h"
 #include "ui_resourcemanagement.h"
 #include "resourcemodel.h"
-#include "freebusyitem.h"
+#include "freebusymodel/freebusyitem.h"
+#include "freebusymodel/freebusycalendar.h"
 #include "ldaputils.h"
 
 #include "freebusyganttproxymodel.h"
@@ -52,17 +53,9 @@
 #include <QColor>
 
 #include <KDebug>
-#include <KSystemTimeZones>
 
 using namespace IncidenceEditorNG;
 
-enum FbStatus {
-    Unkown,
-    Free,
-    Busy,
-    Tentative
-};
-
 class FreebusyViewCalendar : public EventViews::ViewCalendar
 {
 public:
@@ -88,11 +81,12 @@ public:
         }
 
         switch (status) {
-        case Busy:
+        case KCalCore::FreeBusyPeriod::Busy:
             return QColor("#f00");
-        case Tentative:
+        case KCalCore::FreeBusyPeriod::BusyTentative:
+        case KCalCore::FreeBusyPeriod::BusyUnavailable:
             return QColor("#f70");
-        case Free:
+        case KCalCore::FreeBusyPeriod::Free:
             return QColor("#0f0");
         default:
             return QColor("#555");
@@ -124,21 +118,12 @@ ResourceManagement::ResourceManagement()
 
     QVariantList list;
     mModel = new FreeBusyItemModel(this);
-
-    connect(mModel, SIGNAL(layoutChanged()), SLOT(slotFbModelLayoutChanged()));
-    connect(mModel, SIGNAL(modelReset()), SLOT(slotFbModelLayoutChanged()));
-    connect(mModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
-                    SLOT(slotFbModelRowsRemoved(QModelIndex,int,int)));
-    connect(mModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
-                    SLOT(slotFbModelRowsAdded(QModelIndex,int,int)));
-    connect(mModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
-                    SLOT(slotFbModelRowsChanged(QModelIndex,QModelIndex)));
+    mFreebusyCalendar.setModel(mModel);
 
     mAgendaView = new EventViews::AgendaView(QDate(), QDate(), false,  false);
 
-    KCalCore::Calendar::Ptr cal = KCalCore::Calendar::Ptr(new KCalCore::MemoryCalendar(KSystemTimeZones::local()));
     FreebusyViewCalendar *fbCalendar = new FreebusyViewCalendar();
-    fbCalendar->mCalendar = cal;
+    fbCalendar->mCalendar = mFreebusyCalendar.calendar();
     mFbCalendar = EventViews::ViewCalendar::Ptr(fbCalendar);
     mAgendaView->addCalendar(mFbCalendar);
 
@@ -243,8 +228,6 @@ void ResourceManagement::showDetails(const KLDAP::LdapObject &obj, const KLDAP::
     FreeBusyItem::Ptr freebusy( new FreeBusyItem( attendee, this ));
     mModel->clear();
     mModel->addItem(freebusy);
-
-
 }
 
 void ResourceManagement::slotLayoutChanged()
@@ -295,7 +278,6 @@ void ResourceManagement::slotOwnerSearchFinished()
               mUi->formOwner->addRow(translateLDAPAttributeForDisplay(key), new QLabel(list.join("\n")));
           }
       }
-
 }
 
 void ResourceManagement::slotDateChanged(QDate start, QDate end)
@@ -307,74 +289,4 @@ void ResourceManagement::slotDateChanged(QDate start, QDate end)
     mAgendaView->showDates(start, end);
 }
 
-void ResourceManagement::slotFbModelLayoutChanged()
-{
-    if (mFbEvent.count() > 0) {
-        mFbCalendar->getCalendar()->deleteAllEvents();
-        mFbEvent.clear();
-        for (int i = mModel->rowCount()-1; i>=0; i--) {
-            QModelIndex parent = mModel->index(i, 0);
-            slotFbModelRowsAdded(parent, 0, mModel->rowCount(parent)-1);
-        }
-    }
-}
-
-void ResourceManagement::slotFbModelRowsAdded(const QModelIndex &parent, int first, int last)
-{
-    if (!parent.isValid()) {
-        return;
-    }
-    for(int i=first; i<=last; i++) {
-        QModelIndex index = mModel->index(i, 0, parent);
-
-        const KCalCore::FreeBusyPeriod &period = mModel->data(index, FreeBusyItemModel::FreeBusyPeriodRole).value<KCalCore::FreeBusyPeriod>();
-        const KCalCore::Attendee::Ptr &attendee = mModel->data(parent, FreeBusyItemModel::AttendeeRole).value<KCalCore::Attendee::Ptr>();
-        const KCalCore::FreeBusy::Ptr &fb = mModel->data(parent, FreeBusyItemModel::FreeBusyRole).value<KCalCore::FreeBusy::Ptr>();
-
-        KCalCore::Event::Ptr inc = KCalCore::Event::Ptr(new KCalCore::Event());
-        inc->setDtStart(period.start());
-        inc->setDtEnd(period.end());
-        inc->setUid(QLatin1String("fb-") + fb->uid());
-        //TODO: set to correct status if it is added to KCalCore
-        inc->setCustomProperty("FREEBUSY", "STATUS", QString::number(Busy));
-        inc->setSummary(period.summary().isEmpty()? i18n("Busy") : period.summary());
-
-        mFbEvent.insert(index, inc);
-        mFbCalendar->getCalendar()->addEvent(inc);
-
-    }
-}
-
-void ResourceManagement::slotFbModelRowsRemoved(const QModelIndex &parent, int first, int last)
-{
-    if (!parent.isValid()) {
-        for (int i = first; i<=last; i--) {
-            QModelIndex index = mModel->index(i, 0);
-            slotFbModelRowsRemoved(index, 0, mModel->rowCount(index)-1);
-        }
-    } else {
-        for(int i=first; i<=last; i++) {
-            QModelIndex index = mModel->index(i, 0, parent);
-            KCalCore::Event::Ptr inc = mFbEvent.take(index);
-            mFbCalendar->getCalendar()->deleteEvent(inc);
-        }
-    }
-}
-
-void ResourceManagement::slotFbModelRowsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
-{
-    if (!topLeft.parent().isValid()) {
-        return;
-    }
-    for (int i = topLeft.row(); i <= bottomRight.row(); i++) {
-        QModelIndex index = mModel->index(i, 0, topLeft.parent());
-        KCalCore::Event::Ptr inc = mFbEvent.value(index);
-        mFbCalendar->getCalendar()->beginChange(inc);
-        mFbCalendar->getCalendar()->endChange(inc);
-    }
-}
-
-
-
-
-#include "resourcemanagement.moc"
+#include "resourcemanagement.moc"
\ No newline at end of file
diff --git a/incidenceeditor-ng/resourcemanagement.h b/incidenceeditor-ng/resourcemanagement.h
index ee8dc19..1943e5b 100644
--- a/incidenceeditor-ng/resourcemanagement.h
+++ b/incidenceeditor-ng/resourcemanagement.h
@@ -27,7 +27,7 @@
 #include <ldap/ldapclient.h>
 #include <ldap/ldapclientsearch.h>
 
-#include "freebusyitemmodel.h"
+#include "freebusymodel/freebusycalendar.h"
 #include "resourceitem.h"
 
 #include <calendarviews/agenda/viewcalendar.h>
@@ -88,13 +88,9 @@ private slots:
 
     void slotLayoutChanged();
 
-    void slotFbModelLayoutChanged();
-    void slotFbModelRowsRemoved(const QModelIndex &parent, int first, int last);
-    void slotFbModelRowsAdded(const QModelIndex &parent, int first, int last);
-    void slotFbModelRowsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
-
 private:
     FreeBusyItemModel *mModel;
+    FreeBusyCalendar mFreebusyCalendar;
     ResourceItem::Ptr mOwnerItem;
     ResourceItem::Ptr mSelectedItem;
     EventViews::ViewCalendar::Ptr mFbCalendar;
diff --git a/incidenceeditor-ng/schedulingdialog.cpp b/incidenceeditor-ng/schedulingdialog.cpp
index 3c8f006..25cc208 100644
--- a/incidenceeditor-ng/schedulingdialog.cpp
+++ b/incidenceeditor-ng/schedulingdialog.cpp
@@ -20,7 +20,7 @@
 
 #include "schedulingdialog.h"
 #include "conflictresolver.h"
-#include "freeperiodmodel.h"
+#include "freebusymodel/freeperiodmodel.h"
 #ifndef KDEPIM_MOBILE_UI
 #include "visualfreebusywidget.h"
 #endif
diff --git a/incidenceeditor-ng/schedulingdialog.h b/incidenceeditor-ng/schedulingdialog.h
index b90a77d..4eda936 100644
--- a/incidenceeditor-ng/schedulingdialog.h
+++ b/incidenceeditor-ng/schedulingdialog.h
@@ -30,9 +30,10 @@
 
 #include <KDialog>
 
+class FreePeriodModel;
+
 namespace IncidenceEditorNG {
 
-class FreePeriodModel;
 class ConflictResolver;
 class VisualFreeBusyWidget;
 
diff --git a/incidenceeditor-ng/tests/CMakeLists.txt b/incidenceeditor-ng/tests/CMakeLists.txt
index 5403c0f..edf9f35 100644
--- a/incidenceeditor-ng/tests/CMakeLists.txt
+++ b/incidenceeditor-ng/tests/CMakeLists.txt
@@ -20,8 +20,6 @@ ENDMACRO(IE_EXECUTABLE_TESTS)
 
 IE_UNIT_TESTS(
   conflictresolvertest
-  testfreebusyitemmodel
-  testfreeperiodmodel
   testfreebusyganttproxymodel
 )
 
@@ -60,4 +58,5 @@ target_link_libraries(resourcemanagement_gui
   ${KDE4_KDEUI_LIBS}
   ${KDEPIMLIBS_KMIME_LIBS}
   incidenceeditorsng
+  kdepim
 )
diff --git a/incidenceeditor-ng/tests/conflictresolvertest.h b/incidenceeditor-ng/tests/conflictresolvertest.h
index 99b9a61..82d643c 100644
--- a/incidenceeditor-ng/tests/conflictresolvertest.h
+++ b/incidenceeditor-ng/tests/conflictresolvertest.h
@@ -21,7 +21,7 @@
 #ifndef CONFLICTRESOLVERTEST_H
 #define CONFLICTRESOLVERTEST_H
 
-#include "../freebusyitem.h"
+#include "freebusymodel/freebusyitem.h"
 
 #include <KCalCore/FreeBusy>
 #include <KCalCore/Attendee>
@@ -51,7 +51,7 @@ class ConflictResolverTest : public QObject
     void insertAttendees();
     void addAttendee( const QString &email, const KCalCore::FreeBusy::Ptr &fb,
                       KCalCore::Attendee::Role role = KCalCore::Attendee::ReqParticipant ) ;
-    QList<IncidenceEditorNG::FreeBusyItem::Ptr> attendees;
+    QList<FreeBusyItem::Ptr> attendees;
     QWidget *parent;
     IncidenceEditorNG::ConflictResolver *resolver;
     KDateTime base, end;
diff --git a/incidenceeditor-ng/tests/testfreebusyganttproxymodel.cpp b/incidenceeditor-ng/tests/testfreebusyganttproxymodel.cpp
index 3c60fac..849c6aa 100644
--- a/incidenceeditor-ng/tests/testfreebusyganttproxymodel.cpp
+++ b/incidenceeditor-ng/tests/testfreebusyganttproxymodel.cpp
@@ -20,8 +20,8 @@
 
 #include "testfreebusyganttproxymodel.h"
 #include "modeltest.h"
-#include "../freebusyitem.h"
-#include "../freebusyitemmodel.h"
+#include "freebusymodel/freebusyitem.h"
+#include "freebusymodel/freebusyitemmodel.h"
 #include "../freebusyganttproxymodel.h"
 
 #include <kdgantt2/kdganttgraphicsview.h>
diff --git a/incidenceeditor-ng/tests/testfreebusyitemmodel.cpp b/incidenceeditor-ng/tests/testfreebusyitemmodel.cpp
deleted file mode 100644
index 62fa13a..0000000
--- a/incidenceeditor-ng/tests/testfreebusyitemmodel.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 "testfreebusyitemmodel.h"
-#include "modeltest.h"
-#include "../freebusyitemmodel.h"
-#include "../freebusyitem.h"
-
-#include <KCalCore/Attendee>
-
-#include <qtest_kde.h>
-
-QTEST_KDEMAIN( FreeBusyItemModelTest, NoGUI )
-using namespace IncidenceEditorNG;
-
-void FreeBusyItemModelTest::testModelValidity()
-{
-  FreeBusyItemModel * model = new FreeBusyItemModel( this );
-  new ModelTest( model, this );
-
-  QVERIFY( model->rowCount() == 0 );
-
-  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
-  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee( "fred", "fred at example.com" ) );
-  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
-
-  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-
-  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
-  item1->setFreeBusy( fb1 );
-
-  model->addItem( item1 );
-  QVERIFY( model->rowCount() == 1 );
-  QVERIFY( model->containsAttendee( a1 ) );
-
-  QModelIndex i = model->index( 0, 0 );
-  QCOMPARE( a1->fullName(), model->data( i, Qt::DisplayRole ).toString() );
-  QCOMPARE( a1,
-            model->data( i, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
-  QCOMPARE( item1->freeBusy(),
-            model->data( i, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
-
-  QCOMPARE( model->rowCount( i ), 2 );
-
-  model->removeRow( 0 );
-  QVERIFY( model->rowCount() == 0 );
-
-  model->addItem( item1 );
-  QVERIFY( model->rowCount() == 1 );
-
-  model->removeAttendee( a1 );
-  QVERIFY( model->rowCount() == 0 );
-
-  model->addItem( item1 );
-  QVERIFY( model->rowCount() == 1 );
-
-  model->removeItem( item1 );
-  QVERIFY( model->rowCount() == 0 );
-
-  model->addItem( item1 );
-  QVERIFY( model->rowCount() == 1 );
-
-  model->clear();
-  QVERIFY( model->rowCount() == 0 );
-}
-
-void FreeBusyItemModelTest::testModelValidity2()
-{
-  FreeBusyItemModel * model = new FreeBusyItemModel( this );
-  new ModelTest( model, this );
-
-  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt3( QDate( 2010, 7, 24 ), QTime( 12, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt4( QDate( 2010, 7, 24 ), QTime( 14, 0, 0 ), KDateTime::UTC );
-
-  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee( "fred", "fred at example.com" ) );
-  KCalCore::Attendee::Ptr a2( new KCalCore::Attendee( "joe", "joe at example.com" ) );
-  KCalCore::Attendee::Ptr a3( new KCalCore::Attendee( "max", "max at example.com" ) );
-  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
-  KCalCore::FreeBusy::Ptr fb2( new KCalCore::FreeBusy() );
-  KCalCore::FreeBusy::Ptr fb3( new KCalCore::FreeBusy() );
-
-  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-
-  fb2->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb2->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-  fb2->addPeriod( dt3, KCalCore::Duration( 60 * 60 ) );
-
-  fb3->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb3->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-  fb3->addPeriod( dt4, KCalCore::Duration( 60 * 60 * 2 ) );
-
-  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
-  item1->setFreeBusy( fb1 );
-  FreeBusyItem::Ptr item2( new FreeBusyItem( a2, 0 ) );
-  FreeBusyItem::Ptr item3( new FreeBusyItem( a3, 0 ) );
-
-  model->addItem( item1 );
-  model->addItem( item2 );
-  model->addItem( item3 );
-
-  QCOMPARE( model->rowCount(), 3 );
-
-  QVERIFY( model->containsAttendee( a1 ) );
-  QVERIFY( model->containsAttendee( a2 ) );
-  QVERIFY( model->containsAttendee( a3 ) );
-
-  QModelIndex i1 = model->index( 0, 0 );
-  QCOMPARE( a1->fullName(), model->data( i1, Qt::DisplayRole ).toString() );
-  QCOMPARE( a1,
-            model->data( i1, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
-  QCOMPARE( item1->freeBusy(),
-            model->data( i1, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
-
-  QModelIndex i2 = model->index( 1, 0 );
-  QCOMPARE( a2->fullName(), model->data( i2, Qt::DisplayRole ).toString() );
-  QCOMPARE( a2,
-            model->data( i2, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
-  QVERIFY( model->rowCount( i2 ) == 0 );
-  QVERIFY( model->data( i2, FreeBusyItemModel::FreeBusyRole ).isValid() == false );
-
-  QModelIndex i3 = model->index( 2, 0 );
-  QCOMPARE( a3->fullName(),
-            model->data( i3, Qt::DisplayRole ).toString() );
-  QCOMPARE( a3,
-            model->data( i3, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
-  QVERIFY( model->rowCount( i3 ) == 0 );
-  QVERIFY( model->data( i3, FreeBusyItemModel::FreeBusyRole ).isValid() == false );
-
-  model->slotInsertFreeBusy( fb2, "joe at example.com" );
-  QCOMPARE( item2->freeBusy(),
-            model->data( i2, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
-  QVERIFY( model->rowCount( i2 ) == fb2->fullBusyPeriods().size() );
-
-  QModelIndex i2_0 = model->index( 0, 0, i2 );
-  QCOMPARE( fb2->fullBusyPeriods().first(),
-            model->data(
-              i2_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  QModelIndex i2_1 = model->index( 1, 0, i2 );
-  QCOMPARE( fb2->fullBusyPeriods().at( 1 ),
-            model->data(
-              i2_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  QModelIndex i2_2 = model->index( 2, 0, i2 );
-  QCOMPARE( fb2->fullBusyPeriods().last(),
-            model->data(
-              i2_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-
-  model->slotInsertFreeBusy( fb3, "max at example.com" );
-  QCOMPARE( item3->freeBusy(),
-            model->data( i3, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
-  QVERIFY( model->rowCount( i3 ) == fb3->fullBusyPeriods().size() );
-
-  QModelIndex i3_0 = model->index( 0, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().first(),
-            model->data(
-              i3_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  QModelIndex i3_1 = model->index( 1, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().at( 1 ),
-            model->data(
-              i3_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  QModelIndex i3_2 = model->index( 2, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().last(),
-            model->data(
-              i3_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-
-  model->removeAttendee( a2 );
-
-  QCOMPARE( 2, model->rowCount() );
-
-  QVERIFY( model->containsAttendee( a1 ) == true );
-  QVERIFY( model->containsAttendee( a2 ) == false );
-  QVERIFY( model->containsAttendee( a3 ) == true );
-
-  i3_0 = model->index( 0, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().first(),
-            model->data(
-              i3_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  i3_1 = model->index( 1, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().at( 1 ),
-            model->data(
-              i3_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-  i3_2 = model->index( 2, 0, i3 );
-  QCOMPARE( fb3->fullBusyPeriods().last(),
-            model->data(
-              i3_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
-}
-
-void FreeBusyItemModelTest::testInsertFreeBusy()
-{
-  FreeBusyItemModel * model = new FreeBusyItemModel( this );
-  new ModelTest( model, this );
-
-  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
-  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee( "fred", "fred at example.com" ) );
-  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
-  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-
-  const KDateTime dt3( QDate( 2010, 7, 24 ), QTime( 12, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt4( QDate( 2010, 7, 24 ), QTime( 14, 0, 0 ), KDateTime::UTC );
-  KCalCore::FreeBusy::Ptr fb2( new KCalCore::FreeBusy() );
-  fb2->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
-  fb2->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
-  fb2->addPeriod( dt3, KCalCore::Duration( 60 * 60 ) );
-  fb2->addPeriod( dt4, KCalCore::Duration( 60 * 60 * 2 ) );
-
-  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
-  item1->setFreeBusy( fb1 );
-
-  model->addItem( item1 );
-
-  QModelIndex i = model->index( 0, 0 );
-  QCOMPARE( model->rowCount( i ), 2 );
-
-  model->slotInsertFreeBusy( fb2, "fred at example.com" );
-
-  QCOMPARE( model->rowCount( i ), 4 );
-}
-
diff --git a/incidenceeditor-ng/tests/testfreebusyitemmodel.h b/incidenceeditor-ng/tests/testfreebusyitemmodel.h
deleted file mode 100644
index 2a16694..0000000
--- a/incidenceeditor-ng/tests/testfreebusyitemmodel.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 TESTFREEBUSYITEMMODEL_H
-#define TESTFREEBUSYITEMMODEL_H
-
-#include <QObject>
-
-class FreeBusyItemModelTest: public QObject
-{
-  Q_OBJECT
-  private Q_SLOTS:
-    void testModelValidity();
-    void testModelValidity2();
-    void testInsertFreeBusy();
-};
-#endif
diff --git a/incidenceeditor-ng/tests/testfreeperiodmodel.cpp b/incidenceeditor-ng/tests/testfreeperiodmodel.cpp
deleted file mode 100644
index f9096cc..0000000
--- a/incidenceeditor-ng/tests/testfreeperiodmodel.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 "testfreeperiodmodel.h"
-#include "modeltest.h"
-#include "../freeperiodmodel.h"
-
-#include <KCalCore/Period>
-#include <KCalCore/Duration>
-
-#include <KDebug>
-
-#include <qtest_kde.h>
-
-QTEST_KDEMAIN( FreePeriodModelTest, NoGUI )
-using namespace IncidenceEditorNG;
-
-void FreePeriodModelTest::testModelValidity()
-{
-  FreePeriodModel * model = new FreePeriodModel( this );
-  new ModelTest( model, this );
-
-  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
-  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
-
-  KCalCore::Period::List list;
-
-  list << KCalCore::Period( dt1, KCalCore::Duration( 60 * 60 ) );
-  list << KCalCore::Period( dt2, KCalCore::Duration( 60 * 60 ) );
-
-  QVERIFY( model->rowCount() == 0 );
-  model->slotNewFreePeriods( list );
-  QCOMPARE( model->rowCount(), 2 );
-}
-
-void FreePeriodModelTest::testSplitByDay()
-{
-  FreePeriodModel * model = new FreePeriodModel( this );
-  new ModelTest( model, this );
-
-  const KDateTime startDt( QDate( 2010, 7, 24 ), QTime( 8, 0, 0 ), KDateTime::UTC );
-  const KDateTime endDt( QDate( 2010, 7, 25 ), QTime( 8, 0, 0 ), KDateTime::UTC );
-
-  KCalCore::Period::List list;
-
-  // This period goes from 8am on the 24th to 8am on the 25th
-  list << KCalCore::Period( startDt, endDt );
-
-  QVERIFY( model->rowCount() == 0 );
-
-  // as part of adding the new periods
-  // the model should split the above period into two
-  // one from 8am-12 on the 24th, and the second from 00-08 on the 25th
-  model->slotNewFreePeriods( list );
-
-  const KDateTime endPeriod1( QDate( 2010, 7, 24 ), QTime( 23, 59, 59, 999 ), KDateTime::UTC );
-  const KDateTime startPeriod2( QDate( 2010, 7, 25 ), QTime( 0, 0, 0, 0 ), KDateTime::UTC );
-
-  QModelIndex index = model->index( 0, 0 );
-  KCalCore::Period period1 =
-    model->data( index, FreePeriodModel::PeriodRole ).value<KCalCore::Period>();
-  index = model->index( 1, 0 );
-  KCalCore::Period period2 =
-    model->data( index, FreePeriodModel::PeriodRole ).value<KCalCore::Period>();
-
-  QCOMPARE( period1.start(), startDt );
-  QCOMPARE( period1.end(), endPeriod1 );
-  QCOMPARE( period2.start(), startPeriod2 );
-  QCOMPARE( period2.end(), endDt );
-}
-
diff --git a/incidenceeditor-ng/tests/testfreeperiodmodel.h b/incidenceeditor-ng/tests/testfreeperiodmodel.h
deleted file mode 100644
index 7d60b62..0000000
--- a/incidenceeditor-ng/tests/testfreeperiodmodel.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
-  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
-
-  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 TESTFREEPERIODMODEL_H
-#define TESTFREEPERIODMODEL_H
-
-#include <QObject>
-
-class FreePeriodModelTest: public QObject
-{
-  Q_OBJECT
-  private Q_SLOTS:
-    void testModelValidity();
-    void testSplitByDay();
-};
-#endif
diff --git a/incidenceeditor-ng/visualfreebusywidget.cpp b/incidenceeditor-ng/visualfreebusywidget.cpp
index 4166f1e..f73c190 100644
--- a/incidenceeditor-ng/visualfreebusywidget.cpp
+++ b/incidenceeditor-ng/visualfreebusywidget.cpp
@@ -20,7 +20,7 @@
 
 #include "visualfreebusywidget.h"
 #include "freebusyganttproxymodel.h"
-#include "freebusyitemmodel.h"
+#include "freebusymodel/freebusyitemmodel.h"
 
 #include <kdgantt2/kdganttgraphicsview.h>
 #include <kdgantt2/kdganttview.h>
diff --git a/incidenceeditor-ng/visualfreebusywidget.h b/incidenceeditor-ng/visualfreebusywidget.h
index 223c378..265ddfe 100644
--- a/incidenceeditor-ng/visualfreebusywidget.h
+++ b/incidenceeditor-ng/visualfreebusywidget.h
@@ -36,9 +36,10 @@ class KComboBox;
 
 class QTreeView;
 
+class FreeBusyItemModel;
+
 namespace IncidenceEditorNG {
 
-class FreeBusyItemModel;
 class FreeBusyGanttProxyModel;
 class RowController;
 
diff --git a/libkdepim/CMakeLists.txt b/libkdepim/CMakeLists.txt
index a603408..448fdd5 100644
--- a/libkdepim/CMakeLists.txt
+++ b/libkdepim/CMakeLists.txt
@@ -6,8 +6,8 @@ add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5300)
 add_definitions( -DQT_NO_CAST_FROM_ASCII )
 add_definitions( -DQT_NO_CAST_TO_ASCII )
 
-
 add_subdirectory(tests)
+add_subdirectory(freebusymodel/tests)
 add_subdirectory(pics)
 
 add_definitions(${QT_QTDBUS_DEFINITIONS})
@@ -64,6 +64,10 @@ set(kdepim_LIB_SRCS
    widgets/pimmessagebox.cpp
    widgets/overlaywidget.cpp
    widgets/tagwidgets.cpp
+   freebusymodel/freeperiodmodel.cpp
+   freebusymodel/freebusyitem.cpp
+   freebusymodel/freebusyitemmodel.cpp
+   freebusymodel/freebusycalendar.cpp
 )
 
 if (KDEPIM_INPROCESS_LDAP)
@@ -88,6 +92,7 @@ target_link_libraries(kdepim
   ${KDEPIMLIBS_KMIME_LIBS}
   ${KDEPIMLIBS_AKONADI_LIBS}
   ${KDEPIMLIBS_AKONADI_CONTACT_LIBS}
+  ${KDEPIMLIBS_AKONADI_CALENDAR_LIBS}
   ${BALOO_LIBRARIES}
 )
 
@@ -101,6 +106,7 @@ target_link_libraries(kdepim LINK_INTERFACE_LIBRARIES
   ${KDEPIMLIBS_KPIMTEXTEDIT_LIBS}
   ${KDEPIMLIBS_AKONADI_LIBS}
   ${KDEPIMLIBS_AKONADI_CONTACT_LIBS}
+  ${KDEPIMLIBS_AKONADI_CALENDAR_LIBS}
 )
 
 if(MINGW)
diff --git a/libkdepim/freebusymodel/freebusycalendar.cpp b/libkdepim/freebusymodel/freebusycalendar.cpp
new file mode 100644
index 0000000..953a46f
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusycalendar.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2014  Sandro Knauß <knauss at kolabsys.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "freebusycalendar.h"
+
+#include <KCalCore/Event>
+#include <KCalCore/FreeBusyPeriod>
+#include <KCalCore/MemoryCalendar>
+#include <kcalcore/calformat.h>
+
+#include <QStringList>
+#include <klocalizedstring.h>
+#include <KSystemTimeZones>
+
+#include <KDebug>
+
+FreeBusyCalendar::FreeBusyCalendar(QObject *parent)
+: QObject(parent)
+, mModel(0)
+{
+    mCalendar = KCalCore::Calendar::Ptr(new KCalCore::MemoryCalendar(KSystemTimeZones::local()));
+    kDebug() << "creating" << this;
+}
+
+FreeBusyCalendar::~FreeBusyCalendar()
+{
+    kDebug() << "deleting" << this;
+}
+
+
+KCalCore::Calendar::Ptr FreeBusyCalendar::calendar() const
+{
+    return mCalendar;
+}
+
+FreeBusyItemModel *FreeBusyCalendar::model() const
+{
+    return mModel;
+}
+
+void FreeBusyCalendar::setModel(FreeBusyItemModel *model)
+{
+    if (model != mModel) {
+        if (mModel) {
+            disconnect(mModel, 0, 0, 0);
+        }
+        mModel = model;
+        connect(mModel, SIGNAL(layoutChanged()), SLOT(onLayoutChanged()));
+        connect(mModel, SIGNAL(modelReset()), SLOT(onLayoutChanged()));
+        connect(mModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+                SLOT(onRowsRemoved(QModelIndex,int,int)));
+        connect(mModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
+                SLOT(onRowsInserted(QModelIndex,int,int)));
+        connect(mModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+                SLOT(onRowsChanged(QModelIndex,QModelIndex)));
+    }
+}
+
+void FreeBusyCalendar::onLayoutChanged()
+{
+    if (mFbEvent.count() > 0) {
+        mCalendar->deleteAllEvents();
+        mFbEvent.clear();
+        for (int i = mModel->rowCount()-1; i>=0; i--) {
+            QModelIndex parent = mModel->index(i, 0);
+            onRowsInserted(parent, 0, mModel->rowCount(parent)-1);
+        }
+    }
+}
+
+void FreeBusyCalendar::onRowsInserted(const QModelIndex &parent, int first, int last)
+{
+    if (!parent.isValid()) {
+        return;
+    }
+    for(int i=first; i<=last; i++) {
+        QModelIndex index = mModel->index(i, 0, parent);
+
+        const KCalCore::FreeBusyPeriod &period = mModel->data(index, FreeBusyItemModel::FreeBusyPeriodRole).value<KCalCore::FreeBusyPeriod>();
+        const KCalCore::Attendee::Ptr &attendee = mModel->data(parent, FreeBusyItemModel::AttendeeRole).value<KCalCore::Attendee::Ptr>();
+        const KCalCore::FreeBusy::Ptr &fb = mModel->data(parent, FreeBusyItemModel::FreeBusyRole).value<KCalCore::FreeBusy::Ptr>();
+
+        KCalCore::Event::Ptr inc = KCalCore::Event::Ptr(new KCalCore::Event());
+        inc->setDtStart(period.start());
+        inc->setDtEnd(period.end());
+        inc->setUid(QLatin1String("fb-") + fb->uid() + QLatin1String("-") + QString::number(i));
+
+        inc->setCustomProperty("FREEBUSY", "STATUS", QString::number(period.type()));
+        QString summary = period.summary();
+        if (summary.isEmpty()) {
+            switch (period.type()) {
+            case KCalCore::FreeBusyPeriod::Free:
+                summary = i18n("Free");
+                break;
+            case KCalCore::FreeBusyPeriod::Busy:
+                summary =  i18n("Busy");
+                break;
+            case KCalCore::FreeBusyPeriod::BusyUnavailable:
+                summary = i18n("Unavailable");
+                break;
+            case KCalCore::FreeBusyPeriod::BusyTentative:
+                summary = i18n("Tentative");
+                break;
+            default:
+                summary = i18n("Unknown");
+            }
+        }
+        inc->setSummary(summary);
+
+        mFbEvent.insert(index, inc);
+        mCalendar->addEvent(inc);
+    }
+}
+
+void FreeBusyCalendar::onRowsRemoved(const QModelIndex &parent, int first, int last)
+{
+    if (!parent.isValid()) {
+        for (int i = first; i<=last; i--) {
+            QModelIndex index = mModel->index(i, 0);
+            onRowsRemoved(index, 0, mModel->rowCount(index)-1);
+        }
+    } else {
+        for(int i=first; i<=last; i++) {
+            QModelIndex index = mModel->index(i, 0, parent);
+            KCalCore::Event::Ptr inc = mFbEvent.take(index);
+            mCalendar->deleteEvent(inc);
+        }
+    }
+}
+
+void FreeBusyCalendar::onRowsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+    if (!topLeft.parent().isValid()) {
+        return;
+    }
+    for (int i = topLeft.row(); i <= bottomRight.row(); i++) {
+        QModelIndex index = mModel->index(i, 0, topLeft.parent());
+        KCalCore::Event::Ptr inc = mFbEvent.value(index);
+        mCalendar->beginChange(inc);
+        mCalendar->endChange(inc);
+    }
+}
\ No newline at end of file
diff --git a/libkdepim/freebusymodel/freebusycalendar.h b/libkdepim/freebusymodel/freebusycalendar.h
new file mode 100644
index 0000000..ac2e6f6
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusycalendar.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014  Sandro Knauß <knauss at kolabsys.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef FBMODEL_FBCALENDAR_H
+#define FBMODEL_FBCALENDAR_H
+
+#include "libkdepim/kdepim_export.h"
+
+#include "freebusyitemmodel.h"
+
+#include <KCalCore/Calendar>
+#include <KCalCore/Event>
+
+class KDEPIM_EXPORT FreeBusyCalendar : QObject {
+    Q_OBJECT
+public:
+    explicit FreeBusyCalendar(QObject *parent = 0);
+
+    virtual ~FreeBusyCalendar();
+    
+    void setModel(FreeBusyItemModel *model);
+    FreeBusyItemModel *model() const;
+    KCalCore::Calendar::Ptr calendar() const;
+
+private slots:
+    void onRowsChanged(const QModelIndex &, const QModelIndex &);
+    void onRowsInserted(const QModelIndex &,int,int);
+    void onRowsRemoved(const QModelIndex &,int,int);
+    void onLayoutChanged();
+
+private:
+    FreeBusyItemModel *mModel;
+    KCalCore::Calendar::Ptr mCalendar;
+    QMap<QModelIndex,KCalCore::Event::Ptr> mFbEvent;
+};
+
+#endif // FBMODEL_FBCALENDAR_H
diff --git a/libkdepim/freebusymodel/freebusyitem.cpp b/libkdepim/freebusymodel/freebusyitem.cpp
new file mode 100644
index 0000000..ac4468f
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusyitem.cpp
@@ -0,0 +1,82 @@
+/*
+  Copyright (c) 2000,2001,2004 Cornelius Schumacher <schumacher at kde.org>
+  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info at kdab.com
+  Copyright (c) 2010 Andras Mantia <andras at kdab.com>
+  Copyright (C) 2010 Casey Link <casey at kdab.com>
+
+  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 "freebusyitem.h"
+
+#include <akonadi/calendar/freebusymanager.h>
+
+FreeBusyItem::FreeBusyItem( const KCalCore::Attendee::Ptr &attendee, QWidget *parentWidget )
+  : mAttendee( attendee ), mTimerID( 0 ), mIsDownloading( false ), mParentWidget( parentWidget )
+{
+  Q_ASSERT( attendee );
+  setFreeBusy( KCalCore::FreeBusy::Ptr() );
+}
+
+KCalCore::Attendee::Ptr FreeBusyItem::attendee() const
+{
+  return mAttendee;
+}
+
+void FreeBusyItem::setFreeBusy( const KCalCore::FreeBusy::Ptr &fb )
+{
+  mFreeBusy = fb;
+  mIsDownloading = false;
+}
+
+KCalCore::FreeBusy::Ptr FreeBusyItem::freeBusy() const
+{
+  return mFreeBusy;
+}
+
+QString FreeBusyItem::email() const
+{
+  return mAttendee->email();
+}
+
+void FreeBusyItem::setUpdateTimerID( int id )
+{
+  mTimerID = id;
+}
+
+int FreeBusyItem::updateTimerID() const
+{
+  return mTimerID;
+}
+
+void FreeBusyItem::startDownload( bool forceDownload )
+{
+  mIsDownloading = true;
+  Akonadi::FreeBusyManager *m = Akonadi::FreeBusyManager::self();
+  if ( !m->retrieveFreeBusy( attendee()->email(), forceDownload, mParentWidget ) ) {
+    mIsDownloading = false;
+  }
+}
+
+void FreeBusyItem::setIsDownloading( bool d )
+{
+  mIsDownloading = d;
+}
+
+bool FreeBusyItem::isDownloading() const
+{
+  return mIsDownloading;
+}
diff --git a/libkdepim/freebusymodel/freebusyitem.h b/libkdepim/freebusymodel/freebusyitem.h
new file mode 100644
index 0000000..ef7d007
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusyitem.h
@@ -0,0 +1,73 @@
+/*
+  Copyright (c) 2000,2001,2004 Cornelius Schumacher <schumacher at kde.org>
+  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info at kdab.com
+  Copyright (c) 2010 Andras Mantia <andras at kdab.com>
+  Copyright (C) 2010 Casey Link <casey at kdab.com>
+
+  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 INCIDENCEEDITOR_FREEBUSYITEM_H
+#define INCIDENCEEDITOR_FREEBUSYITEM_H
+
+#include "libkdepim/kdepim_export.h"
+
+#include <KCalCore/FreeBusy>
+
+/**
+ * The FreeBusyItem is the whole line for a given attendee..
+ */
+class KDEPIM_EXPORT FreeBusyItem
+{
+  public:
+    typedef QSharedPointer<FreeBusyItem> Ptr;
+
+    /**
+     * @param parentWidget is passed to Akonadi when fetching free/busy data.
+     */
+    FreeBusyItem( const KCalCore::Attendee::Ptr &attendee, QWidget *parentWidget );
+    ~FreeBusyItem() {}
+
+    KCalCore::Attendee::Ptr attendee() const;
+    void setFreeBusy( const KCalCore::FreeBusy::Ptr &fb );
+    KCalCore::FreeBusy::Ptr freeBusy() const;
+
+    QString email() const;
+    void setUpdateTimerID( int id );
+    int updateTimerID() const;
+
+    void startDownload( bool forceDownload );
+    void setIsDownloading( bool d );
+    bool isDownloading() const;
+
+  signals:
+    void attendeeChanged( const KCalCore::Attendee::Ptr &attendee );
+    void freebusyChanged( const KCalCore::FreeBusy::Ptr fb );
+
+  private:
+    KCalCore::Attendee::Ptr mAttendee;
+    KCalCore::FreeBusy::Ptr mFreeBusy;
+
+    // This is used for the update timer
+    int mTimerID;
+
+    // Only run one download job at a time
+    bool mIsDownloading;
+
+    QWidget *mParentWidget;
+};
+
+#endif
diff --git a/libkdepim/freebusymodel/freebusyitemmodel.cpp b/libkdepim/freebusymodel/freebusyitemmodel.cpp
new file mode 100644
index 0000000..1415d60
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusyitemmodel.cpp
@@ -0,0 +1,419 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 "freebusyitemmodel.h"
+
+#include <akonadi/calendar/freebusymanager.h>
+
+#include <KGlobal>
+#include <KLocalizedString>
+#include <KLocale>
+
+#include <QTimerEvent>
+
+class ItemPrivateData
+{
+  public:
+    ItemPrivateData( ItemPrivateData *parent ) : parentItem( parent )
+    {
+    }
+
+    ~ItemPrivateData()
+    {
+      qDeleteAll( childItems );
+    }
+
+    ItemPrivateData *child( int row )
+    {
+      return childItems.value( row );
+    }
+
+    void appendChild( ItemPrivateData *item )
+    {
+      childItems.append(item);
+    }
+
+    ItemPrivateData *removeChild( int row )
+    {
+      return childItems.takeAt( row );
+    }
+
+    int childCount() const
+    {
+      return childItems.count();
+    }
+
+    int row() const
+    {
+      if ( parentItem ) {
+        return parentItem->childItems.indexOf( const_cast<ItemPrivateData*>(this) );
+      }
+      return 0;
+    }
+
+    ItemPrivateData *parent()
+    {
+      return parentItem;
+    }
+
+  private:
+    QList<ItemPrivateData*> childItems;
+    ItemPrivateData *parentItem;
+};
+
+FreeBusyItemModel::FreeBusyItemModel( QObject *parent )
+  : QAbstractItemModel( parent ), mForceDownload( false )
+{
+  qRegisterMetaType<KCalCore::Attendee::Ptr>( "KCalCore::Attendee::Ptr" );
+  qRegisterMetaType<KCalCore::FreeBusy::Ptr>( "KCalCore::FreeBusy::Ptr" );
+  qRegisterMetaType<KCalCore::Period>( "KCalCore::Period" );
+
+  Akonadi::FreeBusyManager *m = Akonadi::FreeBusyManager::self();
+  connect( m, SIGNAL(freeBusyRetrieved(KCalCore::FreeBusy::Ptr,QString)),
+           SLOT(slotInsertFreeBusy(KCalCore::FreeBusy::Ptr,QString)) );
+
+  connect( &mReloadTimer, SIGNAL(timeout()), SLOT(autoReload()) );
+  mReloadTimer.setSingleShot( true );
+
+  mRootData = new ItemPrivateData( 0 );
+}
+
+FreeBusyItemModel::~FreeBusyItemModel()
+{
+  delete mRootData;
+}
+
+QVariant FreeBusyItemModel::data( const QModelIndex &index, int role ) const
+{
+  if ( !index.isValid() ) {
+    return QVariant();
+  }
+
+  ItemPrivateData *data = (ItemPrivateData*) index.internalPointer();
+
+  if ( data->parent() == mRootData ) {
+    int row = index.row();
+    if ( row >= mFreeBusyItems.size() ) {
+      return QVariant();
+    }
+
+    switch( role ) {
+    case Qt::DisplayRole:
+      return mFreeBusyItems.at( row )->attendee()->fullName();
+    case FreeBusyItemModel::AttendeeRole:
+      return QVariant::fromValue( mFreeBusyItems.at( row )->attendee() );
+    case FreeBusyItemModel::FreeBusyRole:
+      if ( mFreeBusyItems.at( row )->freeBusy() ) {
+        return QVariant::fromValue( mFreeBusyItems.at( row )->freeBusy() );
+      } else {
+        return QVariant();
+      }
+    default:
+      return QVariant();
+    }
+  }
+
+  FreeBusyItem::Ptr fbitem = mFreeBusyItems.at( data->parent()->row() );
+  if ( !fbitem->freeBusy() || index.row() >= fbitem->freeBusy()->busyPeriods().size() ) {
+    return QVariant();
+  }
+
+  KCalCore::FreeBusyPeriod period = fbitem->freeBusy()->fullBusyPeriods().at( index.row() );
+  switch( role ) {
+  case Qt::DisplayRole: // return something to make modeltest happy
+    return QString(QLatin1String( "%1 - %2" )).
+      arg( KGlobal::locale()->formatDateTime( period.start() ) ).
+      arg( KGlobal::locale()->formatDateTime( period.end() ) );
+  case FreeBusyItemModel::FreeBusyPeriodRole:
+    return QVariant::fromValue( period );
+  default:
+    return QVariant();
+  }
+}
+
+int FreeBusyItemModel::rowCount( const QModelIndex &parent ) const
+{
+  ItemPrivateData *parentData;
+  if ( parent.column() > 0 ) {
+    return 0;
+  }
+
+  if ( !parent.isValid() ) {
+    parentData = mRootData;
+  } else {
+    parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
+  }
+
+  return parentData->childCount();
+}
+
+int FreeBusyItemModel::columnCount( const QModelIndex &parent ) const
+{
+  Q_UNUSED( parent );
+  return 1;
+}
+
+QModelIndex FreeBusyItemModel::index( int row, int column, const QModelIndex &parent ) const
+{
+  if ( !hasIndex( row, column, parent ) ) {
+    return QModelIndex();
+  }
+
+  ItemPrivateData *parentData;
+  if ( !parent.isValid() ) {
+    parentData = mRootData;
+  } else {
+    parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
+  }
+
+  ItemPrivateData *childData = parentData->child( row );
+  if ( childData ) {
+    return createIndex( row, column, childData );
+  } else {
+    return QModelIndex();
+  }
+//  FreeBusyItem::Ptr item = mFreeBusyItems.at( parent.row() );
+//  KCalCore::FreeBusy::Ptr fb = item->freeBusy();
+//  if( !fb )
+//    return QModelIndex();
+//
+//  QList<KCalCore::FreeBusyPeriod> busyPeriods = fb->fullBusyPeriods();
+//  if( row < busyPeriods.size() )
+//    return createIndex( row, column, new ItemPrivateData( parent.row() ) );
+//  else
+//    return QModelIndex();
+//  }
+}
+
+QModelIndex FreeBusyItemModel::parent( const QModelIndex &child ) const
+{
+  if ( !child.isValid() ) {
+    return QModelIndex();
+  }
+
+  ItemPrivateData *childData = static_cast<ItemPrivateData*>( child.internalPointer() );
+  ItemPrivateData *parentData = childData->parent();
+  if ( parentData == mRootData ) {
+    return QModelIndex();
+  }
+
+  return createIndex( parentData->row(), 0, parentData );
+}
+
+QVariant FreeBusyItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
+{
+  if ( role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0 ) {
+    return i18n( "Attendee" );
+  }
+  return QVariant();
+}
+
+void FreeBusyItemModel::addItem( const FreeBusyItem::Ptr &freebusy )
+{
+  int row = mFreeBusyItems.size();
+  beginInsertRows( QModelIndex(), row, row );
+  mFreeBusyItems.append( freebusy );
+  ItemPrivateData *data = new ItemPrivateData( mRootData );
+  mRootData->appendChild( data );
+  endInsertRows();
+
+  if ( freebusy->freeBusy() && freebusy->freeBusy()->fullBusyPeriods().size() > 0 ) {
+    QModelIndex parent = index( row, 0 );
+    setFreeBusyPeriods( parent, freebusy->freeBusy()->fullBusyPeriods() );
+  }
+  updateFreeBusyData( freebusy );
+}
+
+void FreeBusyItemModel::setFreeBusyPeriods( const QModelIndex &parent,
+                                            const KCalCore::FreeBusyPeriod::List &list )
+{
+  if(!parent.isValid())
+    return;
+
+  ItemPrivateData *parentData = static_cast<ItemPrivateData*>( parent.internalPointer() );
+  int fb_count = list.size();
+  int childCount = parentData->childCount();
+  QModelIndex first = index( 0, 0, parent );
+  QModelIndex last = index( childCount-1, 0, parent );
+
+  if ( childCount > 0 && fb_count < childCount ) {
+      beginRemoveRows( parent, fb_count-1<0 ? 0 : fb_count-1 , childCount - 1 );
+    for ( int i = childCount - 1; i > fb_count; --i ) {
+      delete parentData->removeChild( i );
+    }
+    endRemoveRows();
+    if (fb_count > 0) {
+        last = index(fb_count-1, 0, parent);
+        emit dataChanged(first, last);
+    }
+  } else if (fb_count > childCount) {
+      beginInsertRows( parent, childCount, fb_count - 1 );
+      for ( int i=childCount; i < fb_count; ++i ) {
+        ItemPrivateData *childData= new ItemPrivateData( parentData );
+        parentData->appendChild( childData );
+      }
+      endInsertRows();
+      if (childCount > 0) {
+          last = index(childCount-1, 0, parent);
+          emit dataChanged(first, last);
+      }
+  } else if (fb_count == childCount && fb_count > 0) {
+      emit dataChanged( first, last );
+  }
+}
+
+void FreeBusyItemModel::clear()
+{
+  beginResetModel();
+  mFreeBusyItems.clear();
+  delete mRootData;
+  mRootData = new ItemPrivateData( 0 );
+  endResetModel();
+}
+
+void FreeBusyItemModel::removeRow( int row )
+{
+  beginRemoveRows( QModelIndex(), row, row );
+  mFreeBusyItems.removeAt( row );
+  ItemPrivateData *data = mRootData->removeChild( row );
+  delete data;
+  endRemoveRows();
+}
+
+void FreeBusyItemModel::removeItem( const FreeBusyItem::Ptr &freebusy )
+{
+  int row = mFreeBusyItems.indexOf( freebusy );
+  if( row >= 0 ) {
+    removeRow( row );
+  }
+}
+
+void FreeBusyItemModel::removeAttendee( const KCalCore::Attendee::Ptr &attendee )
+{
+  FreeBusyItem::Ptr anItem;
+  for ( int i = 0; i < mFreeBusyItems.count(); ++i ) {
+    anItem = mFreeBusyItems[i];
+    if ( *anItem->attendee() == *attendee ) {
+      if ( anItem->updateTimerID() != 0 ) {
+        killTimer( anItem->updateTimerID() );
+      }
+      removeRow( i );
+      break;
+    }
+  }
+}
+
+bool FreeBusyItemModel::containsAttendee( const KCalCore::Attendee::Ptr &attendee )
+{
+  FreeBusyItem::Ptr anItem;
+  for ( int i = 0; i < mFreeBusyItems.count(); ++i ) {
+    anItem = mFreeBusyItems[i];
+    if ( *anItem->attendee() == *attendee ) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void FreeBusyItemModel::updateFreeBusyData( const FreeBusyItem::Ptr &item )
+{
+  if ( item->isDownloading() ) {
+    // This item is already in the process of fetching the FB list
+    return;
+  }
+
+  if ( item->updateTimerID() != 0 ) {
+    // An update timer is already running. Reset it
+    killTimer( item->updateTimerID() );
+  }
+
+  // This item does not have a download running, and no timer is set
+  // Do the download in one second
+  item->setUpdateTimerID( startTimer( 1000 ) );
+}
+
+void FreeBusyItemModel::timerEvent( QTimerEvent *event )
+{
+  killTimer( event->timerId() );
+  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
+    if ( item->updateTimerID() == event->timerId() ) {
+      item->setUpdateTimerID( 0 );
+      item->startDownload( mForceDownload );
+      return;
+    }
+  }
+}
+
+void FreeBusyItemModel::slotInsertFreeBusy( const KCalCore::FreeBusy::Ptr &fb,
+                                            const QString &email )
+{
+  if ( !fb ) {
+    return;
+  }
+
+  if ( fb->fullBusyPeriods().isEmpty() ) {
+    return;
+  }
+
+  fb->sortList();
+
+  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
+    if ( item->email() == email ) {
+      item->setFreeBusy( fb );
+      const int row = mFreeBusyItems.indexOf( item );
+      const QModelIndex parent = index( row, 0 );
+      emit dataChanged(parent, parent);
+      setFreeBusyPeriods( parent, fb->fullBusyPeriods() );
+    }
+  }
+}
+
+void FreeBusyItemModel::autoReload()
+{
+  mForceDownload = false;
+  reload();
+}
+
+void FreeBusyItemModel::reload()
+{
+  Q_FOREACH ( FreeBusyItem::Ptr item, mFreeBusyItems ) {
+    if ( mForceDownload ) {
+      item->startDownload( mForceDownload );
+    } else {
+      updateFreeBusyData( item );
+    }
+  }
+}
+
+void FreeBusyItemModel::triggerReload()
+{
+  mReloadTimer.start( 1000 );
+}
+
+void FreeBusyItemModel::cancelReload()
+{
+  mReloadTimer.stop();
+}
+
+void FreeBusyItemModel::manualReload()
+{
+  mForceDownload = true;
+  reload();
+}
diff --git a/libkdepim/freebusymodel/freebusyitemmodel.h b/libkdepim/freebusymodel/freebusyitemmodel.h
new file mode 100644
index 0000000..31636d5
--- /dev/null
+++ b/libkdepim/freebusymodel/freebusyitemmodel.h
@@ -0,0 +1,111 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 FBMODEL_FREEBUSYITEMMODEL_H
+#define FBMODEL_FREEBUSYITEMMODEL_H
+
+#include "libkdepim/kdepim_export.h"
+
+#include "freebusyitem.h"
+
+#include <QAbstractItemModel>
+#include <QTimer>
+
+class ItemPrivateData;
+
+/**
+ * The FreeBusyItemModel is a 2-level tree structure.
+ *
+ * The top level parent nodes represent the freebusy items, and
+ * the 2nd-level child nodes represent the FreeBusyPeriods of the parent
+ * freebusy item.
+ */
+class KDEPIM_EXPORT FreeBusyItemModel : public QAbstractItemModel
+{
+  Q_OBJECT
+  public:
+    enum Roles {
+        AttendeeRole = Qt::UserRole,
+        FreeBusyRole,
+        FreeBusyPeriodRole
+    };
+
+    explicit FreeBusyItemModel( QObject *parent = 0 );
+    virtual ~FreeBusyItemModel();
+
+    virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
+    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual QModelIndex index( int row, int column = 0,
+                               const QModelIndex &parent = QModelIndex() ) const;
+    virtual QModelIndex parent( const QModelIndex &child ) const;
+    virtual QVariant headerData( int section, Qt::Orientation orientation,
+                                 int role = Qt::DisplayRole ) const;
+
+    void addItem( const FreeBusyItem::Ptr &freebusy );
+
+    void clear();
+    void removeAttendee( const KCalCore::Attendee::Ptr &attendee );
+    void removeItem( const FreeBusyItem::Ptr &freebusy );
+    void removeRow( int row );
+
+    bool containsAttendee( const KCalCore::Attendee::Ptr &attendee );
+
+    /**
+     * Queues a reload of free/busy data.
+     * All current attendees will have their free/busy data
+     * redownloaded from Akonadi.
+     */
+    void triggerReload();
+
+    /**
+     * cancel reloading
+     */
+    void cancelReload();
+
+    /**
+     * Reload FB items
+     */
+    void reload();
+
+  public slots:
+    void slotInsertFreeBusy( const KCalCore::FreeBusy::Ptr &fb, const QString &email );
+
+  protected:
+    void timerEvent( QTimerEvent * );
+
+  private slots:
+    // Force the download of FB information
+    void manualReload();
+    // Only download FB if the auto-download option is set in config
+    void autoReload();
+
+  private:
+    void setFreeBusyPeriods( const QModelIndex &parent,
+                             const KCalCore::FreeBusyPeriod::List &list );
+    void updateFreeBusyData( const FreeBusyItem::Ptr & );
+
+    QTimer mReloadTimer;
+    bool mForceDownload;
+    QList<FreeBusyItem::Ptr> mFreeBusyItems;
+    ItemPrivateData *mRootData;
+};
+
+#endif
diff --git a/libkdepim/freebusymodel/freeperiodmodel.cpp b/libkdepim/freebusymodel/freeperiodmodel.cpp
new file mode 100644
index 0000000..e928e85
--- /dev/null
+++ b/libkdepim/freebusymodel/freeperiodmodel.cpp
@@ -0,0 +1,206 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 "freeperiodmodel.h"
+
+#include <KCalCore/Period>
+
+#include <KCalendarSystem>
+#include <KGlobal>
+#include <KSystemTimeZones>
+
+#include <QSet>
+
+FreePeriodModel::FreePeriodModel( QObject *parent ): QAbstractTableModel( parent )
+{
+}
+
+FreePeriodModel::~FreePeriodModel()
+{
+}
+
+QVariant FreePeriodModel::data( const QModelIndex &index, int role ) const
+{
+  if ( !index.isValid() || !hasIndex( index.row(), index.column() ) ) {
+    return QVariant();
+  }
+
+  if( index.column() == 0 ) { //day
+    switch( role ) {
+    case Qt::DisplayRole:
+      return day( index.row() );
+    case Qt::ToolTipRole:
+      return tooltipify( index.row() );
+    case FreePeriodModel::PeriodRole:
+      return QVariant::fromValue( mPeriodList.at( index.row() ) );
+    case Qt::TextAlignmentRole:
+      return static_cast<int>( Qt::AlignRight | Qt::AlignVCenter );
+    default:
+      return QVariant();
+    }
+  } else { // everything else
+    switch( role ) {
+    case Qt::DisplayRole:
+      return date( index.row() );
+    case Qt::ToolTipRole:
+      return tooltipify( index.row() );
+    case FreePeriodModel::PeriodRole:
+      return QVariant::fromValue( mPeriodList.at( index.row() ) );
+    case Qt::TextAlignmentRole:
+      return static_cast<int>( Qt::AlignLeft | Qt::AlignVCenter );
+    default:
+      return QVariant();
+    }
+  }
+}
+
+int FreePeriodModel::rowCount( const QModelIndex &parent ) const
+{
+  if ( !parent.isValid() ) {
+    return mPeriodList.size();
+  }
+  return 0;
+}
+
+int FreePeriodModel::columnCount( const QModelIndex &parent ) const
+{
+  Q_UNUSED( parent );
+  return 2;
+}
+
+QVariant FreePeriodModel::headerData( int section, Qt::Orientation orientation, int role ) const
+{
+  return QAbstractItemModel::headerData( section, orientation, role );
+}
+
+void FreePeriodModel::slotNewFreePeriods( const KCalCore::Period::List &freePeriods )
+{
+  beginResetModel();
+  mPeriodList.clear();
+  mPeriodList = splitPeriodsByDay( freePeriods );
+  qSort( mPeriodList );
+  endResetModel();
+}
+
+KCalCore::Period::List FreePeriodModel::splitPeriodsByDay(
+  const KCalCore::Period::List &freePeriods )
+{
+  KCalCore::Period::List splitList;
+  foreach ( const KCalCore::Period &period, freePeriods ) {
+    if ( period.start().date() == period.end().date() )  {
+      splitList << period; // period occurs on the same day
+      continue;
+    }
+
+    const int validPeriodSecs = 5 * 60; // 5 minutes
+    KCalCore::Period tmpPeriod = period;
+    while ( tmpPeriod.start().date() != tmpPeriod.end().date() ) {
+      const KDateTime midnight( tmpPeriod.start().date(), QTime( 23, 59, 59, 999 ),
+                                tmpPeriod.start().timeSpec() );
+      KCalCore::Period firstPeriod( tmpPeriod.start(), midnight );
+      KCalCore::Period secondPeriod( midnight.addMSecs( 1 ), tmpPeriod.end() );
+      if ( firstPeriod.duration().asSeconds() >= validPeriodSecs ) {
+        splitList << firstPeriod;
+      }
+      tmpPeriod = secondPeriod;
+    }
+    if ( tmpPeriod.duration().asSeconds() >= validPeriodSecs ) {
+      splitList << tmpPeriod;
+    }
+  }
+
+  // Perform some jiggery pokery to remove duplicates
+  QList<KCalCore::Period> tmpList = splitList.toList();
+  QSet<KCalCore::Period>set = tmpList.toSet();
+  tmpList = QList<KCalCore::Period>::fromSet( set );
+  return KCalCore::Period::List::fromList( tmpList );
+}
+
+QString FreePeriodModel::day( int index ) const
+{
+  KCalCore::Period period = mPeriodList.at( index );
+  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
+  const QDate startDate = period.start().date();
+  return ki18nc( "@label Day of the week name, example: Monday,", "%1," ).
+    subs( calSys->weekDayName( startDate.dayOfWeek(), KCalendarSystem::LongDayName ) ).toString();
+}
+
+QString FreePeriodModel::date( int index ) const
+{
+  KCalCore::Period period = mPeriodList.at( index );
+  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
+
+  const QDate startDate = period.start().date();
+  const QString startTime = KGlobal::locale()->formatTime( period.start().time() );
+  const QString endTime = KGlobal::locale()->formatTime( period.end().time() );
+  const QString longMonthName = calSys->monthName( startDate );
+  return ki18nc( "@label A time period duration. It is preceded/followed (based on the "
+                 "orientation) by the name of the week, see the message above. "
+                 "example: 12 June, 8:00am to 9:30am",
+                 "%1 %2, %3 to %4" ).
+    subs( startDate.day() ).
+    subs( longMonthName ).
+    subs( startTime ).
+    subs( endTime ).toString();
+}
+
+QString FreePeriodModel::stringify( int index ) const
+{
+  KCalCore::Period period = mPeriodList.at( index );
+  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
+
+  const QDate startDate = period.start().date();
+  const QString startTime = KGlobal::locale()->formatTime( period.start().time(), false, true );
+  const QString endTime = KGlobal::locale()->formatTime( period.end().time(), false, true );
+  const QString longMonthName = calSys->monthName( startDate );
+  const QString dayofWeek = calSys->weekDayName( startDate.dayOfWeek(),
+                                                 KCalendarSystem::LongDayName );
+
+  // TODO i18n, ping chusslove
+  return ki18nc( "@label A time period duration. KLocale is used to format the components. "
+                 "example: Monday, 12 June, 8:00am to 9:30am",
+                 "%1, %2 %3, %4 to %5" ).
+    subs( dayofWeek ).
+    subs( startDate.day() ).
+    subs( longMonthName ).
+    subs( startTime ).
+    subs( endTime ).toString();
+}
+
+QString FreePeriodModel::tooltipify( int index ) const
+{
+  KDateTime::Spec timeSpec = KSystemTimeZones::local();
+  KCalCore::Period period = mPeriodList.at( index );
+  unsigned long duration = period.duration().asSeconds() * 1000; // we want milliseconds
+  QString toolTip = QLatin1String("<qt>");
+  toolTip += QLatin1String("<b>") + i18nc( "@info:tooltip", "Free Period" ) + QLatin1String("</b>");
+  toolTip += QLatin1String("<hr>");
+  toolTip += QLatin1String("<i>") + i18nc( "@info:tooltip period start time", "Start:" ) + QLatin1String("</i> ");
+  toolTip += KGlobal::locale()->formatDateTime( period.start().toTimeSpec( timeSpec ).dateTime() );
+  toolTip += QLatin1String("<br>");
+  toolTip += QLatin1String("<i>") + i18nc( "@info:tooltip period end time", "End:" ) + QLatin1String("</i> ");
+  toolTip += KGlobal::locale()->formatDateTime( period.end().toTimeSpec( timeSpec ).dateTime() );
+  toolTip += QLatin1String("<br>");
+  toolTip += QLatin1String("<i>") + i18nc( "@info:tooltip period duration", "Duration:" ) + QLatin1String("</i> ");
+  toolTip += KGlobal::locale()->prettyFormatDuration( duration );
+  toolTip += QLatin1String("</qt>");
+  return toolTip;
+}
+
diff --git a/libkdepim/freebusymodel/freeperiodmodel.h b/libkdepim/freebusymodel/freeperiodmodel.h
new file mode 100644
index 0000000..95dbf3f
--- /dev/null
+++ b/libkdepim/freebusymodel/freeperiodmodel.h
@@ -0,0 +1,62 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 INCIDENCEEDITOR_FREEPERIODMODEL_H
+#define INCIDENCEEDITOR_FREEPERIODMODEL_H
+
+#include "libkdepim/kdepim_export.h"
+
+#include <KCalCore/Period>
+
+#include <QAbstractTableModel>
+
+class KDEPIM_EXPORT FreePeriodModel : public QAbstractTableModel
+{
+  Q_OBJECT
+  public:
+    enum Roles {
+      PeriodRole = Qt::UserRole
+    };
+    explicit FreePeriodModel( QObject *parent = 0 );
+    virtual ~FreePeriodModel();
+
+    virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
+    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual QVariant headerData( int section, Qt::Orientation orientation,
+                                 int role = Qt::DisplayRole ) const;
+
+  public slots:
+    void slotNewFreePeriods( const KCalCore::Period::List &freePeriods );
+
+  private:
+    /** Splits period blocks in the provided list, so that each period occurs on one day */
+    KCalCore::Period::List splitPeriodsByDay( const KCalCore::Period::List &freePeriods );
+
+    QString day( int index ) const;
+    QString date( int index ) const;
+    QString stringify( int index ) const;
+    QString tooltipify( int index ) const;
+
+    KCalCore::Period::List mPeriodList;
+    friend class FreePeriodModelTest;
+};
+
+#endif
diff --git a/libkdepim/freebusymodel/tests/CMakeLists.txt b/libkdepim/freebusymodel/tests/CMakeLists.txt
new file mode 100644
index 0000000..9bfbfce
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/CMakeLists.txt
@@ -0,0 +1,22 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+include_directories(
+ ../
+)
+
+MACRO(IE_UNIT_TESTS)
+  FOREACH(_testname ${ARGN})
+    kde4_add_unit_test(${_testname} NOGUI ${_testname}.cpp modeltest.cpp)
+    target_link_libraries(${_testname}
+                          ${QT_QTTEST_LIBRARY}
+                          ${KDEPIMLIBS_AKONADI_LIBS}
+                          ${KDEPIMLIBS_KCALUTILS_LIBS}
+                          ${KDEPIMLIBS_KCALCORE_LIBS}
+                          kdepim)
+  ENDFOREACH(_testname)
+ENDMACRO(IE_UNIT_TESTS)
+
+IE_UNIT_TESTS(
+  testfreeperiodmodel
+  testfreebusyitemmodel
+)
\ No newline at end of file
diff --git a/libkdepim/freebusymodel/tests/modeltest.cpp b/libkdepim/freebusymodel/tests/modeltest.cpp
new file mode 100644
index 0000000..1227d20
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/modeltest.cpp
@@ -0,0 +1,643 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales at trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+//krazy:excludeall=style
+
+#include "modeltest.h"
+
+#include <QtGui>
+
+Q_DECLARE_METATYPE ( QModelIndex )
+
+/*!
+    Connect to all of the models signals.  Whenever anything happens recheck everything.
+*/
+ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false )
+{
+    Q_ASSERT ( model );
+
+    connect ( model, SIGNAL (columnsAboutToBeInserted(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (columnsAboutToBeRemoved(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (columnsInserted(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (columnsRemoved(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (dataChanged(QModelIndex,QModelIndex)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (headerDataChanged(Qt::Orientation,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (layoutAboutToBeChanged()), this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (layoutChanged()), this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (modelReset()), this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (modelAboutToBeReset()), this, SLOT (modelAboutToBeReset()) );
+    connect ( model, SIGNAL (modelReset()), this, SLOT (modelReset()) );
+    connect ( model, SIGNAL (rowsAboutToBeInserted(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (rowsAboutToBeRemoved(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (rowsInserted(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+    connect ( model, SIGNAL (rowsRemoved(QModelIndex,int,int)),
+              this, SLOT (runAllTests()) );
+
+    // Special checks for inserting/removing
+    connect ( model, SIGNAL (layoutAboutToBeChanged()),
+              this, SLOT (layoutAboutToBeChanged()) );
+    connect ( model, SIGNAL (layoutChanged()),
+              this, SLOT (layoutChanged()) );
+
+    connect ( model, SIGNAL (rowsAboutToBeInserted(QModelIndex,int,int)),
+              this, SLOT (rowsAboutToBeInserted(QModelIndex,int,int)) );
+    connect ( model, SIGNAL (rowsAboutToBeRemoved(QModelIndex,int,int)),
+              this, SLOT (rowsAboutToBeRemoved(QModelIndex,int,int)) );
+    connect ( model, SIGNAL (rowsInserted(QModelIndex,int,int)),
+              this, SLOT (rowsInserted(QModelIndex,int,int)) );
+    connect ( model, SIGNAL (rowsRemoved(QModelIndex,int,int)),
+              this, SLOT (rowsRemoved(QModelIndex,int,int)) );
+
+    connect ( model, SIGNAL (rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
+              this, SLOT (rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) );
+    connect ( model, SIGNAL (rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+              this, SLOT (rowsMoved(QModelIndex,int,int,QModelIndex,int)) );
+
+    runAllTests();
+}
+
+void ModelTest::runAllTests()
+{
+
+//     qDebug() << "rats";
+    if ( fetchingMore )
+        return;
+    nonDestructiveBasicTest();
+    rowCount();
+    columnCount();
+    hasIndex();
+    index();
+    parent();
+    data();
+}
+
+/*!
+    nonDestructiveBasicTest tries to call a number of the basic functions (not all)
+    to make sure the model doesn't outright segfault, testing the functions that makes sense.
+*/
+void ModelTest::nonDestructiveBasicTest()
+{
+    Q_ASSERT ( model->buddy ( QModelIndex() ) == QModelIndex() );
+//     model->canFetchMore ( QModelIndex() );
+    Q_ASSERT ( model->columnCount ( QModelIndex() ) >= 0 );
+    Q_ASSERT ( model->data ( QModelIndex() ) == QVariant() );
+//     fetchingMore = true;
+//     model->fetchMore ( QModelIndex() );
+//     fetchingMore = false;
+    Qt::ItemFlags flags = model->flags ( QModelIndex() );
+    Q_ASSERT ( flags == Qt::ItemIsDropEnabled || flags == 0 );
+    Q_UNUSED( flags );
+    model->hasChildren ( QModelIndex() );
+    model->hasIndex ( 0, 0 );
+    model->headerData ( 0, Qt::Horizontal );
+    model->index ( 0, 0 );
+    model->itemData ( QModelIndex() );
+    QVariant cache;
+    model->match ( QModelIndex(), -1, cache );
+    model->mimeTypes();
+    Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() );
+    Q_ASSERT ( model->rowCount() >= 0 );
+    QVariant variant;
+    model->setData ( QModelIndex(), variant, -1 );
+    model->setHeaderData ( -1, Qt::Horizontal, QVariant() );
+    model->setHeaderData ( 999999, Qt::Horizontal, QVariant() );
+    QMap<int, QVariant> roles;
+    model->sibling ( 0, 0, QModelIndex() );
+    model->span ( QModelIndex() );
+    model->supportedDropActions();
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
+
+    Models that are dynamically populated are not as fully tested here.
+ */
+void ModelTest::rowCount()
+{
+//     qDebug() << "rc";
+    // check top row
+    QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+    int rows = model->rowCount ( topIndex );
+    Q_ASSERT ( rows >= 0 );
+    if ( rows > 0 )
+        Q_ASSERT ( model->hasChildren ( topIndex ) == true );
+
+    QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex );
+    if ( secondLevelIndex.isValid() ) { // not the top level
+        // check a row count where parent is valid
+        rows = model->rowCount ( secondLevelIndex );
+        Q_ASSERT ( rows >= 0 );
+        if ( rows > 0 )
+            Q_ASSERT ( model->hasChildren ( secondLevelIndex ) == true );
+    }
+
+    // The models rowCount() is tested more extensively in checkChildren(),
+    // but this catches the big mistakes
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
+ */
+void ModelTest::columnCount()
+{
+    // check top row
+    QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+    Q_ASSERT ( model->columnCount ( topIndex ) >= 0 );
+
+    // check a column count where parent is valid
+    QModelIndex childIndex = model->index ( 0, 0, topIndex );
+    if ( childIndex.isValid() )
+        Q_ASSERT ( model->columnCount ( childIndex ) >= 0 );
+
+    // columnCount() is tested more extensively in checkChildren(),
+    // but this catches the big mistakes
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::hasIndex()
+ */
+void ModelTest::hasIndex()
+{
+//     qDebug() << "hi";
+    // Make sure that invalid values returns an invalid index
+    Q_ASSERT ( model->hasIndex ( -2, -2 ) == false );
+    Q_ASSERT ( model->hasIndex ( -2, 0 ) == false );
+    Q_ASSERT ( model->hasIndex ( 0, -2 ) == false );
+
+    int rows = model->rowCount();
+    int columns = model->columnCount();
+
+    // check out of bounds
+    Q_ASSERT ( model->hasIndex ( rows, columns ) == false );
+    Q_ASSERT ( model->hasIndex ( rows + 1, columns + 1 ) == false );
+
+    Q_UNUSED( columns );
+
+    if ( rows > 0 )
+        Q_ASSERT ( model->hasIndex ( 0, 0 ) == true );
+
+    // hasIndex() is tested more extensively in checkChildren(),
+    // but this catches the big mistakes
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::index()
+ */
+void ModelTest::index()
+{
+//     qDebug() << "i";
+    // Make sure that invalid values returns an invalid index
+    Q_ASSERT ( model->index ( -2, -2 ) == QModelIndex() );
+    Q_ASSERT ( model->index ( -2, 0 ) == QModelIndex() );
+    Q_ASSERT ( model->index ( 0, -2 ) == QModelIndex() );
+
+    int rows = model->rowCount();
+    int columns = model->columnCount();
+
+    if ( rows == 0 )
+        return;
+
+    // Catch off by one errors
+    Q_ASSERT ( model->index ( rows, columns ) == QModelIndex() );
+    Q_ASSERT ( model->index ( 0, 0 ).isValid() == true );
+
+    Q_UNUSED( columns );
+
+    // Make sure that the same index is *always* returned
+    QModelIndex a = model->index ( 0, 0 );
+    QModelIndex b = model->index ( 0, 0 );
+    Q_ASSERT ( a == b );
+
+    // index() is tested more extensively in checkChildren(),
+    // but this catches the big mistakes
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::parent()
+ */
+void ModelTest::parent()
+{
+//     qDebug() << "p";
+    // Make sure the model wont crash and will return an invalid QModelIndex
+    // when asked for the parent of an invalid index.
+    Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() );
+
+    if ( model->rowCount() == 0 )
+        return;
+
+    // Column 0                | Column 1    |
+    // QModelIndex()           |             |
+    //    \- topIndex          | topIndex1   |
+    //         \- childIndex   | childIndex1 |
+
+    // Common error test #1, make sure that a top level index has a parent
+    // that is a invalid QModelIndex.
+    QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+    Q_ASSERT ( model->parent ( topIndex ) == QModelIndex() );
+
+    // Common error test #2, make sure that a second level index has a parent
+    // that is the first level index.
+    if ( model->rowCount ( topIndex ) > 0 ) {
+        QModelIndex childIndex = model->index ( 0, 0, topIndex );
+        Q_ASSERT ( model->parent ( childIndex ) == topIndex );
+    }
+
+    // Common error test #3, the second column should NOT have the same children
+    // as the first column in a row.
+    // Usually the second column shouldn't have children.
+    QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() );
+    if ( model->rowCount ( topIndex1 ) > 0 ) {
+        QModelIndex childIndex = model->index ( 0, 0, topIndex );
+        QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 );
+        Q_ASSERT ( childIndex != childIndex1 );
+    }
+
+    // Full test, walk n levels deep through the model making sure that all
+    // parent's children correctly specify their parent.
+    checkChildren ( QModelIndex() );
+}
+
+/*!
+    Called from the parent() test.
+
+    A model that returns an index of parent X should also return X when asking
+    for the parent of the index.
+
+    This recursive function does pretty extensive testing on the whole model in an
+    effort to catch edge cases.
+
+    This function assumes that rowCount(), columnCount() and index() already work.
+    If they have a bug it will point it out, but the above tests should have already
+    found the basic bugs because it is easier to figure out the problem in
+    those tests then this one.
+ */
+void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth )
+{
+    // First just try walking back up the tree.
+    QModelIndex p = parent;
+    while ( p.isValid() )
+        p = p.parent();
+
+    // For models that are dynamically populated
+//     if ( model->canFetchMore ( parent ) ) {
+//         fetchingMore = true;
+//         model->fetchMore ( parent );
+//         fetchingMore = false;
+//     }
+
+    int rows = model->rowCount ( parent );
+    int columns = model->columnCount ( parent );
+
+    qDebug() << "checkChildren" << "parent=" << model->data ( parent ).value<QString>()
+    << "current count of parent=" << rows << "columns=" << columns;
+    
+    if ( rows > 0 )
+    {
+
+      Q_ASSERT(parent.column() <= 0);
+        Q_ASSERT ( model->hasChildren ( parent ) );
+    }
+
+    // Some further testing against rows(), columns(), and hasChildren()
+    Q_ASSERT ( rows >= 0 );
+    Q_ASSERT ( columns >= 0 );
+    if ( rows > 0 )
+        Q_ASSERT ( model->hasChildren ( parent ) == true );
+
+    //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
+    //         << "columns:" << columns << "parent column:" << parent.column();
+
+    Q_ASSERT ( model->hasIndex ( rows, 0, parent ) == false );
+    Q_ASSERT ( model->index(rows, 0, parent).isValid() == false );
+    for ( int r = 0; r < rows; ++r ) {
+//         if ( model->canFetchMore ( parent ) ) {
+//             fetchingMore = true;
+//             model->fetchMore ( parent );
+//             fetchingMore = false;
+//         }
+        Q_ASSERT ( model->hasIndex ( r, columns, parent ) == false );
+        Q_ASSERT ( model->index(r, columns, parent).isValid() == false );
+        for ( int c = 0; c < columns; ++c ) {
+            Q_ASSERT ( model->hasIndex ( r, c, parent ) == true );
+            QModelIndex index = model->index ( r, c, parent );
+            // rowCount() and columnCount() said that it existed...
+            Q_ASSERT ( index.isValid() == true );
+
+            // index() should always return the same index when called twice in a row
+            QModelIndex modifiedIndex = model->index ( r, c, parent );
+            Q_ASSERT ( index == modifiedIndex );
+
+            // Make sure we get the same index if we request it twice in a row
+            QModelIndex a = model->index ( r, c, parent );
+            QModelIndex b = model->index ( r, c, parent );
+            Q_ASSERT ( a == b );
+
+            // Some basic checking on the index that is returned
+            Q_ASSERT ( index.model() == model );
+            Q_ASSERT ( index.row() == r );
+            Q_ASSERT ( index.column() == c );
+            // While you can technically return a QVariant usually this is a sign
+            // of an bug in data()  Disable if this really is ok in your model.
+            qDebug() << index << index.data() << index.parent();
+            Q_ASSERT ( model->data ( index, Qt::DisplayRole ).isValid() == true );
+
+            // If the next test fails here is some somewhat useful debug you play with.
+            /*
+            if (model->parent(index) != parent) {
+                qDebug() << r << c << currentDepth << model->data(index).toString()
+                         << model->data(parent).toString();
+                qDebug() << index << parent << model->parent(index);
+                // And a view that you can even use to show the model.
+                //QTreeView view;
+                //view.setModel(model);
+                //view.show();
+            }*/
+
+            // Check that we can get back our real parent.
+            Q_ASSERT ( model->parent ( index ) == parent );
+
+            // recursively go down the children
+            if ( model->hasChildren ( index ) && currentDepth < 10 ) {
+                //qDebug() << r << c << "has children" << model->rowCount(index);
+                checkChildren ( index, ++currentDepth );
+            }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
+
+            // make sure that after testing the children that the index doesn't change.
+            QModelIndex newerIndex = model->index ( r, c, parent );
+            Q_ASSERT ( index == newerIndex );
+        }
+    }
+}
+
+/*!
+    Tests model's implementation of QAbstractItemModel::data()
+ */
+void ModelTest::data()
+{
+    // Invalid index should return an invalid qvariant
+    Q_ASSERT ( !model->data ( QModelIndex() ).isValid() );
+
+    if ( model->rowCount() == 0 )
+        return;
+
+    // A valid index should have a valid QVariant data
+    Q_ASSERT ( model->index ( 0, 0 ).isValid() );
+
+    // shouldn't be able to set data on an invalid index
+    Q_ASSERT ( model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) == false );
+
+    // General Purpose roles that should return a QString
+    QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole );
+    if ( variant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+    }
+    variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole );
+    if ( variant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+    }
+    variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole );
+    if ( variant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+    }
+
+    // General Purpose roles that should return a QSize
+    variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole );
+    if ( variant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QSize> ( variant ) );
+    }
+
+    // General Purpose roles that should return a QFont
+    QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole );
+    if ( fontVariant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QFont> ( fontVariant ) );
+    }
+
+    // Check that the alignment is one we know about
+    QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole );
+    if ( textAlignmentVariant.isValid() ) {
+        int alignment = textAlignmentVariant.toInt();
+        Q_ASSERT ( alignment == ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) );
+        Q_UNUSED( alignment );
+    }
+
+    // General Purpose roles that should return a QColor
+    QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole );
+    if ( colorVariant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QColor> ( colorVariant ) );
+    }
+
+    colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole );
+    if ( colorVariant.isValid() ) {
+        Q_ASSERT ( qVariantCanConvert<QColor> ( colorVariant ) );
+    }
+
+    // Check that the "check state" is one we know about.
+    QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole );
+    if ( checkStateVariant.isValid() ) {
+        int state = checkStateVariant.toInt();
+        Q_ASSERT ( state == Qt::Unchecked ||
+                   state == Qt::PartiallyChecked ||
+                   state == Qt::Checked );
+        Q_UNUSED( state );
+    }
+}
+
+/*!
+    Store what is about to be inserted to make sure it actually happens
+
+    \sa rowsInserted()
+ */
+void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end )
+{
+//     Q_UNUSED(end);
+    qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).value<QString>()
+    << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) );
+//     qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) );
+    Changing c;
+    c.parent = parent;
+    c.oldSize = model->rowCount ( parent );
+    c.last = model->data ( model->index ( start - 1, 0, parent ) );
+    c.next = model->data ( model->index ( start, 0, parent ) );
+    insert.push ( c );
+}
+
+/*!
+    Confirm that what was said was going to happen actually did
+
+    \sa rowsAboutToBeInserted()
+ */
+void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end )
+{
+    Changing c = insert.pop();
+    Q_ASSERT ( c.parent == parent );
+    qDebug() << "rowsInserted"  << "start=" << start << "end=" << end << "oldsize=" << c.oldSize
+    << "parent=" << model->data ( parent ).value<QString>() << "current rowcount=" << model->rowCount ( parent );
+
+    for (int ii=start; ii <= end; ii++)
+    {
+      qDebug() << "itemWasInserted:" << model->data ( model->index ( ii, 0, parent ));
+    }
+
+
+    Q_ASSERT ( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) );
+    Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
+    /*
+    if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
+        qDebug() << start << end;
+        for (int i=0; i < model->rowCount(); ++i)
+            qDebug() << model->index(i, 0).data().toString();
+        qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
+    }
+    */
+    Q_ASSERT ( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) );
+}
+
+
+void ModelTest::modelAboutToBeReset()
+{
+  qDebug() << "@@@@@@@@@@@" << "modelAboutToBeReset";
+}
+
+void ModelTest::modelReset()
+{
+  qDebug() << "@@@@@@@@@@@" << "modelReset";
+}
+
+void ModelTest::layoutAboutToBeChanged()
+{
+    for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i )
+        changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) );
+}
+
+void ModelTest::layoutChanged()
+{
+    for ( int i = 0; i < changing.count(); ++i ) {
+        QPersistentModelIndex p = changing[i];
+        Q_ASSERT ( p == model->index ( p.row(), p.column(), p.parent() ) );
+    }
+    changing.clear();
+}
+
+void ModelTest::rowsAboutToBeMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destinationRow )
+{
+  qDebug() << "rowsAboutToBeMoved" << srcParent << start << end << destParent << destinationRow;
+  Changing cs;
+  cs.parent = srcParent;
+  cs.oldSize = model->rowCount ( srcParent );
+  cs.last = model->data ( model->index ( start - 1, 0, srcParent ) );
+  cs.next = model->data ( model->index ( end + 1, 0, srcParent ) );
+  remove.push ( cs );
+  Changing cd;
+  cd.parent = destParent;
+  cd.oldSize = model->rowCount ( destParent );
+  cd.last = model->data ( model->index ( destinationRow - 1, 0, destParent ) );
+  cd.next = model->data ( model->index ( destinationRow, 0, destParent ) );
+  insert.push ( cd );
+}
+
+void ModelTest::rowsMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destinationRow )
+{
+  qDebug() << "rowsMoved" << srcParent << start << end << destParent << destinationRow;
+
+  Changing cd = insert.pop();
+  Q_ASSERT ( cd.parent == destParent );
+  if (srcParent == destParent)
+  {
+    Q_ASSERT ( cd.oldSize == model->rowCount ( destParent ) );
+
+    // TODO: Find out what I can assert here about last and next.
+//     Q_ASSERT ( cd.last == model->data ( model->index ( destinationRow - 1, 0, cd.parent ) ) );
+//     Q_ASSERT ( cd.next == model->data ( model->index ( destinationRow + (end - start + 1), 0, cd.parent ) ) );
+
+
+  }
+  else
+  {
+    Q_ASSERT ( cd.oldSize + ( end - start + 1 ) == model->rowCount ( destParent ) );
+
+    Q_ASSERT ( cd.last == model->data ( model->index ( destinationRow - 1, 0, cd.parent ) ) );
+    Q_ASSERT ( cd.next == model->data ( model->index ( destinationRow + (end - start + 1), 0, cd.parent ) ) );
+  }
+  Changing cs = remove.pop();
+
+  Q_ASSERT ( cs.parent == srcParent );
+  if (srcParent == destParent)
+  {
+    Q_ASSERT ( cs.oldSize == model->rowCount ( srcParent ) );
+  }
+  else
+  {
+    Q_ASSERT ( cs.oldSize - ( end - start + 1 ) == model->rowCount ( srcParent ) );
+
+    Q_ASSERT ( cs.last == model->data ( model->index ( start - 1, 0, srcParent ) ) );
+    qDebug() << cs.next << model->data ( model->index ( start, 0, srcParent ) );
+    Q_ASSERT ( cs.next == model->data ( model->index ( start, 0, srcParent ) ) );
+  }
+
+}
+
+/*!
+    Store what is about to be inserted to make sure it actually happens
+
+    \sa rowsRemoved()
+ */
+void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end )
+{
+qDebug() << "ratbr" << parent << start << end;
+    for (int ii=start; ii <= end; ii++)
+    {
+      qDebug() << "itemwillbe removed:" << model->data ( model->index ( ii, 0, parent ));
+    }
+
+
+    Changing c;
+    c.parent = parent;
+    c.oldSize = model->rowCount ( parent );
+    c.last = model->data ( model->index ( start - 1, 0, parent ) );
+    c.next = model->data ( model->index ( end + 1, 0, parent ) );
+    remove.push ( c );
+}
+
+/*!
+    Confirm that what was said was going to happen actually did
+
+    \sa rowsAboutToBeRemoved()
+ */
+void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end )
+{
+  qDebug() << "rr" << parent << start << end;
+    Changing c = remove.pop();
+    Q_ASSERT ( c.parent == parent );
+    Q_ASSERT ( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) );
+    Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
+    Q_ASSERT ( c.next == model->data ( model->index ( start, 0, c.parent ) ) );
+}
+
diff --git a/libkdepim/freebusymodel/tests/modeltest.h b/libkdepim/freebusymodel/tests/modeltest.h
new file mode 100644
index 0000000..0ed70be
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/modeltest.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales at trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+//krazy:excludeall=style
+
+#ifndef MODELTEST_H
+#define MODELTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStack>
+
+class ModelTest : public QObject
+{
+  Q_OBJECT
+
+public:
+  explicit ModelTest( QAbstractItemModel *model, QObject *parent = 0 );
+
+private Q_SLOTS:
+  void nonDestructiveBasicTest();
+  void rowCount();
+  void columnCount();
+  void hasIndex();
+  void index();
+  void parent();
+  void data();
+
+protected Q_SLOTS:
+  void runAllTests();
+  void layoutAboutToBeChanged();
+  void layoutChanged();
+  void modelAboutToBeReset();
+  void modelReset();
+  void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end );
+  void rowsInserted( const QModelIndex & parent, int start, int end );
+  void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end );
+  void rowsRemoved( const QModelIndex & parent, int start, int end );
+  void rowsAboutToBeMoved ( const QModelIndex &, int, int, const QModelIndex &, int);
+  void rowsMoved ( const QModelIndex &, int, int, const QModelIndex &, int );
+
+private:
+  void checkChildren( const QModelIndex &parent, int currentDepth = 0 );
+
+  QAbstractItemModel *model;
+
+  struct Changing {
+    QModelIndex parent;
+    int oldSize;
+    QVariant last;
+    QVariant next;
+  };
+  QStack<Changing> insert;
+  QStack<Changing> remove;
+
+  bool fetchingMore;
+
+  QList<QPersistentModelIndex> changing;
+};
+
+#endif
diff --git a/libkdepim/freebusymodel/tests/testfreebusyitemmodel.cpp b/libkdepim/freebusymodel/tests/testfreebusyitemmodel.cpp
new file mode 100644
index 0000000..c34b5d2
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/testfreebusyitemmodel.cpp
@@ -0,0 +1,240 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 "testfreebusyitemmodel.h"
+#include "modeltest.h"
+#include "freebusyitemmodel.h"
+#include "freebusyitem.h"
+
+#include <KCalCore/Attendee>
+
+#include <qtest_kde.h>
+
+QTEST_KDEMAIN( FreeBusyItemModelTest, NoGUI )
+
+void FreeBusyItemModelTest::testModelValidity()
+{
+  FreeBusyItemModel * model = new FreeBusyItemModel( this );
+  new ModelTest( model, this );
+
+  QVERIFY( model->rowCount() == 0 );
+
+  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
+  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee( QLatin1String("fred"), QLatin1String("fred at example.com") ) );
+  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
+
+  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+
+  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
+  item1->setFreeBusy( fb1 );
+
+  model->addItem( item1 );
+  QVERIFY( model->rowCount() == 1 );
+  QVERIFY( model->containsAttendee( a1 ) );
+
+  QModelIndex i = model->index( 0, 0 );
+  QCOMPARE( a1->fullName(), model->data( i, Qt::DisplayRole ).toString() );
+  QCOMPARE( a1,
+            model->data( i, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
+  QCOMPARE( item1->freeBusy(),
+            model->data( i, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
+
+  QCOMPARE( model->rowCount( i ), 2 );
+
+  model->removeRow( 0 );
+  QVERIFY( model->rowCount() == 0 );
+
+  model->addItem( item1 );
+  QVERIFY( model->rowCount() == 1 );
+
+  model->removeAttendee( a1 );
+  QVERIFY( model->rowCount() == 0 );
+
+  model->addItem( item1 );
+  QVERIFY( model->rowCount() == 1 );
+
+  model->removeItem( item1 );
+  QVERIFY( model->rowCount() == 0 );
+
+  model->addItem( item1 );
+  QVERIFY( model->rowCount() == 1 );
+
+  model->clear();
+  QVERIFY( model->rowCount() == 0 );
+}
+
+void FreeBusyItemModelTest::testModelValidity2()
+{
+  FreeBusyItemModel * model = new FreeBusyItemModel( this );
+  new ModelTest( model, this );
+
+  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt3( QDate( 2010, 7, 24 ), QTime( 12, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt4( QDate( 2010, 7, 24 ), QTime( 14, 0, 0 ), KDateTime::UTC );
+
+  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee(QLatin1String("fred"), QLatin1String("fred at example.com")));
+  KCalCore::Attendee::Ptr a2( new KCalCore::Attendee(QLatin1String("joe"), QLatin1String("joe at example.com")));
+  KCalCore::Attendee::Ptr a3( new KCalCore::Attendee(QLatin1String("max"), QLatin1String("max at example.com")));
+  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
+  KCalCore::FreeBusy::Ptr fb2( new KCalCore::FreeBusy() );
+  KCalCore::FreeBusy::Ptr fb3( new KCalCore::FreeBusy() );
+
+  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+
+  fb2->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb2->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+  fb2->addPeriod( dt3, KCalCore::Duration( 60 * 60 ) );
+
+  fb3->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb3->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+  fb3->addPeriod( dt4, KCalCore::Duration( 60 * 60 * 2 ) );
+
+  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
+  item1->setFreeBusy( fb1 );
+  FreeBusyItem::Ptr item2( new FreeBusyItem( a2, 0 ) );
+  FreeBusyItem::Ptr item3( new FreeBusyItem( a3, 0 ) );
+
+  model->addItem( item1 );
+  model->addItem( item2 );
+  model->addItem( item3 );
+
+  QCOMPARE( model->rowCount(), 3 );
+
+  QVERIFY( model->containsAttendee( a1 ) );
+  QVERIFY( model->containsAttendee( a2 ) );
+  QVERIFY( model->containsAttendee( a3 ) );
+
+  QModelIndex i1 = model->index( 0, 0 );
+  QCOMPARE( a1->fullName(), model->data( i1, Qt::DisplayRole ).toString() );
+  QCOMPARE( a1,
+            model->data( i1, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
+  QCOMPARE( item1->freeBusy(),
+            model->data( i1, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
+
+  QModelIndex i2 = model->index( 1, 0 );
+  QCOMPARE( a2->fullName(), model->data( i2, Qt::DisplayRole ).toString() );
+  QCOMPARE( a2,
+            model->data( i2, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
+  QVERIFY( model->rowCount( i2 ) == 0 );
+  QVERIFY( model->data( i2, FreeBusyItemModel::FreeBusyRole ).isValid() == false );
+
+  QModelIndex i3 = model->index( 2, 0 );
+  QCOMPARE( a3->fullName(),
+            model->data( i3, Qt::DisplayRole ).toString() );
+  QCOMPARE( a3,
+            model->data( i3, FreeBusyItemModel::AttendeeRole ).value<KCalCore::Attendee::Ptr>() );
+  QVERIFY( model->rowCount( i3 ) == 0 );
+  QVERIFY( model->data( i3, FreeBusyItemModel::FreeBusyRole ).isValid() == false );
+
+  model->slotInsertFreeBusy( fb2, QLatin1String("joe at example.com"));
+  QCOMPARE( item2->freeBusy(),
+            model->data( i2, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
+  QVERIFY( model->rowCount( i2 ) == fb2->fullBusyPeriods().size() );
+
+  QModelIndex i2_0 = model->index( 0, 0, i2 );
+  QCOMPARE( fb2->fullBusyPeriods().first(),
+            model->data(
+              i2_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  QModelIndex i2_1 = model->index( 1, 0, i2 );
+  QCOMPARE( fb2->fullBusyPeriods().at( 1 ),
+            model->data(
+              i2_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  QModelIndex i2_2 = model->index( 2, 0, i2 );
+  QCOMPARE( fb2->fullBusyPeriods().last(),
+            model->data(
+              i2_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+
+  model->slotInsertFreeBusy( fb3, QLatin1String("max at example.com"));
+  QCOMPARE( item3->freeBusy(),
+            model->data( i3, FreeBusyItemModel::FreeBusyRole ).value<KCalCore::FreeBusy::Ptr>() );
+  QVERIFY( model->rowCount( i3 ) == fb3->fullBusyPeriods().size() );
+
+  QModelIndex i3_0 = model->index( 0, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().first(),
+            model->data(
+              i3_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  QModelIndex i3_1 = model->index( 1, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().at( 1 ),
+            model->data(
+              i3_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  QModelIndex i3_2 = model->index( 2, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().last(),
+            model->data(
+              i3_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+
+  model->removeAttendee( a2 );
+
+  QCOMPARE( 2, model->rowCount() );
+
+  QVERIFY( model->containsAttendee( a1 ) == true );
+  QVERIFY( model->containsAttendee( a2 ) == false );
+  QVERIFY( model->containsAttendee( a3 ) == true );
+
+  i3_0 = model->index( 0, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().first(),
+            model->data(
+              i3_0, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  i3_1 = model->index( 1, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().at( 1 ),
+            model->data(
+              i3_1, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+  i3_2 = model->index( 2, 0, i3 );
+  QCOMPARE( fb3->fullBusyPeriods().last(),
+            model->data(
+              i3_2, FreeBusyItemModel::FreeBusyPeriodRole ).value<KCalCore::FreeBusyPeriod>() );
+}
+
+void FreeBusyItemModelTest::testInsertFreeBusy()
+{
+  FreeBusyItemModel * model = new FreeBusyItemModel( this );
+  new ModelTest( model, this );
+
+  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
+  KCalCore::Attendee::Ptr a1( new KCalCore::Attendee( QLatin1String("fred"), QLatin1String("fred at example.com")) );
+  KCalCore::FreeBusy::Ptr fb1( new KCalCore::FreeBusy() );
+  fb1->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb1->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+
+  const KDateTime dt3( QDate( 2010, 7, 24 ), QTime( 12, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt4( QDate( 2010, 7, 24 ), QTime( 14, 0, 0 ), KDateTime::UTC );
+  KCalCore::FreeBusy::Ptr fb2( new KCalCore::FreeBusy() );
+  fb2->addPeriod( dt1, KCalCore::Duration( 60 * 60 ) );
+  fb2->addPeriod( dt2, KCalCore::Duration( 60 * 60 ) );
+  fb2->addPeriod( dt3, KCalCore::Duration( 60 * 60 ) );
+  fb2->addPeriod( dt4, KCalCore::Duration( 60 * 60 * 2 ) );
+
+  FreeBusyItem::Ptr item1( new FreeBusyItem( a1, 0 ) );
+  item1->setFreeBusy( fb1 );
+
+  model->addItem( item1 );
+
+  QModelIndex i = model->index( 0, 0 );
+  QCOMPARE( model->rowCount( i ), 2 );
+
+  model->slotInsertFreeBusy( fb2, QLatin1String("fred at example.com"));
+
+  QCOMPARE( model->rowCount( i ), 4 );
+}
+
diff --git a/libkdepim/freebusymodel/tests/testfreebusyitemmodel.h b/libkdepim/freebusymodel/tests/testfreebusyitemmodel.h
new file mode 100644
index 0000000..2a16694
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/testfreebusyitemmodel.h
@@ -0,0 +1,33 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 TESTFREEBUSYITEMMODEL_H
+#define TESTFREEBUSYITEMMODEL_H
+
+#include <QObject>
+
+class FreeBusyItemModelTest: public QObject
+{
+  Q_OBJECT
+  private Q_SLOTS:
+    void testModelValidity();
+    void testModelValidity2();
+    void testInsertFreeBusy();
+};
+#endif
diff --git a/libkdepim/freebusymodel/tests/testfreeperiodmodel.cpp b/libkdepim/freebusymodel/tests/testfreeperiodmodel.cpp
new file mode 100644
index 0000000..dd1b524
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/testfreeperiodmodel.cpp
@@ -0,0 +1,87 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 "testfreeperiodmodel.h"
+#include "modeltest.h"
+#include "freeperiodmodel.h"
+
+#include <KCalCore/Period>
+#include <KCalCore/Duration>
+
+#include <KDebug>
+
+#include <qtest_kde.h>
+
+QTEST_KDEMAIN( FreePeriodModelTest, NoGUI )
+
+void FreePeriodModelTest::testModelValidity()
+{
+  FreePeriodModel * model = new FreePeriodModel( this );
+  new ModelTest( model, this );
+
+  const KDateTime dt1( QDate( 2010, 7, 24 ), QTime( 7, 0, 0 ), KDateTime::UTC );
+  const KDateTime dt2( QDate( 2010, 7, 24 ), QTime( 10, 0, 0 ), KDateTime::UTC );
+
+  KCalCore::Period::List list;
+
+  list << KCalCore::Period( dt1, KCalCore::Duration( 60 * 60 ) );
+  list << KCalCore::Period( dt2, KCalCore::Duration( 60 * 60 ) );
+
+  QVERIFY( model->rowCount() == 0 );
+  model->slotNewFreePeriods( list );
+  QCOMPARE( model->rowCount(), 2 );
+}
+
+void FreePeriodModelTest::testSplitByDay()
+{
+  FreePeriodModel * model = new FreePeriodModel( this );
+  new ModelTest( model, this );
+
+  const KDateTime startDt( QDate( 2010, 7, 24 ), QTime( 8, 0, 0 ), KDateTime::UTC );
+  const KDateTime endDt( QDate( 2010, 7, 25 ), QTime( 8, 0, 0 ), KDateTime::UTC );
+
+  KCalCore::Period::List list;
+
+  // This period goes from 8am on the 24th to 8am on the 25th
+  list << KCalCore::Period( startDt, endDt );
+
+  QVERIFY( model->rowCount() == 0 );
+
+  // as part of adding the new periods
+  // the model should split the above period into two
+  // one from 8am-12 on the 24th, and the second from 00-08 on the 25th
+  model->slotNewFreePeriods( list );
+
+  const KDateTime endPeriod1( QDate( 2010, 7, 24 ), QTime( 23, 59, 59, 999 ), KDateTime::UTC );
+  const KDateTime startPeriod2( QDate( 2010, 7, 25 ), QTime( 0, 0, 0, 0 ), KDateTime::UTC );
+
+  QModelIndex index = model->index( 0, 0 );
+  KCalCore::Period period1 =
+    model->data( index, FreePeriodModel::PeriodRole ).value<KCalCore::Period>();
+  index = model->index( 1, 0 );
+  KCalCore::Period period2 =
+    model->data( index, FreePeriodModel::PeriodRole ).value<KCalCore::Period>();
+
+  QCOMPARE( period1.start(), startDt );
+  QCOMPARE( period1.end(), endPeriod1 );
+  QCOMPARE( period2.start(), startPeriod2 );
+  QCOMPARE( period2.end(), endDt );
+}
+
diff --git a/libkdepim/freebusymodel/tests/testfreeperiodmodel.h b/libkdepim/freebusymodel/tests/testfreeperiodmodel.h
new file mode 100644
index 0000000..7d60b62
--- /dev/null
+++ b/libkdepim/freebusymodel/tests/testfreeperiodmodel.h
@@ -0,0 +1,32 @@
+/*
+  Copyright (C) 2010 Casey Link <unnamedrambler at gmail.com>
+  Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info at kdab.net>
+
+  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 TESTFREEPERIODMODEL_H
+#define TESTFREEPERIODMODEL_H
+
+#include <QObject>
+
+class FreePeriodModelTest: public QObject
+{
+  Q_OBJECT
+  private Q_SLOTS:
+    void testModelValidity();
+    void testSplitByDay();
+};
+#endif




More information about the commits mailing list