Libkdepim

kcheckcombobox.cpp
1/*
2 This file is part of libkdepim.
3
4 SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at>
5 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
8*/
9
10#include "kcheckcombobox.h"
11
12#include "libkdepim_debug.h"
13
14#include <QAbstractItemView>
15#include <QKeyEvent>
16#include <QLineEdit>
17#include <QStandardItemModel>
18
19using namespace KPIM;
20
21/// Class KCheckComboBox::KCheckComboBoxPrivate
22
23namespace KPIM
24{
25class Q_DECL_HIDDEN KCheckComboBox::KCheckComboBoxPrivate
26{
27public:
28 KCheckComboBoxPrivate(KCheckComboBox *qq)
29 : mSeparator(QLatin1Char(','))
30 , q(qq)
31 {
32 }
33
34 void makeInsertedItemsCheckable(const QModelIndex &, int start, int end);
35 [[nodiscard]] QString squeeze(const QString &text);
36 void updateCheckedItems(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex(), int role = Qt::DisplayRole);
37 void toggleCheckState();
38
39public:
40 QString mSeparator;
41 QString mDefaultText;
42 bool mSqueezeText = false;
43 bool mIgnoreHide = false;
44 bool mAlwaysShowDefaultText = false;
45
46private:
47 KCheckComboBox *const q;
48};
49}
50
51void KCheckComboBox::KCheckComboBoxPrivate::makeInsertedItemsCheckable(const QModelIndex &parent, int start, int end)
52{
53 Q_UNUSED(parent)
55 if (model) {
56 for (int r = start; r <= end; ++r) {
57 QStandardItem *item = model->item(r, 0);
58 item->setCheckable(true);
59 }
60 } else {
61 qCWarning(LIBKDEPIM_LOG) << "KCheckComboBox: model is not a QStandardItemModel but a" << q->model() << ". Cannot proceed.";
62 }
63}
64
65QString KCheckComboBox::KCheckComboBoxPrivate::squeeze(const QString &text)
66{
67 QFontMetrics fm(q->fontMetrics());
68 // The 4 pixels is 2 * horizontalMargin from QLineEdit.
69 // The rest is code from QLineEdit::paintEvent, where it determines whether to scroll the text
70 // (on my machine minLB=2 and minRB=2, so this removes 8 pixels in total)
71 const int minLB = qMax(0, -fm.minLeftBearing());
72 const int minRB = qMax(0, -fm.minRightBearing());
73 const int lineEditWidth = q->lineEdit()->width() - 4 - minLB - minRB;
74 const int textWidth = fm.boundingRect(text).width();
75 if (textWidth > lineEditWidth) {
76 return fm.elidedText(text, Qt::ElideMiddle, lineEditWidth);
77 }
78
79 return text;
80}
81
82void KCheckComboBox::KCheckComboBoxPrivate::updateCheckedItems(const QModelIndex &topLeft, const QModelIndex &bottomRight, int role)
83{
84 Q_UNUSED(topLeft)
85 Q_UNUSED(bottomRight)
86
87 const QStringList items = q->checkedItems(role);
88 QString text;
89 if (items.isEmpty() || mAlwaysShowDefaultText) {
90 text = mDefaultText;
91 } else {
92 text = items.join(mSeparator);
93 }
94
95 if (mSqueezeText) {
96 text = squeeze(text);
97 }
98
99 q->lineEdit()->setText(text);
100
101 Q_EMIT q->checkedItemsChanged(items);
102}
103
104void KCheckComboBox::KCheckComboBoxPrivate::toggleCheckState()
105{
106 int selected = q->currentIndex();
107 if (q->view()->isVisible() && q->itemEnabled(selected)) {
108 const QModelIndex index = q->view()->currentIndex().siblingAtRow(selected);
109 const QVariant value = index.data(Qt::CheckStateRole);
110 if (value.isValid()) {
111 const auto state = static_cast<Qt::CheckState>(value.toInt());
112 q->model()->setData(index, state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
113 }
114 }
115}
116
117/// Class KCheckComboBox
118
121 , d(new KCheckComboBox::KCheckComboBoxPrivate(this))
122{
123 connect(this, &QComboBox::activated, this, [this]() {
124 d->toggleCheckState();
125 });
126 connect(model(), &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &index, int start, int end) {
127 d->makeInsertedItemsCheckable(index, start, end);
128 });
129 connect(model(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
130 d->updateCheckedItems(topLeft, bottomRight);
131 });
132
133 // read-only contents
134 setEditable(true);
135
137 connect(lineEdit(), &QLineEdit::textChanged, this, [this](const QString &text) {
138 if (text.isEmpty()) {
139 // Clear checked items
141 }
142 });
143
144 view()->installEventFilter(this);
145 view()->viewport()->installEventFilter(this);
146
148
149 d->updateCheckedItems();
150}
151
153
155{
156 if (!d->mIgnoreHide) {
158 }
159 d->mIgnoreHide = false;
160}
161
163{
164 return static_cast<Qt::CheckState>(itemData(index, Qt::CheckStateRole).toInt());
165}
166
168{
169 setItemData(index, state, Qt::CheckStateRole);
170}
171
172QStringList KCheckComboBox::checkedItems(int role) const
173{
174 QStringList items;
175 if (model()) {
176 const QModelIndex index = model()->index(0, modelColumn(), rootModelIndex());
177 const QModelIndexList indexes = model()->match(index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly);
178 items.reserve(indexes.count());
179 for (const QModelIndex &index : indexes) {
180 items += index.data(role).toString();
181 }
182 }
183 return items;
184}
185
187{
188 for (int r = 0; r < model()->rowCount(rootModelIndex()); ++r) {
189 const QModelIndex indx = model()->index(r, modelColumn(), rootModelIndex());
190
191 const QString text = indx.data(role).toString();
192 const bool found = items.contains(text);
194 }
195 d->updateCheckedItems(QModelIndex(), QModelIndex(), role);
196}
197
198QString KCheckComboBox::defaultText() const
199{
200 return d->mDefaultText;
201}
202
204{
205 if (d->mDefaultText != text) {
206 d->mDefaultText = text;
207 d->updateCheckedItems();
208 }
209}
210
211bool KCheckComboBox::squeezeText() const
212{
213 return d->mSqueezeText;
214}
215
217{
218 if (d->mSqueezeText != squeeze) {
219 d->mSqueezeText = squeeze;
220 d->updateCheckedItems();
221 }
222}
223
225{
226 Q_ASSERT(index >= 0 && index <= count());
227
228 auto itemModel = qobject_cast<QStandardItemModel *>(model());
229 Q_ASSERT(itemModel);
230
231 QStandardItem *item = itemModel->item(index, 0);
232 return item->isEnabled();
233}
234
235void KCheckComboBox::setItemEnabled(int index, bool enabled)
236{
237 Q_ASSERT(index >= 0 && index <= count());
238
239 auto itemModel = qobject_cast<QStandardItemModel *>(model());
240 Q_ASSERT(itemModel);
241
242 QStandardItem *item = itemModel->item(index, 0);
243 item->setEnabled(enabled);
244}
245
246QString KCheckComboBox::separator() const
247{
248 return d->mSeparator;
249}
250
252{
253 if (d->mSeparator != separator) {
254 d->mSeparator = separator;
255 d->updateCheckedItems();
256 }
257}
258
259void KCheckComboBox::keyPressEvent(QKeyEvent *event)
260{
261 switch (event->key()) {
262 case Qt::Key_Up:
263 case Qt::Key_Down:
264 showPopup();
265 event->accept();
266 break;
267 case Qt::Key_Return:
268 case Qt::Key_Enter:
269 case Qt::Key_Escape:
270 hidePopup();
271 event->accept();
272 break;
273 default:
274 break;
275 }
276 // don't call base class implementation, we don't need all that stuff in there
277}
278
279#ifndef QT_NO_WHEELEVENT
280void KCheckComboBox::wheelEvent(QWheelEvent *event)
281{
282 // discard mouse wheel events on the combo box
283 event->accept();
284}
285
286#endif
287
288void KCheckComboBox::resizeEvent(QResizeEvent *event)
289{
291 if (d->mSqueezeText) {
292 d->updateCheckedItems();
293 }
294}
295
296bool KCheckComboBox::eventFilter(QObject *receiver, QEvent *event)
297{
298 switch (event->type()) {
299 case QEvent::KeyPress:
302 switch (static_cast<QKeyEvent *>(event)->key()) {
303 case Qt::Key_Space:
304 if (event->type() == QEvent::KeyPress && view()->isVisible()) {
305 d->toggleCheckState();
306 }
307 // Always eat the event: don't let QItemDelegate toggle the current index when the view is hidden.
308 return true;
309 case Qt::Key_Return:
310 case Qt::Key_Enter:
311 case Qt::Key_Escape:
312 // ignore Enter keys, they would normally select items.
313 // but we select with Space, because multiple selection is possible
314 // we simply close the popup on Enter/Escape
315 hidePopup();
316 return true;
317 }
318 break;
322 d->mIgnoreHide = true;
323 if (receiver == lineEdit()) {
324 showPopup();
325 return true;
326 }
327 break;
328 default:
329 break;
330 }
331 return QComboBox::eventFilter(receiver, event);
332}
333
335{
336 return d->mAlwaysShowDefaultText;
337}
338
340{
341 if (always != d->mAlwaysShowDefaultText) {
342 d->mAlwaysShowDefaultText = always;
343 d->updateCheckedItems();
344 }
345}
346
347#include "moc_kcheckcombobox.cpp"
A combobox that shows its items in such a way that they can be checked in the drop menu.
bool alwaysShowDefaultText() const
Returns whether the default text is always shown, even if there are no checked items.
KCheckComboBox(QWidget *parent=nullptr)
Creates a new checkable combobox.
void setItemEnabled(int index, bool enabled=true)
Set the item at.
bool itemEnabled(int index)
Return whether or not the item at.
~KCheckComboBox() override
Destroys the time zone combobox.
void hidePopup() override
Hides the popup list if it is currently shown.
void setSeparator(const QString &separator)
Sets the separator used to separate items in the line edit.
void setSqueezeText(bool squeeze)
Sets whether or not the text must be squeezed.
Qt::CheckState itemCheckState(int index) const
Returns the check state of item at given index.
void setItemCheckState(int index, Qt::CheckState state)
Changes the check state of the given index to the given state.
void setDefaultText(const QString &text)
Sets the default text that is shown when no items are selected.
void setCheckedItems(const QStringList &items, int role=Qt::DisplayRole)
Sets the currently selected items.
void setAlwaysShowDefaultText(bool always)
Sets if the default text should always be shown even if there are no checked items.
Q_SCRIPTABLE Q_NOREPLY void start()
Class KCheckComboBox::KCheckComboBoxPrivate.
const QList< QKeySequence > & end()
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const
virtual int rowCount(const QModelIndex &parent) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
QWidget * viewport() const const
void activated(int index)
void setEditable(bool editable)
virtual bool event(QEvent *event) override
virtual void hidePopup()
QVariant itemData(int index, int role) const const
QLineEdit * lineEdit() const const
QAbstractItemModel * model() const const
virtual void resizeEvent(QResizeEvent *e) override
QModelIndex rootModelIndex() const const
void setItemData(int index, const QVariant &value, int role)
virtual void showPopup()
QAbstractItemView * view() const const
void setAlignment(Qt::Alignment flag)
void textChanged(const QString &text)
bool isEmpty() const const
void reserve(qsizetype size)
QVariant data(int role) const const
QModelIndex siblingAtRow(int row) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
QObject * parent() const const
T qobject_cast(QObject *object)
bool isEnabled() const const
void setCheckable(bool checkable)
void setEnabled(bool enabled)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
AlignLeft
CheckState
DisplayRole
MatchExactly
ElideMiddle
bool isValid() const const
int toInt(bool *ok) const const
QString toString() const const
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:17 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.