Akonadi

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

KDE's Doxygen guidelines are available online.