Skip to content

Instantly share code, notes, and snippets.

@eugesh
Forked from mistic100/qchecklist.h
Last active April 26, 2024 12:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eugesh/fe43a4b596702ae41498d4831711ba3d to your computer and use it in GitHub Desktop.
Save eugesh/fe43a4b596702ae41498d4831711ba3d to your computer and use it in GitHub Desktop.
[Qt/C++] QComboBox with support of checkboxes
#ifndef QCHECKLIST
#define QCHECKLIST
#include <QWidget>
#include <QComboBox>
#include <QStandardItemModel>
#include <QLineEdit>
#include <QEvent>
#include <QStyledItemDelegate>
#include <QListView>
/**
* @brief QComboBox with support of checkboxes
* http://stackoverflow.com/questions/8422760/combobox-of-checkboxes
*
* @author Damien Sorel <damien@sorel.me>
*/
class QCheckList : public QComboBox
{
Q_OBJECT
public:
/**
* @brief Additional value to Qt::CheckState when some checkboxes are Qt::PartiallyChecked
*/
static const int StateUnknown = 3;
private:
QStandardItemModel* m_model;
/**
* @brief Text displayed when no item is checked
*/
QString m_noneCheckedText;
/**
* @brief Text displayed when all items are checked
*/
QString m_allCheckedText;
/**
* @brief Text displayed when some items are partially checked
*/
QString m_unknownlyCheckedText;
signals:
void globalCheckStateChanged(int);
void checkStateChanged(int index, Qt::CheckState);
public:
QCheckList(QWidget* _parent = nullptr) : QComboBox(_parent)
{
m_model = new QStandardItemModel();
setModel(m_model);
setEditable(true);
lineEdit()->setReadOnly(true);
lineEdit()->installEventFilter(this);
setItemDelegate(new QCheckListStyledItemDelegate(this));
connect(lineEdit(), &QLineEdit::selectionChanged, lineEdit(), &QLineEdit::deselect);
connect(view(), &QAbstractItemView::pressed, this, &QCheckList::on_itemPressed);
connect(m_model, &QAbstractItemModel::dataChanged, this, &QCheckList::on_modelDataChanged);
connect(this, &QCheckList::checkStateChanged, [=](int index, Qt::CheckState state) {
if (!m_noneCheckedText.isEmpty() && itemText(index) == m_noneCheckedText && state == Qt::Checked) {
for (int i = 1; i < count(); ++i) {
setChecked(i, Qt::Unchecked);
}
} else if (!m_allCheckedText.isEmpty() && itemText(index) == m_allCheckedText && state == Qt::Checked) {
int first = 0;
if (!m_noneCheckedText.isEmpty()) {
setChecked(m_noneCheckedText, Qt::Unchecked);
first = 1;
}
for (int i = first; i < count(); ++i) {
setChecked(i, Qt::Checked);
}
} else if (!m_noneCheckedText.isEmpty() && isChecked(m_noneCheckedText)) {
setChecked(m_noneCheckedText, Qt::Unchecked);
} else if (state == Qt::Unchecked && itemText(index) != m_noneCheckedText ) {
setChecked(m_allCheckedText, Qt::Unchecked);
}
} );
}
~QCheckList()
{
delete m_model;
}
void setAllCheckedText(const QString &text)
{
m_allCheckedText = text;
updateText();
}
void setNoneCheckedText(const QString &text)
{
m_noneCheckedText = text;
updateText();
}
void setUnknownlyCheckedText(const QString &text)
{
m_unknownlyCheckedText = text;
updateText();
}
/**
* @brief Adds a item to the checklist (setChecklist must have been called)
* @return the new QStandardItem
*/
QStandardItem* addCheckItem(const QString &label, const QVariant &data, const Qt::CheckState checkState)
{
QStandardItem* item = new QStandardItem(label);
item->setCheckState(checkState);
item->setData(data);
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
m_model->appendRow(item);
updateText();
return item;
}
QVector<QStandardItem*> checkedItems()
{
QVector<QStandardItem*> items;
for (int i = 0; i < m_model->rowCount(); i++)
{
if (m_model->item(i)->checkState() == Qt::Checked)
items.append(m_model->item(i));
}
return items;
}
bool isChecked(int index) const
{
return (m_model->item(index)->checkState() == Qt::Checked);
}
bool isChecked(const QString &text) const
{
return (m_model->item(findText(text))->checkState() == Qt::Checked);
}
void setChecked(int index, Qt::CheckState checked = Qt::Checked)
{
m_model->item(index)->setCheckState(checked);
}
void setChecked(const QString &text, Qt::CheckState checked = Qt::Checked)
{
m_model->item(findText(text))->setCheckState(checked);
}
/**
* @brief Computes the global state of the checklist :
* - if there is no item: StateUnknown
* - if there is at least one item partially checked: StateUnknown
* - if all items are checked: Qt::Checked
* - if no item is checked: Qt::Unchecked
* - else: Qt::PartiallyChecked
*/
int globalCheckState()
{
int nbRows = m_model->rowCount(), nbChecked = 0, nbUnchecked = 0;
if (nbRows == 0)
{
return StateUnknown;
}
for (int i = 0; i < nbRows; i++)
{
if (m_model->item(i)->checkState() == Qt::Checked)
{
nbChecked++;
}
else if (m_model->item(i)->checkState() == Qt::Unchecked)
{
nbUnchecked++;
}
else
{
return StateUnknown;
}
}
return nbChecked == nbRows ? Qt::Checked : nbUnchecked == nbRows ? Qt::Unchecked : Qt::PartiallyChecked;
}
protected:
bool eventFilter(QObject* _object, QEvent* _event)
{
if (_object == lineEdit() && _event->type() == QEvent::MouseButtonPress)
{
showPopup();
return true;
}
return false;
}
private:
void updateText()
{
QString text;
switch (globalCheckState())
{
case Qt::Checked:
text = m_allCheckedText;
break;
case Qt::Unchecked:
text = m_noneCheckedText;
break;
case Qt::PartiallyChecked:
for (int i = 0; i < m_model->rowCount(); i++)
{
if (m_model->item(i)->checkState() == Qt::Checked)
{
if (!text.isEmpty())
{
text+= ", ";
}
text+= m_model->item(i)->text();
}
}
break;
default:
text = m_unknownlyCheckedText;
}
lineEdit()->setText(text);
}
private slots:
void on_modelDataChanged()
{
updateText();
emit globalCheckStateChanged(globalCheckState());
}
void on_itemPressed(const QModelIndex &index)
{
QStandardItem* item = m_model->itemFromIndex(index);
if (item->checkState() == Qt::Checked)
{
item->setCheckState(Qt::Unchecked);
}
else
{
item->setCheckState(Qt::Checked);
}
emit checkStateChanged(index.row(), item->checkState());
}
public:
class QCheckListStyledItemDelegate : public QStyledItemDelegate
{
public:
QCheckListStyledItemDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
void paint(QPainter * painter_, const QStyleOptionViewItem & option_, const QModelIndex & index_) const
{
QStyleOptionViewItem & refToNonConstOption = const_cast<QStyleOptionViewItem &>(option_);
refToNonConstOption.showDecorationSelected = false;
QStyledItemDelegate::paint(painter_, refToNonConstOption, index_);
}
};
};
#endif // QCHECKLIST
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment