Понадобилось реализовать выпадающий список с множественным выбором для программы на Qt5. Для этого реализовал модель, у элементов которой выставлен флаг Qt::ItemIsUserCheckable
class ItemModel : public QStandardItemModel
{
    Q_OBJECT
  public:
    ItemModel(QObject *parent = nullptr);
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
  signals:
    void itemCheckStateChanged();
};
ItemModel::ItemModel(QObject *parent)
  : QStandardItemModel(0, 1, parent)
{
}
Qt::ItemFlags ItemModel::flags(const QModelIndex &index) const
{
  return QStandardItemModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant ItemModel::data(const QModelIndex &index, int role) const
{
  QVariant value = QStandardItemModel::data(index, role);
  if (index.isValid() && role == Qt::CheckStateRole && !value.isValid())
  {
    value = Qt::Unchecked;
  }
  return value;
}
bool ItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
  bool ok = QStandardItemModel::setData(index, value, role);
  if (ok && role == Qt::CheckStateRole)
  {
    emit dataChanged(index, index);
    emit itemCheckStateChanged();
  }
  return ok;
}
и собственно сам combobox с отображением флажков
class CComboBox : public QComboBox
{
    Q_OBJECT
  public:
    CComboBox(QWidget *parent = nullptr);
    QString separator() const;
    void setSeparator(const QString &separator);
    QString defaultText() const;
    void setDefaultText(const QString &text);
    QStringList checkedItems() const;
    Qt::CheckState itemCheckState(int index) const;
    void setItemCheckState(int index, Qt::CheckState state);
    void toggleItemCheckState(int index);
    void hidePopup() override;
    bool eventFilter(QObject *object, QEvent *event) override;
  signals:
    void checkedItemsChanged(const QStringList &items);
  public slots:
    void setCheckedItems(const QStringList &items);
  protected:
    void resizeEvent(QResizeEvent *event) override;
  protected slots:
    void showContextMenu(QPoint pos);
    void selectAllOptions();
    void deselectAllOptions();
  private:
    void updateCheckedItems();
    void updateDisplayText();
    QString mSeparator;
    QString mDefaultText;
    bool mSkipHide = false;
    QMenu *mContextMenu = nullptr;
    QAction *mSelectAllAction = nullptr;
    QAction *mDeselectAllAction = nullptr;
};
CComboBox::CComboBox(QWidget *parent) : QComboBox(parent), mSeparator(QStringLiteral(", "))
{
  setModel(new ItemModel(this));
  QLineEdit *lineEdit = new QLineEdit(this);
  lineEdit->setReadOnly(true);
  setLineEdit(lineEdit);
  mContextMenu = new QMenu(this);
  mSelectAllAction = mContextMenu->addAction(tr("Select All"));
  mDeselectAllAction = mContextMenu->addAction(tr("Deselect All"));
  connect(mSelectAllAction, &QAction::triggered, this, &CComboBox::selectAllOptions);
  connect(mDeselectAllAction, &QAction::triggered, this, &CComboBox::deselectAllOptions);
  view()->viewport()->installEventFilter(this);
  view()->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(view(), &QAbstractItemView::customContextMenuRequested, this, &CComboBox::showContextMenu);
  ItemModel *myModel = qobject_cast<QgsCheckableItemModel *>(model());
  connect(myModel, &QgsCheckableItemModel::itemCheckStateChanged, this, &CComboBox::updateCheckedItems);
  connect(model(), &QStandardItemModel::rowsInserted, this, [ = ](const QModelIndex &, int, int) { updateCheckedItems(); });
  connect(model(), &QStandardItemModel::rowsRemoved, this, [ = ](const QModelIndex &, int, int) { updateCheckedItems(); });
  connect(this, static_cast< void (QComboBox::*)(int) >(&QComboBox::activated), this, &CComboBox::toggleItemCheckState);
}
QString CComboBox::separator() const
{
  return mSeparator;
}
void CComboBox::setSeparator(const QString &separator)
{
  if (mSeparator != separator)
  {
    mSeparator = separator;
    updateDisplayText();
  }
}
QString CComboBox::defaultText() const
{
  return mDefaultText;
}
void CComboBox::setDefaultText(const QString &text)
{
  if (mDefaultText != text)
  {
    mDefaultText = text;
    updateDisplayText();
  }
}
QStringList CComboBox::checkedItems() const
{
  QStringList items;
  if (model())
  {
    QModelIndex index = model()->index(0, modelColumn(), rootModelIndex());
    QModelIndexList indexes = model()->match(index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly);
    const auto constIndexes = indexes;
    for (const QModelIndex &index : constIndexes)
    {
      items += index.data().toString();
    }
  }
  return items;
}
Qt::CheckState CComboBox::itemCheckState(int index) const
{
  return static_cast<Qt::CheckState>(itemData(index, Qt::CheckStateRole).toInt());
}
void CComboBox::setItemCheckState(int index, Qt::CheckState state)
{
  setItemData(index, state, Qt::CheckStateRole);
}
void CComboBox::toggleItemCheckState(int index)
{
  QVariant value = itemData(index, Qt::CheckStateRole);
  if (value.isValid())
  {
    Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
    setItemData(index, (state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked), Qt::CheckStateRole);
  }
}
void CComboBox::hidePopup()
{
  if (!mSkipHide)
  {
    QComboBox::hidePopup();
  }
  mSkipHide = false;
}
void CComboBox::showContextMenu(QPoint pos)
{
  Q_UNUSED(pos)
  mContextMenu->exec(QCursor::pos());
}
void CComboBox::selectAllOptions()
{
  blockSignals(true);
  for (int i = 0;  i < count(); i++)
  {
    setItemData(i, Qt::Checked, Qt::CheckStateRole);
  }
  blockSignals(false);
  updateCheckedItems();
}
void CComboBox::deselectAllOptions()
{
  blockSignals(true);
  for (int i = 0;  i < count(); i++)
  {
    setItemData(i, Qt::Unchecked, Qt::CheckStateRole);
  }
  blockSignals(false);
  updateCheckedItems();
}
bool CComboBox::eventFilter(QObject *object, QEvent *event)
{
  if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && object == view()->viewport())
  {
    mSkipHide = true;
  }
  if (event->type() == QEvent::MouseButtonRelease)
  {
    if (static_cast<QMouseEvent *>(event)->button() == Qt::RightButton)
    {
      return true;
    }
  }
  return QComboBox::eventFilter(object, event);
}
void CComboBox::setCheckedItems(const QStringList &items)
{
  const auto constItems = items;
  for (const QString &text : constItems)
  {
    const int index = findText(text);
    setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked);
  }
}
void CComboBox::resizeEvent(QResizeEvent *event)
{
  QComboBox::resizeEvent(event);
  updateDisplayText();
}
void CComboBox::updateCheckedItems()
{
  QStringList items = checkedItems();
  updateDisplayText();
  emit checkedItemsChanged(items);
}
void CComboBox::updateDisplayText()
{
  QString text;
  QStringList items = checkedItems();
  if (items.isEmpty())
  {
    text = mDefaultText;
  }
  else
  {
    text = items.join(mSeparator);
  }
  QRect rect = lineEdit()->rect();
  QFontMetrics fontMetrics(font());
  text = fontMetrics.elidedText(text, Qt::ElideRight, rect.width());
  setEditText(text);
}
В целом все работает, но есть один непонятный баг. Если отметить флажками два элемента (или больше), а затем, не закрывая выпадающий список, снять флажки у всех элементов, кроме одного и щелкнуть за пределами списка чтобы закрыть его, то у единственного отмеченого элемента практически всегда сбрасывается состояние на неотмеченное. Подозреваю, что это каким-то образом связано з сигналом activated, но решить проблему не могу. Кто-нибудь может подсказать как поправить?
