File indexing completed on 2025-01-05 04:47:11
0001 /* 0002 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0003 SPDX-FileCopyrightText: 2020 Daniel Vrátil <dvratil@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "tagselectioncombobox.h" 0009 0010 #include "monitor.h" 0011 #include "tagmodel.h" 0012 0013 #include <KCheckableProxyModel> 0014 #include <QAbstractItemView> 0015 #include <QEvent> 0016 #include <QItemSelectionModel> 0017 #include <QKeyEvent> 0018 #include <QLineEdit> 0019 #include <QLocale> 0020 0021 #include <KLocalizedString> 0022 0023 #include <algorithm> 0024 #include <variant> 0025 0026 using namespace Akonadi; 0027 0028 namespace 0029 { 0030 template<typename List> 0031 List tagsFromSelection(const QItemSelection &selection, int role) 0032 { 0033 List tags; 0034 for (int i = 0; i < selection.size(); ++i) { 0035 const auto indexes = selection.at(i).indexes(); 0036 std::transform(indexes.cbegin(), indexes.cend(), std::back_inserter(tags), [role](const auto &idx) { 0037 return idx.model()->data(idx, role).template value<typename List::value_type>(); 0038 }); 0039 } 0040 return tags; 0041 } 0042 0043 QString getEditText(const QItemSelection &selection) 0044 { 0045 const auto tags = tagsFromSelection<Tag::List>(selection, TagModel::TagRole); 0046 QStringList names; 0047 names.reserve(tags.size()); 0048 std::transform(tags.cbegin(), tags.cend(), std::back_inserter(names), std::bind(&Tag::name, std::placeholders::_1)); 0049 return QLocale{}.createSeparatedList(names); 0050 } 0051 0052 } // namespace 0053 0054 class Akonadi::TagSelectionComboBoxPrivate 0055 { 0056 public: 0057 explicit TagSelectionComboBoxPrivate(TagSelectionComboBox *parent) 0058 : q(parent) 0059 { 0060 } 0061 0062 enum LoopControl { 0063 Break, 0064 Continue, 0065 }; 0066 0067 template<typename Selection, typename Comp> 0068 void setSelection(const Selection &entries, Comp &&cmp) 0069 { 0070 if (!mModelReady) { 0071 mPendingSelection = entries; 0072 return; 0073 } 0074 0075 const auto forEachIndex = [this, entries, cmp](auto &&func) { 0076 for (int i = 0, cnt = tagModel->rowCount(); i < cnt; ++i) { 0077 const auto index = tagModel->index(i, 0); 0078 const auto tag = tagModel->data(index, TagModel::TagRole).value<Tag>(); 0079 if (std::any_of(entries.cbegin(), entries.cend(), std::bind(cmp, tag, std::placeholders::_1))) { 0080 if (func(index) == Break) { 0081 break; 0082 } 0083 } 0084 } 0085 }; 0086 0087 if (mCheckable) { 0088 QItemSelection selection; 0089 forEachIndex([&selection](const QModelIndex &index) { 0090 selection.push_back(QItemSelectionRange{index}); 0091 return Continue; 0092 }); 0093 selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); 0094 } else { 0095 forEachIndex([this](const QModelIndex &index) { 0096 q->setCurrentIndex(index.row()); 0097 return Break; 0098 }); 0099 } 0100 } 0101 0102 void toggleItem(const QModelIndex &tagModelIndex) const 0103 { 0104 selectionModel->select(tagModelIndex, QItemSelectionModel::Toggle); 0105 } 0106 0107 void setItemChecked(const QModelIndex &tagModelIndex, Qt::CheckState state) const 0108 { 0109 selectionModel->select(tagModelIndex, state == Qt::Checked ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); 0110 } 0111 0112 void setCheckable(bool checkable) 0113 { 0114 if (checkable) { 0115 selectionModel = std::make_unique<QItemSelectionModel>(tagModel.get(), q); 0116 checkableProxy = std::make_unique<KCheckableProxyModel>(q); 0117 checkableProxy->setSourceModel(tagModel.get()); 0118 checkableProxy->setSelectionModel(selectionModel.get()); 0119 0120 tagModel->setParent(nullptr); 0121 q->setModel(checkableProxy.get()); 0122 tagModel->setParent(q); 0123 0124 q->setEditable(true); 0125 q->lineEdit()->setReadOnly(true); 0126 q->lineEdit()->setPlaceholderText(i18nc("@label Placeholder text in tag selection combobox", "Select tags...")); 0127 q->lineEdit()->setAlignment(Qt::AlignLeft); 0128 0129 q->lineEdit()->installEventFilter(q); 0130 q->view()->installEventFilter(q); 0131 q->view()->viewport()->installEventFilter(q); 0132 0133 q->connect(selectionModel.get(), &QItemSelectionModel::selectionChanged, q, [this]() { 0134 const auto selection = selectionModel->selection(); 0135 q->setEditText(getEditText(selection)); 0136 Q_EMIT q->selectionChanged(tagsFromSelection<Tag::List>(selection, TagModel::TagRole)); 0137 }); 0138 q->connect(q, &QComboBox::activated, selectionModel.get(), [this](int i) { 0139 if (q->view()->isVisible()) { 0140 const auto index = tagModel->index(i, 0); 0141 toggleItem(index); 0142 } 0143 }); 0144 } else { 0145 // QComboBox automatically deletes models that it is a parent of 0146 // which breaks our stuff 0147 tagModel->setParent(nullptr); 0148 q->setModel(tagModel.get()); 0149 tagModel->setParent(q); 0150 0151 if (q->lineEdit()) { 0152 q->lineEdit()->removeEventFilter(q); 0153 } 0154 if (q->view()) { 0155 q->view()->removeEventFilter(q); 0156 q->view()->viewport()->removeEventFilter(q); 0157 } 0158 0159 q->setEditable(false); 0160 0161 selectionModel.reset(); 0162 checkableProxy.reset(); 0163 } 0164 } 0165 0166 std::unique_ptr<QItemSelectionModel> selectionModel; 0167 std::unique_ptr<TagModel> tagModel; 0168 std::unique_ptr<KCheckableProxyModel> checkableProxy; 0169 0170 bool mCheckable = false; 0171 bool mAllowHide = true; 0172 bool mModelReady = false; 0173 0174 std::variant<std::monostate, Tag::List, QStringList> mPendingSelection; 0175 0176 private: 0177 TagSelectionComboBox *const q; 0178 }; 0179 0180 TagSelectionComboBox::TagSelectionComboBox(QWidget *parent) 0181 : QComboBox(parent) 0182 , d(new TagSelectionComboBoxPrivate(this)) 0183 { 0184 auto monitor = new Monitor(this); 0185 monitor->setObjectName(QLatin1StringView("TagSelectionComboBoxMonitor")); 0186 monitor->setTypeMonitored(Monitor::Tags); 0187 0188 d->tagModel = std::make_unique<TagModel>(monitor, this); 0189 connect(d->tagModel.get(), &TagModel::populated, this, [this]() { 0190 d->mModelReady = true; 0191 if (auto list = std::get_if<Tag::List>(&d->mPendingSelection)) { 0192 setSelection(*list); 0193 } else if (auto slist = std::get_if<QStringList>(&d->mPendingSelection)) { 0194 setSelection(*slist); 0195 } 0196 d->mPendingSelection = std::monostate{}; 0197 }); 0198 0199 d->setCheckable(d->mCheckable); 0200 } 0201 0202 TagSelectionComboBox::~TagSelectionComboBox() = default; 0203 0204 void TagSelectionComboBox::setCheckable(bool checkable) 0205 { 0206 if (d->mCheckable != checkable) { 0207 d->mCheckable = checkable; 0208 d->setCheckable(d->mCheckable); 0209 } 0210 } 0211 0212 bool TagSelectionComboBox::checkable() const 0213 { 0214 return d->mCheckable; 0215 } 0216 0217 void TagSelectionComboBox::setSelection(const Tag::List &tags) 0218 { 0219 d->setSelection(tags, [](const Tag &a, const Tag &b) { 0220 return a.name() == b.name(); 0221 }); 0222 } 0223 0224 void TagSelectionComboBox::setSelection(const QStringList &tagNames) 0225 { 0226 d->setSelection(tagNames, [](const Tag &a, const QString &b) { 0227 return a.name() == b; 0228 }); 0229 } 0230 0231 Tag::List TagSelectionComboBox::selection() const 0232 { 0233 if (!d->selectionModel) { 0234 return {currentData(TagModel::TagRole).value<Tag>()}; 0235 } 0236 return tagsFromSelection<Tag::List>(d->selectionModel->selection(), TagModel::TagRole); 0237 } 0238 0239 QStringList TagSelectionComboBox::selectionNames() const 0240 { 0241 if (!d->selectionModel) { 0242 return {currentText()}; 0243 } 0244 return tagsFromSelection<QStringList>(d->selectionModel->selection(), TagModel::NameRole); 0245 } 0246 0247 void TagSelectionComboBox::hidePopup() 0248 { 0249 if (d->mAllowHide) { 0250 QComboBox::hidePopup(); 0251 } 0252 d->mAllowHide = true; 0253 } 0254 0255 void TagSelectionComboBox::keyPressEvent(QKeyEvent *event) 0256 { 0257 switch (event->key()) { 0258 case Qt::Key_Up: 0259 case Qt::Key_Down: 0260 showPopup(); 0261 event->accept(); 0262 break; 0263 case Qt::Key_Return: 0264 case Qt::Key_Enter: 0265 case Qt::Key_Escape: 0266 hidePopup(); 0267 event->accept(); 0268 break; 0269 default: 0270 break; 0271 } 0272 } 0273 0274 bool TagSelectionComboBox::eventFilter(QObject *receiver, QEvent *event) 0275 { 0276 switch (event->type()) { 0277 case QEvent::KeyPress: 0278 case QEvent::KeyRelease: 0279 case QEvent::ShortcutOverride: 0280 switch (static_cast<QKeyEvent *>(event)->key()) { 0281 case Qt::Key_Return: 0282 case Qt::Key_Enter: 0283 case Qt::Key_Escape: 0284 hidePopup(); 0285 return true; 0286 } 0287 break; 0288 case QEvent::MouseButtonDblClick: 0289 case QEvent::MouseButtonPress: 0290 case QEvent::MouseButtonRelease: 0291 d->mAllowHide = false; 0292 if (receiver == lineEdit()) { 0293 showPopup(); 0294 return true; 0295 } 0296 break; 0297 default: 0298 break; 0299 } 0300 return QComboBox::eventFilter(receiver, event); 0301 } 0302 0303 #include "moc_tagselectioncombobox.cpp"