File indexing completed on 2024-05-19 05:14:36
0001 /* 0002 SPDX-FileCopyrightText: 2014 Jonathan Marten <jjm@keelhaul.me.uk> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "categoryselectwidget.h" 0008 0009 #include "kaddressbook_debug.h" 0010 #include <Akonadi/Monitor> 0011 #include <Akonadi/TagModel> 0012 #include <KLocalizedString> 0013 #include <QHBoxLayout> 0014 #include <QStandardItemModel> 0015 #include <QTimer> 0016 #include <QToolButton> 0017 0018 #include <Libkdepim/KCheckComboBox> 0019 #include <chrono> 0020 using namespace std::chrono_literals; 0021 using namespace Akonadi; 0022 0023 static const int FILTER_ROLE = Qt::UserRole + 1; 0024 0025 class CategorySelectWidgetPrivate : public QObject 0026 { 0027 Q_OBJECT 0028 Q_DECLARE_PUBLIC(CategorySelectWidget) 0029 0030 public: 0031 explicit CategorySelectWidgetPrivate(CategorySelectWidget *parent); 0032 0033 Akonadi::TagModel *tagModel = nullptr; 0034 int rowOffset = 0; 0035 QTimer *updateTimer = nullptr; 0036 KPIM::KCheckComboBox *checkCombo = nullptr; 0037 0038 void init(); 0039 [[nodiscard]] QStandardItemModel *itemModel() const; 0040 void selectAll(Qt::CheckState state) const; 0041 [[nodiscard]] QList<Akonadi::Tag> filterTags() const; 0042 0043 public Q_SLOTS: 0044 void slotSelectAll(); 0045 void slotSelectNone(); 0046 0047 void slotTagsInserted(const QModelIndex &parent, int start, int end); 0048 void slotTagsRemoved(const QModelIndex &parent, int start, int end); 0049 void slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); 0050 0051 void slotCheckedItemsChanged(); 0052 void slotCheckedItemsTimer(); 0053 0054 private: 0055 CategorySelectWidget *const q_ptr; 0056 }; 0057 0058 CategorySelectWidgetPrivate::CategorySelectWidgetPrivate(CategorySelectWidget *parent) 0059 : QObject() 0060 , q_ptr(parent) 0061 { 0062 } 0063 0064 void CategorySelectWidgetPrivate::init() 0065 { 0066 Q_Q(CategorySelectWidget); 0067 0068 auto hbox = new QHBoxLayout(q); 0069 hbox->setSpacing(0); 0070 hbox->setContentsMargins({}); 0071 0072 checkCombo = new KPIM::KCheckComboBox; 0073 checkCombo->setMinimumWidth(150); 0074 checkCombo->setSqueezeText(true); 0075 connect(checkCombo, &KPIM::KCheckComboBox::checkedItemsChanged, this, &CategorySelectWidgetPrivate::slotCheckedItemsChanged); 0076 hbox->addWidget(checkCombo); 0077 0078 auto monitor = new Monitor(this); 0079 monitor->setTypeMonitored(Monitor::Tags); 0080 tagModel = new Akonadi::TagModel(monitor, this); 0081 0082 connect(tagModel, &QAbstractItemModel::rowsInserted, this, &CategorySelectWidgetPrivate::slotTagsInserted); 0083 connect(tagModel, &QAbstractItemModel::rowsRemoved, this, &CategorySelectWidgetPrivate::slotTagsRemoved); 0084 connect(tagModel, &QAbstractItemModel::dataChanged, this, &CategorySelectWidgetPrivate::slotTagsChanged); 0085 0086 updateTimer = new QTimer(this); 0087 updateTimer->setSingleShot(true); 0088 updateTimer->setInterval(200ms); 0089 connect(updateTimer, &QTimer::timeout, this, &CategorySelectWidgetPrivate::slotCheckedItemsTimer); 0090 0091 auto but = new QToolButton(q); 0092 but->setAutoRaise(true); 0093 but->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); 0094 but->setToolTip(i18nc("@action:button", "Reset category filter")); 0095 connect(but, &QToolButton::clicked, this, &CategorySelectWidgetPrivate::slotSelectAll); 0096 hbox->addWidget(but); 0097 0098 but = new QToolButton(q); 0099 but->setAutoRaise(true); 0100 but->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); 0101 but->setToolTip(i18nc("@action:button", "Clear category filter")); 0102 connect(but, &QToolButton::clicked, this, &CategorySelectWidgetPrivate::slotSelectNone); 0103 hbox->addWidget(but); 0104 0105 auto item = new QStandardItem(i18n("(Untagged)")); 0106 item->setCheckState(Qt::Checked); 0107 item->setData(CategorySelectWidget::FilterUntagged, FILTER_ROLE); 0108 itemModel()->appendRow(item); 0109 0110 item = new QStandardItem(i18n("(Groups)")); 0111 item->setCheckState(Qt::Checked); 0112 item->setData(CategorySelectWidget::FilterGroups, FILTER_ROLE); 0113 itemModel()->appendRow(item); 0114 0115 checkCombo->setAlwaysShowDefaultText(false); 0116 checkCombo->setDefaultText(i18n("(All)")); 0117 0118 rowOffset = itemModel()->rowCount(); 0119 } 0120 0121 QStandardItemModel *CategorySelectWidgetPrivate::itemModel() const 0122 { 0123 auto m = qobject_cast<QStandardItemModel *>(checkCombo->model()); 0124 Q_ASSERT(m != nullptr); 0125 return m; 0126 } 0127 0128 void CategorySelectWidgetPrivate::slotTagsRemoved(const QModelIndex &parent, int start, int end) 0129 { 0130 itemModel()->removeRows(start + rowOffset, end + rowOffset, parent); 0131 } 0132 0133 void CategorySelectWidgetPrivate::slotTagsChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 0134 { 0135 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { 0136 QStandardItem *it = itemModel()->item(row + rowOffset); 0137 Q_ASSERT(it != nullptr); 0138 0139 QModelIndex idx = tagModel->index(row, 0); 0140 it->setText(tagModel->data(idx, TagModel::NameRole).toString()); 0141 it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>()); 0142 it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE); 0143 } 0144 } 0145 0146 void CategorySelectWidgetPrivate::slotTagsInserted(const QModelIndex &parent, int start, int end) 0147 { 0148 for (int row = start; row <= end; ++row) { 0149 QModelIndex idx = tagModel->index(row, 0, parent); 0150 #if 0 0151 qCDebug(KADDRESSBOOK_LOG) << "idx" << idx << "=" << tagModel->data(idx, Qt::DisplayRole).toString() 0152 << "name" << tagModel->data(idx, TagModel::NameRole).toString() 0153 << "tag" << tagModel->data(idx, TagModel::TagRole) 0154 << "id" << tagModel->data(idx, TagModel::IdRole).toInt(); 0155 #endif 0156 auto it = new QStandardItem(tagModel->data(idx, TagModel::NameRole).toString()); 0157 it->setIcon(tagModel->data(idx, Qt::DecorationRole).value<QIcon>()); 0158 it->setData(tagModel->data(idx, TagModel::IdRole), FILTER_ROLE); 0159 it->setCheckState(Qt::Checked); 0160 0161 // If a tag with a parent arrives from the model, we know that its parent 0162 // must already have arrived. So there is no need for a list of pending 0163 // tags, as is required in Akonadi::TagModel. 0164 // 0165 // FIXME: not tested (no way to create hierarchical tags at present) 0166 if (parent != QModelIndex()) { 0167 const auto parentId = tagModel->data(idx, TagModel::IdRole).value<Tag::Id>(); 0168 QModelIndexList matchList = itemModel()->match(itemModel()->index(0, 0), FILTER_ROLE, parentId, 1, Qt::MatchExactly | Qt::MatchRecursive); 0169 if (matchList.count() == 1) { // found the parent tag 0170 QModelIndex parentIndex = matchList.at(0); 0171 itemModel()->itemFromIndex(parentIndex)->appendRow(it); 0172 } else { 0173 qCWarning(KADDRESSBOOK_LOG) << "Cannot find parent with ID" << parentId; 0174 itemModel()->insertRow(row + rowOffset, it); 0175 } 0176 } else { 0177 itemModel()->insertRow(row + rowOffset, it); 0178 } 0179 } 0180 } 0181 0182 void CategorySelectWidgetPrivate::selectAll(Qt::CheckState state) const 0183 { 0184 for (int row = 0; row < itemModel()->rowCount(); ++row) { 0185 QStandardItem *it = itemModel()->item(row); 0186 it->setCheckState(state); 0187 } 0188 } 0189 0190 void CategorySelectWidgetPrivate::slotSelectAll() 0191 { 0192 selectAll(Qt::Checked); 0193 } 0194 0195 void CategorySelectWidgetPrivate::slotSelectNone() 0196 { 0197 selectAll(Qt::Unchecked); 0198 } 0199 0200 void CategorySelectWidgetPrivate::slotCheckedItemsChanged() 0201 { 0202 updateTimer->start(); 0203 } 0204 0205 void CategorySelectWidgetPrivate::slotCheckedItemsTimer() 0206 { 0207 Q_Q(CategorySelectWidget); 0208 0209 bool allOn = true; 0210 for (int row = 0; row < itemModel()->rowCount(); ++row) { 0211 const QStandardItem *it = itemModel()->item(row); 0212 Qt::CheckState rowState = static_cast<Qt::CheckState>(it->data(Qt::CheckStateRole).toInt()); 0213 if (rowState != Qt::Checked) { 0214 allOn = false; 0215 break; 0216 } 0217 } 0218 0219 if (allOn) { 0220 checkCombo->setAlwaysShowDefaultText(true); 0221 checkCombo->setDefaultText(i18n("(All)")); 0222 } else { 0223 checkCombo->setAlwaysShowDefaultText(false); 0224 checkCombo->setDefaultText(i18n("(None)")); 0225 } 0226 0227 const QStringList checkedList = checkCombo->checkedItems(); 0228 if (!checkedList.isEmpty()) { 0229 checkCombo->setToolTip(i18n("<qt>Category filter: %1", checkedList.join(i18n(", ")))); 0230 } else { 0231 checkCombo->setToolTip(QString()); 0232 } 0233 0234 Q_EMIT q->filterChanged(filterTags()); 0235 } 0236 0237 QList<Akonadi::Tag> CategorySelectWidgetPrivate::filterTags() const 0238 { 0239 QList<Tag> filter; 0240 bool allOn = true; 0241 for (int row = 0; row < itemModel()->rowCount(); ++row) { 0242 const QStandardItem *it = itemModel()->item(row); 0243 Q_ASSERT(it != nullptr); 0244 if (it->checkState() == Qt::Checked) { 0245 Tag tag(it->data(FILTER_ROLE).toInt()); 0246 tag.setName(it->text()); 0247 if (tag.id() != 0) { 0248 filter.append(tag); 0249 } 0250 } else { 0251 allOn = false; 0252 } 0253 } 0254 0255 if (allOn) { 0256 filter.clear(); 0257 filter.append(Tag(CategorySelectWidget::FilterAll)); 0258 } 0259 0260 // qCDebug(KADDRESSBOOK_LOG) << "filter" << filter; 0261 return filter; 0262 } 0263 0264 CategorySelectWidget::CategorySelectWidget(QWidget *parent) 0265 : QWidget(parent) 0266 , d_ptr(new CategorySelectWidgetPrivate(this)) 0267 { 0268 Q_D(CategorySelectWidget); 0269 d->init(); 0270 } 0271 0272 CategorySelectWidget::~CategorySelectWidget() = default; 0273 0274 QList<Akonadi::Tag> CategorySelectWidget::filterTags() const 0275 { 0276 Q_D(const CategorySelectWidget); 0277 return d->filterTags(); 0278 } 0279 0280 #include "categoryselectwidget.moc" 0281 0282 #include "moc_categoryselectwidget.cpp"