File indexing completed on 2024-11-10 04:48:57
0001 /* 0002 This file is part of libkdepim. 0003 0004 SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at> 0005 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0008 */ 0009 0010 #include "kcheckcombobox.h" 0011 0012 #include "libkdepim_debug.h" 0013 0014 #include <QAbstractItemView> 0015 #include <QKeyEvent> 0016 #include <QLineEdit> 0017 #include <QStandardItemModel> 0018 0019 using namespace KPIM; 0020 0021 /// Class KCheckComboBox::KCheckComboBoxPrivate 0022 0023 namespace KPIM 0024 { 0025 class Q_DECL_HIDDEN KCheckComboBox::KCheckComboBoxPrivate 0026 { 0027 public: 0028 KCheckComboBoxPrivate(KCheckComboBox *qq) 0029 : mSeparator(QLatin1Char(',')) 0030 , q(qq) 0031 { 0032 } 0033 0034 void makeInsertedItemsCheckable(const QModelIndex &, int start, int end); 0035 [[nodiscard]] QString squeeze(const QString &text); 0036 void updateCheckedItems(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex(), int role = Qt::DisplayRole); 0037 void toggleCheckState(); 0038 0039 public: 0040 QString mSeparator; 0041 QString mDefaultText; 0042 bool mSqueezeText = false; 0043 bool mIgnoreHide = false; 0044 bool mAlwaysShowDefaultText = false; 0045 0046 private: 0047 KCheckComboBox *const q; 0048 }; 0049 } 0050 0051 void KCheckComboBox::KCheckComboBoxPrivate::makeInsertedItemsCheckable(const QModelIndex &parent, int start, int end) 0052 { 0053 Q_UNUSED(parent) 0054 auto model = qobject_cast<QStandardItemModel *>(q->model()); 0055 if (model) { 0056 for (int r = start; r <= end; ++r) { 0057 QStandardItem *item = model->item(r, 0); 0058 item->setCheckable(true); 0059 } 0060 } else { 0061 qCWarning(LIBKDEPIM_LOG) << "KCheckComboBox: model is not a QStandardItemModel but a" << q->model() << ". Cannot proceed."; 0062 } 0063 } 0064 0065 QString KCheckComboBox::KCheckComboBoxPrivate::squeeze(const QString &text) 0066 { 0067 QFontMetrics fm(q->fontMetrics()); 0068 // The 4 pixels is 2 * horizontalMargin from QLineEdit. 0069 // The rest is code from QLineEdit::paintEvent, where it determines whether to scroll the text 0070 // (on my machine minLB=2 and minRB=2, so this removes 8 pixels in total) 0071 const int minLB = qMax(0, -fm.minLeftBearing()); 0072 const int minRB = qMax(0, -fm.minRightBearing()); 0073 const int lineEditWidth = q->lineEdit()->width() - 4 - minLB - minRB; 0074 const int textWidth = fm.boundingRect(text).width(); 0075 if (textWidth > lineEditWidth) { 0076 return fm.elidedText(text, Qt::ElideMiddle, lineEditWidth); 0077 } 0078 0079 return text; 0080 } 0081 0082 void KCheckComboBox::KCheckComboBoxPrivate::updateCheckedItems(const QModelIndex &topLeft, const QModelIndex &bottomRight, int role) 0083 { 0084 Q_UNUSED(topLeft) 0085 Q_UNUSED(bottomRight) 0086 0087 const QStringList items = q->checkedItems(role); 0088 QString text; 0089 if (items.isEmpty() || mAlwaysShowDefaultText) { 0090 text = mDefaultText; 0091 } else { 0092 text = items.join(mSeparator); 0093 } 0094 0095 if (mSqueezeText) { 0096 text = squeeze(text); 0097 } 0098 0099 q->lineEdit()->setText(text); 0100 0101 Q_EMIT q->checkedItemsChanged(items); 0102 } 0103 0104 void KCheckComboBox::KCheckComboBoxPrivate::toggleCheckState() 0105 { 0106 int selected = q->currentIndex(); 0107 if (q->view()->isVisible() && q->itemEnabled(selected)) { 0108 const QModelIndex index = q->view()->currentIndex().siblingAtRow(selected); 0109 const QVariant value = index.data(Qt::CheckStateRole); 0110 if (value.isValid()) { 0111 const auto state = static_cast<Qt::CheckState>(value.toInt()); 0112 q->model()->setData(index, state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); 0113 } 0114 } 0115 } 0116 0117 /// Class KCheckComboBox 0118 0119 KCheckComboBox::KCheckComboBox(QWidget *parent) 0120 : QComboBox(parent) 0121 , d(new KCheckComboBox::KCheckComboBoxPrivate(this)) 0122 { 0123 connect(this, &QComboBox::activated, this, [this]() { 0124 d->toggleCheckState(); 0125 }); 0126 connect(model(), &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &index, int start, int end) { 0127 d->makeInsertedItemsCheckable(index, start, end); 0128 }); 0129 connect(model(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight) { 0130 d->updateCheckedItems(topLeft, bottomRight); 0131 }); 0132 0133 // read-only contents 0134 setEditable(true); 0135 0136 lineEdit()->setAlignment(Qt::AlignLeft); 0137 connect(lineEdit(), &QLineEdit::textChanged, this, [this](const QString &text) { 0138 if (text.isEmpty()) { 0139 // Clear checked items 0140 setCheckedItems(QStringList()); 0141 } 0142 }); 0143 0144 view()->installEventFilter(this); 0145 view()->viewport()->installEventFilter(this); 0146 0147 lineEdit()->installEventFilter(this); 0148 0149 d->updateCheckedItems(); 0150 } 0151 0152 KCheckComboBox::~KCheckComboBox() = default; 0153 0154 void KCheckComboBox::hidePopup() 0155 { 0156 if (!d->mIgnoreHide) { 0157 QComboBox::hidePopup(); 0158 } 0159 d->mIgnoreHide = false; 0160 } 0161 0162 Qt::CheckState KCheckComboBox::itemCheckState(int index) const 0163 { 0164 return static_cast<Qt::CheckState>(itemData(index, Qt::CheckStateRole).toInt()); 0165 } 0166 0167 void KCheckComboBox::setItemCheckState(int index, Qt::CheckState state) 0168 { 0169 setItemData(index, state, Qt::CheckStateRole); 0170 } 0171 0172 QStringList KCheckComboBox::checkedItems(int role) const 0173 { 0174 QStringList items; 0175 if (model()) { 0176 const QModelIndex index = model()->index(0, modelColumn(), rootModelIndex()); 0177 const QModelIndexList indexes = model()->match(index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly); 0178 items.reserve(indexes.count()); 0179 for (const QModelIndex &index : indexes) { 0180 items += index.data(role).toString(); 0181 } 0182 } 0183 return items; 0184 } 0185 0186 void KCheckComboBox::setCheckedItems(const QStringList &items, int role) 0187 { 0188 for (int r = 0; r < model()->rowCount(rootModelIndex()); ++r) { 0189 const QModelIndex indx = model()->index(r, modelColumn(), rootModelIndex()); 0190 0191 const QString text = indx.data(role).toString(); 0192 const bool found = items.contains(text); 0193 model()->setData(indx, found ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); 0194 } 0195 d->updateCheckedItems(QModelIndex(), QModelIndex(), role); 0196 } 0197 0198 QString KCheckComboBox::defaultText() const 0199 { 0200 return d->mDefaultText; 0201 } 0202 0203 void KCheckComboBox::setDefaultText(const QString &text) 0204 { 0205 if (d->mDefaultText != text) { 0206 d->mDefaultText = text; 0207 d->updateCheckedItems(); 0208 } 0209 } 0210 0211 bool KCheckComboBox::squeezeText() const 0212 { 0213 return d->mSqueezeText; 0214 } 0215 0216 void KCheckComboBox::setSqueezeText(bool squeeze) 0217 { 0218 if (d->mSqueezeText != squeeze) { 0219 d->mSqueezeText = squeeze; 0220 d->updateCheckedItems(); 0221 } 0222 } 0223 0224 bool KCheckComboBox::itemEnabled(int index) 0225 { 0226 Q_ASSERT(index >= 0 && index <= count()); 0227 0228 auto itemModel = qobject_cast<QStandardItemModel *>(model()); 0229 Q_ASSERT(itemModel); 0230 0231 QStandardItem *item = itemModel->item(index, 0); 0232 return item->isEnabled(); 0233 } 0234 0235 void KCheckComboBox::setItemEnabled(int index, bool enabled) 0236 { 0237 Q_ASSERT(index >= 0 && index <= count()); 0238 0239 auto itemModel = qobject_cast<QStandardItemModel *>(model()); 0240 Q_ASSERT(itemModel); 0241 0242 QStandardItem *item = itemModel->item(index, 0); 0243 item->setEnabled(enabled); 0244 } 0245 0246 QString KCheckComboBox::separator() const 0247 { 0248 return d->mSeparator; 0249 } 0250 0251 void KCheckComboBox::setSeparator(const QString &separator) 0252 { 0253 if (d->mSeparator != separator) { 0254 d->mSeparator = separator; 0255 d->updateCheckedItems(); 0256 } 0257 } 0258 0259 void KCheckComboBox::keyPressEvent(QKeyEvent *event) 0260 { 0261 switch (event->key()) { 0262 case Qt::Key_Up: 0263 case Qt::Key_Down: 0264 showPopup(); 0265 event->accept(); 0266 break; 0267 case Qt::Key_Return: 0268 case Qt::Key_Enter: 0269 case Qt::Key_Escape: 0270 hidePopup(); 0271 event->accept(); 0272 break; 0273 default: 0274 break; 0275 } 0276 // don't call base class implementation, we don't need all that stuff in there 0277 } 0278 0279 #ifndef QT_NO_WHEELEVENT 0280 void KCheckComboBox::wheelEvent(QWheelEvent *event) 0281 { 0282 // discard mouse wheel events on the combo box 0283 event->accept(); 0284 } 0285 0286 #endif 0287 0288 void KCheckComboBox::resizeEvent(QResizeEvent *event) 0289 { 0290 QComboBox::resizeEvent(event); 0291 if (d->mSqueezeText) { 0292 d->updateCheckedItems(); 0293 } 0294 } 0295 0296 bool KCheckComboBox::eventFilter(QObject *receiver, QEvent *event) 0297 { 0298 switch (event->type()) { 0299 case QEvent::KeyPress: 0300 case QEvent::KeyRelease: 0301 case QEvent::ShortcutOverride: 0302 switch (static_cast<QKeyEvent *>(event)->key()) { 0303 case Qt::Key_Space: 0304 if (event->type() == QEvent::KeyPress && view()->isVisible()) { 0305 d->toggleCheckState(); 0306 } 0307 // Always eat the event: don't let QItemDelegate toggle the current index when the view is hidden. 0308 return true; 0309 case Qt::Key_Return: 0310 case Qt::Key_Enter: 0311 case Qt::Key_Escape: 0312 // ignore Enter keys, they would normally select items. 0313 // but we select with Space, because multiple selection is possible 0314 // we simply close the popup on Enter/Escape 0315 hidePopup(); 0316 return true; 0317 } 0318 break; 0319 case QEvent::MouseButtonDblClick: 0320 case QEvent::MouseButtonPress: 0321 case QEvent::MouseButtonRelease: 0322 d->mIgnoreHide = true; 0323 if (receiver == lineEdit()) { 0324 showPopup(); 0325 return true; 0326 } 0327 break; 0328 default: 0329 break; 0330 } 0331 return QComboBox::eventFilter(receiver, event); 0332 } 0333 0334 bool KCheckComboBox::alwaysShowDefaultText() const 0335 { 0336 return d->mAlwaysShowDefaultText; 0337 } 0338 0339 void KCheckComboBox::setAlwaysShowDefaultText(bool always) 0340 { 0341 if (always != d->mAlwaysShowDefaultText) { 0342 d->mAlwaysShowDefaultText = always; 0343 d->updateCheckedItems(); 0344 } 0345 } 0346 0347 #include "moc_kcheckcombobox.cpp"