Prepare future 5.1.1
[kdepim.git] / kaddressbook / categoryselectwidget.cpp
blob8a57f0191723977aaaa41985b6c298b3a2766b48
1 /*
2 Copyright (c) 2014 Jonathan Marten <jjm@keelhaul.me.uk>
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
20 #include "categoryselectwidget.h"
22 #include "kaddressbook_debug.h"
23 #include <KLocalizedString>
24 #include <qtoolbutton.h>
25 #include <qlayout.h>
26 #include <qstandarditemmodel.h>
27 #include <qtimer.h>
29 #include <AkonadiCore/monitor.h>
30 #include <AkonadiCore/tagmodel.h>
32 #include <Libkdepim/KCheckComboBox>
34 using namespace Akonadi;
36 static const int FILTER_ROLE = Qt::UserRole + 1;
38 class CategorySelectWidgetPrivate : public QObject
40 Q_OBJECT
41 Q_DECLARE_PUBLIC(CategorySelectWidget)
43 public:
44 explicit CategorySelectWidgetPrivate(CategorySelectWidget *parent);
46 Akonadi::TagModel *tagModel;
47 int rowOffset;
48 QTimer *updateTimer;
49 KPIM::KCheckComboBox *checkCombo;
51 void init();
52 QStandardItemModel *itemModel() const;
53 void selectAll(Qt::CheckState state) const;
54 QList<Akonadi::Tag::Id> filterTags() const;
56 public Q_SLOTS:
57 void slotSelectAll();
58 void slotSelectNone();
60 void slotTagsInserted(const QModelIndex &parent, int start, int end);
61 void slotTagsRemoved(const QModelIndex &parent, int start, int end);
62 void slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
64 void slotCheckedItemsChanged();
65 void slotCheckedItemsTimer();
67 private:
68 CategorySelectWidget *q_ptr;
71 CategorySelectWidgetPrivate::CategorySelectWidgetPrivate(CategorySelectWidget *parent)
72 : QObject(),
73 tagModel(Q_NULLPTR),
74 rowOffset(0),
75 updateTimer(Q_NULLPTR),
76 checkCombo(Q_NULLPTR),
77 q_ptr(parent)
81 void CategorySelectWidgetPrivate::init()
83 Q_Q(CategorySelectWidget);
85 QHBoxLayout *hbox = new QHBoxLayout(q);
86 hbox->setSpacing(0);
87 hbox->setMargin(0);
89 checkCombo = new KPIM::KCheckComboBox;
90 checkCombo->setMinimumWidth(150);
91 checkCombo->setSqueezeText(true);
92 connect(checkCombo, &KPIM::KCheckComboBox::checkedItemsChanged,
93 this, &CategorySelectWidgetPrivate::slotCheckedItemsChanged);
94 hbox->addWidget(checkCombo);
96 Monitor *monitor = new Monitor(this);
97 monitor->setTypeMonitored(Monitor::Tags);
98 tagModel = new Akonadi::TagModel(monitor, this);
100 connect(tagModel, &QAbstractItemModel::rowsInserted,
101 this, &CategorySelectWidgetPrivate::slotTagsInserted);
102 connect(tagModel, &QAbstractItemModel::rowsRemoved,
103 this, &CategorySelectWidgetPrivate::slotTagsRemoved);
104 connect(tagModel, &QAbstractItemModel::dataChanged,
105 this, &CategorySelectWidgetPrivate::slotTagsChanged);
107 updateTimer = new QTimer(this);
108 updateTimer->setSingleShot(true);
109 updateTimer->setInterval(200);
110 connect(updateTimer, &QTimer::timeout, this, &CategorySelectWidgetPrivate::slotCheckedItemsTimer);
112 QToolButton *but = new QToolButton(q);
113 but ->setAutoRaise(true);
114 but->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
115 but->setToolTip(i18nc("@action:button", "Reset category filter"));
116 connect(but, &QToolButton::clicked, this, &CategorySelectWidgetPrivate::slotSelectAll);
117 hbox->addWidget(but);
119 but = new QToolButton(q);
120 but->setAutoRaise(true);
121 but->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
122 but->setToolTip(i18nc("@action:button", "Clear category filter"));
123 connect(but, &QToolButton::clicked, this, &CategorySelectWidgetPrivate::slotSelectNone);
124 hbox->addWidget(but);
126 QStandardItem *item = new QStandardItem(i18n("(Untagged)"));
127 item->setCheckState(Qt::Checked);
128 item->setData(CategorySelectWidget::FilterUntagged, FILTER_ROLE);
129 itemModel()->appendRow(item);
131 item = new QStandardItem(i18n("(Groups)"));
132 item->setCheckState(Qt::Checked);
133 item->setData(CategorySelectWidget::FilterGroups, FILTER_ROLE);
134 itemModel()->appendRow(item);
136 rowOffset = itemModel()->rowCount();
139 QStandardItemModel *CategorySelectWidgetPrivate::itemModel() const
141 QStandardItemModel *m = qobject_cast<QStandardItemModel *>(checkCombo->model());
142 Q_ASSERT(m != NULL);
143 return m;
146 void CategorySelectWidgetPrivate::slotTagsRemoved(const QModelIndex &parent, int start, int end)
148 itemModel()->removeRows(start + rowOffset, end + rowOffset, parent);
151 void CategorySelectWidgetPrivate::slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
153 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
154 QStandardItem *it = itemModel()->item(row + rowOffset);
155 Q_ASSERT(it != NULL);
157 QModelIndex idx = tagModel->index(row, 0);
158 it->setText(tagModel->data(idx, TagModel::NameRole).toString());
159 it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>());
160 it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE);
164 void CategorySelectWidgetPrivate::slotTagsInserted(const QModelIndex &parent, int start, int end)
166 for (int row = start; row <= end; ++row) {
167 QModelIndex idx = tagModel->index(row, 0, parent);
168 #if 0
169 qCDebug(KADDRESSBOOK_LOG) << "idx" << idx << "=" << tagModel->data(idx, Qt::DisplayRole).toString()
170 << "name" << tagModel->data(idx, TagModel::NameRole).toString()
171 << "tag" << tagModel->data(idx, TagModel::TagRole)
172 << "id" << tagModel->data(idx, TagModel::IdRole).toInt();
173 #endif
174 QStandardItem *it = new QStandardItem(tagModel->data(idx, TagModel::NameRole).toString());
175 it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>());
176 it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE);
177 it->setCheckState(Qt::Checked);
179 // If a tag with a parent arrives from the model, we know that its parent
180 // must already have arrived. So there is no need for a list of pending
181 // tags, as is required in Akonadi::TagModel.
183 // FIXME: not tested (no way to create hierarchial tags at present)
184 if (parent != QModelIndex()) {
185 const Tag::Id parentId = tagModel->data(idx, TagModel::IdRole).value<Tag::Id>();
186 QModelIndexList matchList = itemModel()->match(itemModel()->index(0, 0), FILTER_ROLE,
187 parentId, 1,
188 Qt::MatchExactly | Qt::MatchRecursive);
189 if (matchList.count() == 1) { // found the parent tag
190 QModelIndex parentIndex = matchList.at(0);
191 itemModel()->itemFromIndex(parentIndex)->appendRow(it);
192 } else {
193 qCWarning(KADDRESSBOOK_LOG) << "Cannot find parent with ID" << parentId;
194 itemModel()->insertRow(row + rowOffset, it);
196 } else {
197 itemModel()->insertRow(row + rowOffset, it);
202 void CategorySelectWidgetPrivate::selectAll(Qt::CheckState state) const
204 for (int row = 0; row < itemModel()->rowCount(); ++row) {
205 QStandardItem *it = itemModel()->item(row);
206 it->setCheckState(state);
210 void CategorySelectWidgetPrivate::slotSelectAll()
212 selectAll(Qt::Checked);
215 void CategorySelectWidgetPrivate::slotSelectNone()
217 selectAll(Qt::Unchecked);
220 void CategorySelectWidgetPrivate::slotCheckedItemsChanged()
222 updateTimer->start();
225 void CategorySelectWidgetPrivate::slotCheckedItemsTimer()
227 Q_Q(CategorySelectWidget);
229 bool allOn = true;
230 for (int row = 0; row < itemModel()->rowCount(); ++row) {
231 const QStandardItem *it = itemModel()->item(row);
232 Qt::CheckState rowState = static_cast<Qt::CheckState>(it->data(Qt::CheckStateRole).toInt());
233 if (rowState != Qt::Checked) {
234 allOn = false;
235 break;
239 if (allOn) {
240 checkCombo->setAlwaysShowDefaultText(true);
241 checkCombo->setDefaultText(i18n("(All)"));
242 } else {
243 checkCombo->setAlwaysShowDefaultText(false);
244 checkCombo->setDefaultText(i18n("(None)"));
247 const QStringList checkedList = checkCombo->checkedItems();
248 if (!checkedList.isEmpty()) {
249 checkCombo->setToolTip(i18n("<qt>Category filter: %1", checkedList.join(i18n(", "))));
250 } else {
251 checkCombo->setToolTip(QString());
254 Q_EMIT q->filterChanged(filterTags());
257 QList<Akonadi::Tag::Id> CategorySelectWidgetPrivate::filterTags() const
259 QList<Tag::Id> filter;
260 bool allOn = true;
261 for (int row = 0; row < itemModel()->rowCount(); ++row) {
262 const QStandardItem *it = itemModel()->item(row);
263 Q_ASSERT(it != NULL);
264 if (it->checkState() == Qt::Checked) {
265 Tag::Id id = it->data(FILTER_ROLE).toInt();
266 if (id != 0) {
267 filter.append(id);
269 } else {
270 allOn = false;
274 if (allOn) {
275 filter.clear();
276 filter.append(CategorySelectWidget::FilterAll);
279 //qCDebug(KADDRESSBOOK_LOG) << "filter" << filter;
280 return filter;
283 CategorySelectWidget::CategorySelectWidget(QWidget *parent)
284 : QWidget(parent),
285 d_ptr(new CategorySelectWidgetPrivate(this))
287 Q_D(CategorySelectWidget);
288 d->init();
291 CategorySelectWidget::~CategorySelectWidget()
293 delete d_ptr;
296 QList<Akonadi::Tag::Id> CategorySelectWidget::filterTags() const
298 Q_D(const CategorySelectWidget);
299 return d->filterTags();
302 #include "categoryselectwidget.moc"