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>
31 List 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 
43 QString 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 
54 class Akonadi::TagSelectionComboBoxPrivate
55 {
56 public:
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..."));
127  q->lineEdit()->setAlignment(Qt::AlignLeft);
128 
129  q->lineEdit()->installEventFilter(q);
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, qOverload<int>(&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()) {
152  q->lineEdit()->removeEventFilter(q);
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 
176 private:
177  TagSelectionComboBox *const q;
178 };
179 
180 TagSelectionComboBox::TagSelectionComboBox(QWidget *parent)
181  : QComboBox(parent)
182  , d(new TagSelectionComboBoxPrivate(this))
183 {
184  auto monitor = new Monitor(this);
185  monitor->setObjectName(QStringLiteral("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 
202 TagSelectionComboBox::~TagSelectionComboBox() = default;
203 
204 void TagSelectionComboBox::setCheckable(bool checkable)
205 {
206  if (d->mCheckable != checkable) {
207  d->mCheckable = checkable;
208  d->setCheckable(d->mCheckable);
209  }
210 }
211 
212 bool TagSelectionComboBox::checkable() const
213 {
214  return d->mCheckable;
215 }
216 
217 void 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 
224 void TagSelectionComboBox::setSelection(const QStringList &tagNames)
225 {
226  d->setSelection(tagNames, [](const Tag &a, const QString &b) {
227  return a.name() == b;
228  });
229 }
230 
231 Tag::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 
239 QStringList TagSelectionComboBox::selectionNames() const
240 {
241  if (!d->selectionModel) {
242  return {currentText()};
243  }
244  return tagsFromSelection<QStringList>(d->selectionModel->selection(), TagModel::NameRole);
245 }
246 
247 void TagSelectionComboBox::hidePopup()
248 {
249  if (d->mAllowHide) {
251  }
252  d->mAllowHide = true;
253 }
254 
255 void 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 
274 bool TagSelectionComboBox::eventFilter(QObject *receiver, QEvent *event)
275 {
276  switch (event->type()) {
277  case QEvent::KeyPress:
278  case QEvent::KeyRelease:
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 }
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 Tue Dec 6 2022 03:53:34 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.