[kde-doc-english] [kdepim] kaddressbook: KAddressBook: Add a category filter

Jonathan Marten jjm at keelhaul.me.uk
Fri May 2 20:19:37 UTC 2014


Git commit 0a92fb453bad60345291d4e585d6266a41c84dae by Jonathan Marten.
Committed on 02/05/2014 at 20:14.
Pushed by marten into branch 'master'.

KAddressBook: Add a category filter

This is a widget which by default appears to the right of the
quick search bar.  Checking or unchecking categories in this list
filters the entries that are shown.

FEATURE:332103
REVIEW:117834
FIXED-IN:4.14
GUI:

M  +2    -0    kaddressbook/CMakeLists.txt
A  +150  -0    kaddressbook/categoryfilterproxymodel.cpp     [License: LGPL (v2+)]
A  +86   -0    kaddressbook/categoryfilterproxymodel.h     [License: LGPL (v2+)]
A  +316  -0    kaddressbook/categoryselectwidget.cpp     [License: LGPL (v2+)]
A  +87   -0    kaddressbook/categoryselectwidget.h     [License: LGPL (v2+)]
M  +2    -1    kaddressbook/kaddressbookui.rc
M  +24   -4    kaddressbook/mainwidget.cpp
M  +5    -0    kaddressbook/mainwidget.h
M  +2    -1    kaddressbook/settings/kaddressbook.kcfg

http://commits.kde.org/kdepim/0a92fb453bad60345291d4e585d6266a41c84dae

diff --git a/kaddressbook/CMakeLists.txt b/kaddressbook/CMakeLists.txt
index 8c39833..9feea1c 100644
--- a/kaddressbook/CMakeLists.txt
+++ b/kaddressbook/CMakeLists.txt
@@ -89,6 +89,8 @@ set(kaddressbook_merge_SRCS
 set(kaddressbook_LIB_SRCS
   startup.cpp
   aboutdata.cpp
+  categoryfilterproxymodel.cpp
+  categoryselectwidget.cpp
   contactfields.cpp
   contactselectiondialog.cpp
   contactselectionwidget.cpp
diff --git a/kaddressbook/categoryfilterproxymodel.cpp b/kaddressbook/categoryfilterproxymodel.cpp
new file mode 100644
index 0000000..f006274
--- /dev/null
+++ b/kaddressbook/categoryfilterproxymodel.cpp
@@ -0,0 +1,150 @@
+/*
+    Copyright (c) 2014 Jonathan Marten <jjm at keelhaul.me.uk>
+
+    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 "categoryfilterproxymodel.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <akonadi/entitytreemodel.h>
+#include <akonadi/item.h>
+
+#include <kabc/addressee.h>
+#include <kabc/contactgroup.h>
+
+#include "categoryselectwidget.h"
+
+using namespace Akonadi;
+
+
+class CategoryFilterProxyModelPrivate : public QObject
+{
+    Q_OBJECT
+    Q_DECLARE_PUBLIC(CategoryFilterProxyModel);
+
+public:
+    CategoryFilterProxyModelPrivate(CategoryFilterProxyModel *parent);
+
+    QList<Tag::Id> filterIdList;
+    bool filterEnabled;
+
+private:
+    CategoryFilterProxyModel *q_ptr;
+};
+
+
+CategoryFilterProxyModelPrivate::CategoryFilterProxyModelPrivate(CategoryFilterProxyModel *parent)
+    : QObject(),
+      filterEnabled(false),
+      q_ptr(parent)
+{
+}
+
+
+CategoryFilterProxyModel::CategoryFilterProxyModel(QObject *parent)
+    : QSortFilterProxyModel(parent),
+      d_ptr(new CategoryFilterProxyModelPrivate(this))
+{
+    setDynamicSortFilter(true);
+}
+
+
+CategoryFilterProxyModel::~CategoryFilterProxyModel()
+{
+    delete d_ptr;
+}
+
+
+bool CategoryFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
+{
+    Q_D(const CategoryFilterProxyModel);
+
+    const QModelIndex index = sourceModel()->index(row, 0, parent);
+    const Akonadi::Item item = index.data(EntityTreeModel::ItemRole).value<Akonadi::Item>();
+
+    if (!d->filterEnabled) return true;			// filter not enabled
+    if (d->filterIdList.isEmpty()) return false;	// nothing accepted
+							// all accepted
+    if (d->filterIdList.first()==CategorySelectWidget::FilterAll) return true;
+
+    //kDebug() << "for row" << row << "item" << item.url() << "filter" << d->filterIdList;
+    if (item.hasPayload<KABC::Addressee>()) {
+        const KABC::Addressee contact = item.payload<KABC::Addressee>();
+
+        const QStringList categories = contact.categories();
+        //kDebug() << "is contact" << contact.assembledName() << "cats" << categories;
+
+        int validCategories = 0;
+        int count = categories.count();
+        for (int i = 0; i<count; ++i) {
+            const QString cat = categories.at(i);
+            if (cat.startsWith(QLatin1String("akonadi:"))) {
+                const int idx = cat.indexOf(QLatin1String("?tag="));
+                if (idx>=0) {
+                    ++validCategories;
+                    Tag::Id id = cat.mid(idx+5).toInt();
+                    if (d->filterIdList.contains(id)) {
+                        //kDebug() << "matches category" << cat;
+                        return true;			// a category matches filter
+                    }
+                }
+            }
+        }
+
+        if (validCategories>0) {
+            //kDebug() << "valid item but no match";
+            return false;				// categorised but no match
+        } else {
+            //kDebug() << "item with no categories";
+            return d->filterIdList.contains(CategorySelectWidget::FilterUntagged);
+        }
+    }
+    else if (item.hasPayload<KABC::ContactGroup>()) {	// a contact group item
+        return d->filterIdList.contains(CategorySelectWidget::FilterGroups);
+    }
+
+    return true;					// not a recognised item
+}
+
+
+void CategoryFilterProxyModel::setFilterCategories(const QList<Akonadi::Tag::Id> &idList)
+{
+    Q_D(CategoryFilterProxyModel);
+
+    if (idList!=d->filterIdList) {
+        kDebug() << idList;
+        d->filterIdList = idList;
+        invalidateFilter();
+    }
+}
+
+
+void CategoryFilterProxyModel::setFilterEnabled(bool enable)
+{
+    Q_D(CategoryFilterProxyModel);
+
+    if (enable!=d->filterEnabled) {
+        kDebug() << enable;
+        d->filterEnabled = enable;
+        invalidateFilter();
+    }
+}
+
+
+#include "categoryfilterproxymodel.moc"
diff --git a/kaddressbook/categoryfilterproxymodel.h b/kaddressbook/categoryfilterproxymodel.h
new file mode 100644
index 0000000..f863abf
--- /dev/null
+++ b/kaddressbook/categoryfilterproxymodel.h
@@ -0,0 +1,86 @@
+/*
+    Copyright (c) 2014 Jonathan Marten <jjm at keelhaul.me.uk>
+
+    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 CATEGORYFILTERPROXYMODEL_H
+#define CATEGORYFILTERPROXYMODEL_H
+
+#include <qsortfilterproxymodel.h>
+
+#include <akonadi/tag.h>
+
+class CategoryFilterProxyModelPrivate;
+
+
+/**
+ * @short A proxy model to filter contacts by categories (tags).
+ *
+ * @since 4.14
+ * @author Jonathan Marten
+ **/
+
+class CategoryFilterProxyModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+    Q_DECLARE_PRIVATE(CategoryFilterProxyModel);
+
+public:
+    /**
+     * Constructor.
+     *
+     * @param parent The parent object
+     **/
+    explicit CategoryFilterProxyModel(QObject *parent = 0);
+
+    /**
+     * Destructor.
+     **/
+    virtual ~CategoryFilterProxyModel();
+
+public slots:
+    /**
+     * Set the categories to be accepted by the filter.
+     *
+     * @param idList A list of @c Akonadi::Tag::Id's of the categories
+     * which are to be accepted by the filter.
+     * @see CategorySelectModel::filterChanged
+     **/
+    void setFilterCategories(const QList<Akonadi::Tag::Id> &idList);
+
+    /**
+     * Enable or disable the filter.
+     *
+     * @param enable If @c true, enable the filter to accept only those categories
+     * set by @c setFilterCategories().  If @false, disable the filter so that all
+     * entries are accepted.
+     *
+     * The default state is that the filter is disabled.
+     **/
+    void setFilterEnabled(bool enable);
+
+protected:
+    /**
+     * @reimp
+     **/
+    virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const;
+
+private:
+    CategoryFilterProxyModelPrivate * const d_ptr;
+};
+
+#endif							// CATEGORYFILTERPROXYMODEL_H
diff --git a/kaddressbook/categoryselectwidget.cpp b/kaddressbook/categoryselectwidget.cpp
new file mode 100644
index 0000000..cca1028
--- /dev/null
+++ b/kaddressbook/categoryselectwidget.cpp
@@ -0,0 +1,316 @@
+/*
+    Copyright (c) 2014 Jonathan Marten <jjm at keelhaul.me.uk>
+
+    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 "categoryselectwidget.h"
+
+#include <kicon.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kdialog.h>
+
+#include <qtoolbutton.h>
+#include <qlayout.h>
+#include <qstandarditemmodel.h>
+#include <qtimer.h>
+
+#include <akonadi/monitor.h>
+#include <akonadi/tagmodel.h>
+
+#include <widgets/kcheckcombobox.h>
+
+using namespace Akonadi;
+
+
+static const int FILTER_ROLE = Qt::UserRole+1;
+
+
+class CategorySelectWidgetPrivate : public QObject
+{
+    Q_OBJECT
+    Q_DECLARE_PUBLIC(CategorySelectWidget);
+
+public:
+    CategorySelectWidgetPrivate(CategorySelectWidget *parent);
+
+    Akonadi::TagModel *tagModel;
+    int rowOffset;
+    QTimer *updateTimer;
+    KPIM::KCheckComboBox *checkCombo;
+
+    void init();
+    QStandardItemModel *itemModel() const;
+    void selectAll(Qt::CheckState state) const;
+    QList<Akonadi::Tag::Id> filterTags() const;
+
+public slots:
+    void slotSelectAll();
+    void slotSelectNone();
+
+    void slotTagsInserted(const QModelIndex &parent, int start, int end);
+    void slotTagsRemoved(const QModelIndex &parent, int start, int end);
+    void slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+
+    void slotCheckedItemsChanged();
+    void slotCheckedItemsTimer();
+
+private:
+    CategorySelectWidget *q_ptr;
+};
+
+
+CategorySelectWidgetPrivate::CategorySelectWidgetPrivate(CategorySelectWidget *parent)
+    : QObject(),
+      q_ptr(parent)
+{
+}
+
+
+void CategorySelectWidgetPrivate::init()
+{
+    Q_Q(CategorySelectWidget);
+
+    QHBoxLayout *hbox = new QHBoxLayout(q);
+    hbox->setSpacing(0);
+
+    checkCombo = new KPIM::KCheckComboBox;
+    checkCombo->setSqueezeText(true);
+    connect(checkCombo, SIGNAL(checkedItemsChanged(QStringList)),
+            SLOT(slotCheckedItemsChanged()));
+    hbox->addWidget(checkCombo);
+
+    Monitor *monitor = new Monitor(this);
+    monitor->setTypeMonitored(Monitor::Tags);
+    tagModel = new Akonadi::TagModel(monitor, this);
+
+    connect(tagModel, SIGNAL(rowsInserted(const QModelIndex &,int,int)),
+            SLOT(slotTagsInserted(const QModelIndex &,int,int)));
+    connect(tagModel, SIGNAL(rowsRemoved(const QModelIndex &,int,int)),
+            SLOT(slotTagsRemoved(const QModelIndex &,int,int)));
+    connect(tagModel, SIGNAL(dataChanged(const QModelIndex &,const QModelIndex &)),
+            SLOT(slotTagsChanged(const QModelIndex &,const QModelIndex &)));
+
+    updateTimer = new QTimer(this);
+    updateTimer->setSingleShot(true);
+    updateTimer->setInterval(200);
+    connect(updateTimer, SIGNAL(timeout()), SLOT(slotCheckedItemsTimer()));
+
+    hbox->addSpacing(KDialog::spacingHint());
+
+    QToolButton *but = new QToolButton(q);
+    but ->setAutoRaise(true);
+    but->setIcon(KIcon(QLatin1String("edit-undo")));
+    but->setToolTip(i18nc("@action:button", "Reset category filter"));
+    connect(but, SIGNAL(clicked(bool)), SLOT(slotSelectAll()));
+    hbox->addWidget(but);
+
+    but = new QToolButton(q);
+    but->setAutoRaise(true);
+    but->setIcon(KIcon(QLatin1String("edit-clear")));
+    but->setToolTip(i18nc("@action:button", "Clear category filter"));
+    connect(but, SIGNAL(clicked(bool)), SLOT(slotSelectNone()));
+    hbox->addWidget(but);
+
+    QStandardItem *item = new QStandardItem(i18n("(Untagged)"));
+    item->setCheckState(Qt::Checked);
+    item->setData(CategorySelectWidget::FilterUntagged, FILTER_ROLE);
+    itemModel()->appendRow(item);
+
+    item = new QStandardItem(i18n("(Groups)"));
+    item->setCheckState(Qt::Checked);
+    item->setData(CategorySelectWidget::FilterGroups, FILTER_ROLE);
+    itemModel()->appendRow(item);
+
+    rowOffset = itemModel()->rowCount();
+}
+
+
+QStandardItemModel *CategorySelectWidgetPrivate::itemModel() const
+{
+    QStandardItemModel *m = qobject_cast<QStandardItemModel *>(checkCombo->model());
+    Q_ASSERT(m!=NULL);
+    return m;
+}
+
+
+void CategorySelectWidgetPrivate::slotTagsRemoved(const QModelIndex &parent, int start, int end)
+{
+    itemModel()->removeRows(start+rowOffset, end+rowOffset, parent);
+}
+
+
+void CategorySelectWidgetPrivate::slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+    for (int row = topLeft.row(); row<=bottomRight.row(); ++row) {
+        QStandardItem *it = itemModel()->item(row+rowOffset);
+        Q_ASSERT(it!=NULL);
+
+        QModelIndex idx = tagModel->index(row, 0);
+        it->setText(tagModel->data(idx, TagModel::NameRole).toString());
+        it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>());
+        it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE);
+    }
+}
+
+
+void CategorySelectWidgetPrivate::slotTagsInserted(const QModelIndex &parent, int start, int end)
+{
+    for (int row = start; row<=end; ++row) {
+        QModelIndex idx = tagModel->index(row, 0, parent);
+        kDebug() << "idx" << idx << "=" << tagModel->data(idx, Qt::DisplayRole).toString()
+            << "name" << tagModel->data(idx, TagModel::NameRole).toString()
+            << "tag" << tagModel->data(idx, TagModel::TagRole)
+            << "id" << tagModel->data(idx, TagModel::IdRole).toInt();
+
+        QStandardItem *it = new QStandardItem(tagModel->data(idx, TagModel::NameRole).toString());
+        it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>());
+        it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE);
+        it->setCheckState(Qt::Checked);
+
+        // If a tag with a parent arrives from the model, we know that its parent
+        // must already have arrived.  So there is no need for a list of pending
+        // tags, as is required in Akonadi::TagModel.
+        //
+        // FIXME: not tested (no way to create hierarchial tags at present)
+        if (parent!=QModelIndex()) {
+            const Tag::Id parentId = tagModel->data(idx, TagModel::IdRole).value<Tag::Id>();
+            QModelIndexList matchList = itemModel()->match(itemModel()->index(0, 0), FILTER_ROLE,
+                                                           parentId, 1,
+                                                           Qt::MatchExactly|Qt::MatchRecursive);
+            if (matchList.count()==1) {			// found the parent tag
+                QModelIndex parentIndex = matchList.first();
+                itemModel()->itemFromIndex(parentIndex)->appendRow(it);
+            } else {
+                kWarning() << "Cannot find parent with ID" << parentId;
+                itemModel()->insertRow(row+rowOffset, it);
+            }
+        } else {
+            itemModel()->insertRow(row+rowOffset, it);
+        }
+    }
+}
+
+
+void CategorySelectWidgetPrivate::selectAll(Qt::CheckState state) const
+{
+    for (int row = 0; row<itemModel()->rowCount(); ++row) {
+        QStandardItem *it = itemModel()->item(row);
+        it->setCheckState(state);
+    }
+}
+
+
+void CategorySelectWidgetPrivate::slotSelectAll()
+{
+    selectAll(Qt::Checked);
+}
+
+
+void CategorySelectWidgetPrivate::slotSelectNone()
+{
+    selectAll(Qt::Unchecked);
+}
+
+
+void CategorySelectWidgetPrivate::slotCheckedItemsChanged()
+{
+    updateTimer->start();
+}
+
+
+void CategorySelectWidgetPrivate::slotCheckedItemsTimer()
+{
+    Q_Q(CategorySelectWidget);
+
+    bool allOn = true;
+    for (int row = 0; row<itemModel()->rowCount(); ++row) {
+        const QStandardItem *it = itemModel()->item(row);
+        Qt::CheckState rowState = static_cast<Qt::CheckState>(it->data(Qt::CheckStateRole).toInt());
+        if (rowState!=Qt::Checked) {
+            allOn = false;
+            break;
+        }
+    }
+
+    if (allOn) {
+        checkCombo->setAlwaysShowDefaultText(true);
+        checkCombo->setDefaultText(i18n("(All)"));
+    } else {
+        checkCombo->setAlwaysShowDefaultText(false);
+        checkCombo->setDefaultText(i18n("(None)"));
+    }
+
+    const QStringList checkedList = checkCombo->checkedItems();
+    if (!checkedList.isEmpty()) {
+        checkCombo->setToolTip(i18n("<qt>Category filter: %1", checkedList.join(i18n(", "))));
+    } else {
+        checkCombo->setToolTip(QString());
+    }
+
+    emit q->filterChanged(filterTags());
+}
+
+
+QList<Akonadi::Tag::Id> CategorySelectWidgetPrivate::filterTags() const
+{
+    QList<Tag::Id> filter;
+    bool allOn = true;
+    for (int row = 0; row<itemModel()->rowCount(); ++row) {
+        const QStandardItem *it = itemModel()->item(row);
+        Q_ASSERT(it!=NULL);
+        if (it->checkState()==Qt::Checked) {
+            Tag::Id id = it->data(FILTER_ROLE).toInt();
+            if (id!=0) filter.append(id);
+        } else {
+            allOn = false;
+        }
+    }
+
+    if (allOn) {
+        filter.clear();
+        filter.append(CategorySelectWidget::FilterAll);
+    }
+
+    kDebug() << "filter" << filter;
+    return filter;
+}
+
+
+CategorySelectWidget::CategorySelectWidget(QWidget *parent)
+    : QWidget(parent),
+      d_ptr(new CategorySelectWidgetPrivate(this))
+{
+    Q_D(CategorySelectWidget);
+    d->init();
+}
+
+
+CategorySelectWidget::~CategorySelectWidget()
+{
+    delete d_ptr;
+}
+
+
+QList<Akonadi::Tag::Id> CategorySelectWidget::filterTags() const
+{
+    Q_D(const CategorySelectWidget);
+    return d->filterTags();
+}
+
+
+#include "categoryselectwidget.moc"
diff --git a/kaddressbook/categoryselectwidget.h b/kaddressbook/categoryselectwidget.h
new file mode 100644
index 0000000..eff9fc3
--- /dev/null
+++ b/kaddressbook/categoryselectwidget.h
@@ -0,0 +1,87 @@
+/*
+    Copyright (c) 2014 Jonathan Marten <jjm at keelhaul.me.uk>
+
+    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 CATEGORYSELECTWIDGET_H
+#define CATEGORYSELECTWIDGET_H
+
+#include <qwidget.h>
+#include <akonadi/tag.h>
+
+class CategorySelectWidgetPrivate;
+
+/**
+ * @short A widget to specify a category (tag) filter.
+ *
+ * @since 4.14
+ * @author Jonathan Marten
+ **/
+
+class CategorySelectWidget : public QWidget
+{
+    Q_OBJECT
+    Q_DECLARE_PRIVATE(CategorySelectWidget);
+
+public:
+    /**
+     * Constructor.
+     *
+     * @param parent The parent widget
+     **/
+    explicit CategorySelectWidget(QWidget *parent = 0);
+
+    /**
+     * Destructor.
+     **/
+    virtual ~CategorySelectWidget();
+
+    /**
+     * Special @c Akonadi::Tag::Id values for filtering.
+     **/
+    enum FilterTag
+    {
+        FilterAll = -2,					/**< All items */
+        FilterUntagged = -3,				/**< Untagged items */
+        FilterGroups = -4				/**< Contact groups */
+    };
+
+    /**
+     * Get the current tag filter list.
+     *
+     * @return The filter list, as would be sent by @c filterChanged()
+     *
+     * @see filterChanged
+     **/
+    QList<Akonadi::Tag::Id> filterTags() const;
+
+signals:
+    /**
+     * The tag filter selection has changed.
+     *
+     * @param idList A list of @c Akonadi::Tag::Id's of the tags which
+     * are to be included in the filter.
+     *
+     * @see CategorySelectModel::filterChanged
+     **/
+    void filterChanged(const QList<Akonadi::Tag::Id> &idList);
+
+private:
+    CategorySelectWidgetPrivate * const d_ptr;
+};
+
+#endif							// CATEGORYSELECTWIDGET_H
diff --git a/kaddressbook/kaddressbookui.rc b/kaddressbook/kaddressbookui.rc
index 6f4a774..623b2b8 100644
--- a/kaddressbook/kaddressbookui.rc
+++ b/kaddressbook/kaddressbookui.rc
@@ -1,5 +1,5 @@
 <!DOCTYPE kpartgui>
-<kpartgui name="kaddressbook" version="18">
+<kpartgui name="kaddressbook" version="19">
 
   <MenuBar>
     <Menu name="file"><text>&File</text>
@@ -98,6 +98,7 @@
     <Action name="akonadi_contact_create"/>
     <Action name="akonadi_contact_group_create"/>
     <Action name="quick_search"/>
+    <Action name="category_filter"/>
   </ToolBar>
 
 </kpartgui>
diff --git a/kaddressbook/mainwidget.cpp b/kaddressbook/mainwidget.cpp
index c2555fe..8983649 100644
--- a/kaddressbook/mainwidget.cpp
+++ b/kaddressbook/mainwidget.cpp
@@ -29,7 +29,8 @@
 #include "xxportmanager.h"
 #include "utils.h"
 #include "kaddressbookadaptor.h"
-
+#include "categoryselectwidget.h"
+#include "categoryfilterproxymodel.h"
 
 #include "kaddressbookgrantlee/formatter/grantleecontactformatter.h"
 #include "kaddressbookgrantlee/formatter/grantleecontactgroupformatter.h"
@@ -154,6 +155,9 @@ MainWidget::MainWidget( KXMLGUIClient *guiClient, QWidget *parent )
    *                           mContactsFilterModel
    *                                   ^
    *                                   |
+   * mCategorySelectWidget --> mCategoryFilterModel
+   *                                   ^
+   *                                   |
    *                               mItemTree
    *                                   ^
    *                                   |
@@ -184,6 +188,8 @@ MainWidget::MainWidget( KXMLGUIClient *guiClient, QWidget *parent )
    *             selectionProxyModel:  Filters out all collections and items that are no children
    *                                   of the collections currently selected in selectionModel
    *                       mItemTree:  Filters out all collections
+   *           mCategorySelectWidget:  Selects a list of categories for filtering
+   *            mCategoryFilterModel:  Filters the contacts by the selected categories
    *            mContactsFilterModel:  Filters the contacts by the content of mQuickSearchWidget
    *                       mItemView:  Shows the items (contacts and contact groups) in a view
    *
@@ -226,8 +232,17 @@ MainWidget::MainWidget( KXMLGUIClient *guiClient, QWidget *parent )
   mItemTree->addMimeTypeExclusionFilter( Akonadi::Collection::mimeType() );
   mItemTree->setHeaderGroup( Akonadi::EntityTreeModel::ItemListHeaders );
 
+  mCategoryFilterModel = new CategoryFilterProxyModel( this );
+  mCategoryFilterModel->setSourceModel( mItemTree );
+  mCategoryFilterModel->setFilterCategories( mCategorySelectWidget->filterTags() );
+  mCategoryFilterModel->setFilterEnabled( true );
+
+  connect(mCategorySelectWidget, SIGNAL(filterChanged(const QList<Akonadi::Tag::Id> &)),
+          mCategoryFilterModel, SLOT(setFilterCategories(const QList<Akonadi::Tag::Id> &)));
+
   mContactsFilterModel = new Akonadi::ContactsFilterProxyModel( this );
-  mContactsFilterModel->setSourceModel( mItemTree );
+  mContactsFilterModel->setSourceModel( mCategoryFilterModel );
+
   connect( mQuickSearchWidget, SIGNAL(filterStringChanged(QString)),
            mContactsFilterModel, SLOT(setFilterString(QString)) );
   connect( mQuickSearchWidget, SIGNAL(filterStringChanged(QString)),
@@ -519,6 +534,9 @@ void MainWidget::setupGui()
   // the quick search widget which is embedded in the toolbar action
   mQuickSearchWidget = new QuickSearchWidget;
 
+  // the category filter widget which is embedded in the toolbar action
+  mCategorySelectWidget = new CategorySelectWidget;
+
   // setup the default actions
   Akonadi::ContactDefaultActions *actions = new Akonadi::ContactDefaultActions( this );
   actions->connectToView( mContactDetails );
@@ -560,6 +578,10 @@ void MainWidget::setupActions( KActionCollection *collection )
   action->setText( i18n( "Quick search" ) );
   action->setDefaultWidget( mQuickSearchWidget );
 
+  action = collection->addAction( QLatin1String("category_filter") );
+  action->setText( i18n( "Category filter" ) );
+  action->setDefaultWidget( mCategorySelectWidget );
+
   action = collection->addAction( QLatin1String("select_all") );
   action->setText( i18n( "Select All" ) );
   action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_A ) );
@@ -955,5 +977,3 @@ void MainWidget::slotCheckNewCalendar( const QModelIndex &parent, int begin, int
         }
     }
 }
-
-
diff --git a/kaddressbook/mainwidget.h b/kaddressbook/mainwidget.h
index 0075edf..69ccb6d 100644
--- a/kaddressbook/mainwidget.h
+++ b/kaddressbook/mainwidget.h
@@ -59,6 +59,8 @@ class QStackedWidget;
 class QuickSearchWidget;
 class XXPortManager;
 class QActionGroup;
+class CategorySelectWidget;
+class CategoryFilterProxyModel;
 
 class KADDRESSBOOK_EXPORT MainWidget : public QWidget
 {
@@ -121,10 +123,13 @@ private:
     Akonadi::EntityMimeTypeFilterModel *mItemTree;
     Akonadi::EntityMimeTypeFilterModel *mAllContactsModel;
     Akonadi::ContactsFilterProxyModel *mContactsFilterModel;
+    CategoryFilterProxyModel *mCategoryFilterModel;
 
     QuickSearchWidget *mQuickSearchWidget;
     Akonadi::EntityTreeView *mCollectionView;
     Akonadi::EntityTreeView *mItemView;
+    CategorySelectWidget *mCategorySelectWidget;
+
     QWidget *mDetailsPane;
     QStackedWidget *mDetailsViewStack;
     ContactSwitcher *mContactSwitcher;
diff --git a/kaddressbook/settings/kaddressbook.kcfg b/kaddressbook/settings/kaddressbook.kcfg
index f3a3e38..cc06279 100644
--- a/kaddressbook/settings/kaddressbook.kcfg
+++ b/kaddressbook/settings/kaddressbook.kcfg
@@ -55,7 +55,8 @@
       <label>Viewing mode</label>
       <tooltip>Viewing mode (number of columns)</tooltip>
       <whatsthis>Choose the layout for the main view</whatsthis>
-      </entry>
+    </entry>
+
   </group>
 
 </kcfg>


More information about the kde-doc-english mailing list