Akonadi

tagselectioncombobox.cpp
1/*
2 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
3 SPDX-FileCopyrightText: 2020 Daniel Vrátil <dvratil@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "tagselectioncombobox.h"
9
10#include "monitor.h"
11#include "tagmodel.h"
12
13#include <KCheckableProxyModel>
14#include <QAbstractItemView>
15#include <QEvent>
16#include <QItemSelectionModel>
17#include <QKeyEvent>
18#include <QLineEdit>
19#include <QLocale>
20
21#include <KLocalizedString>
22
23#include <algorithm>
24#include <variant>
25
26using namespace Akonadi;
27
28namespace
29{
30template<typename List>
31List tagsFromSelection(const QItemSelection &selection, int role)
32{
33 List tags;
34 for (int i = 0; i < selection.size(); ++i) {
35 const auto indexes = selection.at(i).indexes();
36 std::transform(indexes.cbegin(), indexes.cend(), std::back_inserter(tags), [role](const auto &idx) {
37 return idx.model()->data(idx, role).template value<typename List::value_type>();
38 });
39 }
40 return tags;
41}
42
43QString getEditText(const QItemSelection &selection)
44{
45 const auto tags = tagsFromSelection<Tag::List>(selection, TagModel::TagRole);
46 QStringList names;
47 names.reserve(tags.size());
48 std::transform(tags.cbegin(), tags.cend(), std::back_inserter(names), std::bind(&Tag::name, std::placeholders::_1));
49 return QLocale{}.createSeparatedList(names);
50}
51
52} // namespace
53
54class Akonadi::TagSelectionComboBoxPrivate
55{
56public:
57 explicit TagSelectionComboBoxPrivate(TagSelectionComboBox *parent)
58 : q(parent)
59 {
60 }
61
62 enum LoopControl {
63 Break,
64 Continue,
65 };
66
67 template<typename Selection, typename Comp>
68 void setSelection(const Selection &entries, Comp &&cmp)
69 {
70 if (!mModelReady) {
71 mPendingSelection = entries;
72 return;
73 }
74
75 const auto forEachIndex = [this, entries, cmp](auto &&func) {
76 for (int i = 0, cnt = tagModel->rowCount(); i < cnt; ++i) {
77 const auto index = tagModel->index(i, 0);
78 const auto tag = tagModel->data(index, TagModel::TagRole).value<Tag>();
79 if (std::any_of(entries.cbegin(), entries.cend(), std::bind(cmp, tag, std::placeholders::_1))) {
80 if (func(index) == Break) {
81 break;
82 }
83 }
84 }
85 };
86
87 if (mCheckable) {
88 QItemSelection selection;
89 forEachIndex([&selection](const QModelIndex &index) {
90 selection.push_back(QItemSelectionRange{index});
91 return Continue;
92 });
93 selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
94 } else {
95 forEachIndex([this](const QModelIndex &index) {
96 q->setCurrentIndex(index.row());
97 return Break;
98 });
99 }
100 }
101
102 void toggleItem(const QModelIndex &tagModelIndex) const
103 {
104 selectionModel->select(tagModelIndex, QItemSelectionModel::Toggle);
105 }
106
107 void setItemChecked(const QModelIndex &tagModelIndex, Qt::CheckState state) const
108 {
109 selectionModel->select(tagModelIndex, state == Qt::Checked ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
110 }
111
112 void setCheckable(bool checkable)
113 {
114 if (checkable) {
115 selectionModel = std::make_unique<QItemSelectionModel>(tagModel.get(), q);
116 checkableProxy = std::make_unique<KCheckableProxyModel>(q);
117 checkableProxy->setSourceModel(tagModel.get());
118 checkableProxy->setSelectionModel(selectionModel.get());
119
120 tagModel->setParent(nullptr);
121 q->setModel(checkableProxy.get());
122 tagModel->setParent(q);
123
124 q->setEditable(true);
125 q->lineEdit()->setReadOnly(true);
126 q->lineEdit()->setPlaceholderText(i18nc("@label Placeholder text in tag selection combobox", "Select tags..."));
128
130 q->view()->installEventFilter(q);
131 q->view()->viewport()->installEventFilter(q);
132
133 q->connect(selectionModel.get(), &QItemSelectionModel::selectionChanged, q, [this]() {
134 const auto selection = selectionModel->selection();
135 q->setEditText(getEditText(selection));
136 Q_EMIT q->selectionChanged(tagsFromSelection<Tag::List>(selection, TagModel::TagRole));
137 });
138 q->connect(q, &QComboBox::activated, selectionModel.get(), [this](int i) {
139 if (q->view()->isVisible()) {
140 const auto index = tagModel->index(i, 0);
141 toggleItem(index);
142 }
143 });
144 } else {
145 // QComboBox automatically deletes models that it is a parent of
146 // which breaks our stuff
147 tagModel->setParent(nullptr);
148 q->setModel(tagModel.get());
149 tagModel->setParent(q);
150
151 if (q->lineEdit()) {
153 }
154 if (q->view()) {
155 q->view()->removeEventFilter(q);
156 q->view()->viewport()->removeEventFilter(q);
157 }
158
159 q->setEditable(false);
160
161 selectionModel.reset();
162 checkableProxy.reset();
163 }
164 }
165
166 std::unique_ptr<QItemSelectionModel> selectionModel;
167 std::unique_ptr<TagModel> tagModel;
168 std::unique_ptr<KCheckableProxyModel> checkableProxy;
169
170 bool mCheckable = false;
171 bool mAllowHide = true;
172 bool mModelReady = false;
173
174 std::variant<std::monostate, Tag::List, QStringList> mPendingSelection;
175
176private:
177 TagSelectionComboBox *const q;
178};
179
180TagSelectionComboBox::TagSelectionComboBox(QWidget *parent)
181 : QComboBox(parent)
182 , d(new TagSelectionComboBoxPrivate(this))
183{
184 auto monitor = new Monitor(this);
185 monitor->setObjectName(QLatin1StringView("TagSelectionComboBoxMonitor"));
186 monitor->setTypeMonitored(Monitor::Tags);
187
188 d->tagModel = std::make_unique<TagModel>(monitor, this);
189 connect(d->tagModel.get(), &TagModel::populated, this, [this]() {
190 d->mModelReady = true;
191 if (auto list = std::get_if<Tag::List>(&d->mPendingSelection)) {
192 setSelection(*list);
193 } else if (auto slist = std::get_if<QStringList>(&d->mPendingSelection)) {
194 setSelection(*slist);
195 }
196 d->mPendingSelection = std::monostate{};
197 });
198
199 d->setCheckable(d->mCheckable);
200}
201
202TagSelectionComboBox::~TagSelectionComboBox() = default;
203
204void TagSelectionComboBox::setCheckable(bool checkable)
205{
206 if (d->mCheckable != checkable) {
207 d->mCheckable = checkable;
208 d->setCheckable(d->mCheckable);
209 }
210}
211
212bool TagSelectionComboBox::checkable() const
213{
214 return d->mCheckable;
215}
216
217void TagSelectionComboBox::setSelection(const Tag::List &tags)
218{
219 d->setSelection(tags, [](const Tag &a, const Tag &b) {
220 return a.name() == b.name();
221 });
222}
223
224void TagSelectionComboBox::setSelection(const QStringList &tagNames)
225{
226 d->setSelection(tagNames, [](const Tag &a, const QString &b) {
227 return a.name() == b;
228 });
229}
230
231Tag::List TagSelectionComboBox::selection() const
232{
233 if (!d->selectionModel) {
234 return {currentData(TagModel::TagRole).value<Tag>()};
235 }
236 return tagsFromSelection<Tag::List>(d->selectionModel->selection(), TagModel::TagRole);
237}
238
239QStringList TagSelectionComboBox::selectionNames() const
240{
241 if (!d->selectionModel) {
242 return {currentText()};
243 }
244 return tagsFromSelection<QStringList>(d->selectionModel->selection(), TagModel::NameRole);
245}
246
247void TagSelectionComboBox::hidePopup()
248{
249 if (d->mAllowHide) {
251 }
252 d->mAllowHide = true;
253}
254
255void TagSelectionComboBox::keyPressEvent(QKeyEvent *event)
256{
257 switch (event->key()) {
258 case Qt::Key_Up:
259 case Qt::Key_Down:
260 showPopup();
261 event->accept();
262 break;
263 case Qt::Key_Return:
264 case Qt::Key_Enter:
265 case Qt::Key_Escape:
266 hidePopup();
267 event->accept();
268 break;
269 default:
270 break;
271 }
272}
273
274bool TagSelectionComboBox::eventFilter(QObject *receiver, QEvent *event)
275{
276 switch (event->type()) {
277 case QEvent::KeyPress:
280 switch (static_cast<QKeyEvent *>(event)->key()) {
281 case Qt::Key_Return:
282 case Qt::Key_Enter:
283 case Qt::Key_Escape:
284 hidePopup();
285 return true;
286 }
287 break;
291 d->mAllowHide = false;
292 if (receiver == lineEdit()) {
293 showPopup();
294 return true;
295 }
296 break;
297 default:
298 break;
299 }
300 return QComboBox::eventFilter(receiver, event);
301}
302
303#include "moc_tagselectioncombobox.cpp"
Monitors an item or collection for changes.
Definition monitor.h:72
The TagSelectionCombo class.
An Akonadi Tag.
Definition tag.h:26
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
QWidget * viewport() const const
void activated(int index)
void setCurrentIndex(int index)
void setEditable(bool editable)
virtual bool event(QEvent *event) override
virtual void hidePopup()
QLineEdit * lineEdit() const const
virtual void setModel(QAbstractItemModel *model)
virtual void showPopup()
QAbstractItemView * view() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void setAlignment(Qt::Alignment flag)
void setPlaceholderText(const QString &)
void setReadOnly(bool)
const_reference at(qsizetype i) const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T value(qsizetype i) const const
QString createSeparatedList(const QStringList &list) const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
T qobject_cast(QObject *object)
void removeEventFilter(QObject *obj)
AlignLeft
CheckState
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.