Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qoptionswidget_p.h"
++
++ #include <QtWidgets/qitemdelegate.h>
++ #include <QtWidgets/qlayout.h>
++ #include <QtWidgets/qlistwidget.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ class ListWidgetDelegate : public QItemDelegate
++ {
++ public:
++ ListWidgetDelegate(QWidget *w) : QItemDelegate(w), m_widget(w) {}
++
++ static bool isSeparator(const QModelIndex &index) {
++ return index.data(Qt::AccessibleDescriptionRole).toString() == "separator"_L1;
++ }
++ static void setSeparator(QListWidgetItem *item) {
++ item->setData(Qt::AccessibleDescriptionRole, QString::fromLatin1("separator"));
++ item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
++ }
++
++ protected:
++ void paint(QPainter *painter, const QStyleOptionViewItem &option,
++ const QModelIndex &index) const override
++ {
++ if (isSeparator(index)) {
++ QRect rect = option.rect;
++ if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(option.widget))
++ rect.setWidth(view->viewport()->width());
++ QStyleOption opt;
++ opt.rect = rect;
++ m_widget->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter,
++ m_widget);
++ } else {
++ QItemDelegate::paint(painter, option, index);
++ }
++ }
++
++ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
++ {
++ if (isSeparator(index)) {
++ int pm = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget);
++ return {pm, pm};
++ }
++ return QItemDelegate::sizeHint(option, index);
++ }
++
++ private:
++ QWidget *m_widget;
++ };
++
++ static QStringList subtract(const QStringList &minuend, const QStringList &subtrahend)
++ {
++ QStringList result = minuend;
++ for (const QString &str : subtrahend)
++ result.removeOne(str);
++ return result;
++ }
++
++ QOptionsWidget::QOptionsWidget(QWidget *parent)
++ : QWidget(parent)
++ , m_noOptionText(tr("No Option"))
++ , m_invalidOptionText(tr("Invalid Option"))
++ {
++ m_listWidget = new QListWidget(this);
++ m_listWidget->setItemDelegate(new ListWidgetDelegate(m_listWidget));
++ QVBoxLayout *layout = new QVBoxLayout(this);
++ layout->addWidget(m_listWidget);
++ layout->setContentsMargins({});
++ connect(m_listWidget, &QListWidget::itemChanged, this, &QOptionsWidget::itemChanged);
++ }
++
++ void QOptionsWidget::setOptions(const QStringList &validOptions, const QStringList &selectedOptions)
++ {
++ m_listWidget->clear();
++ m_optionToItem.clear();
++ m_itemToOption.clear();
++
++ m_validOptions = validOptions;
++ m_validOptions.removeDuplicates();
++ std::sort(m_validOptions.begin(), m_validOptions.end());
++
++ m_selectedOptions = selectedOptions;
++ m_selectedOptions.removeDuplicates();
++ std::sort(m_selectedOptions.begin(), m_selectedOptions.end());
++
++ m_invalidOptions = subtract(m_selectedOptions, m_validOptions);
++ const QStringList validSelectedOptions = subtract(m_selectedOptions, m_invalidOptions);
++ const QStringList validUnselectedOptions = subtract(m_validOptions, m_selectedOptions);
++
++ for (const QString &option : validSelectedOptions)
++ appendItem(option, true, true);
++
++ for (const QString &option : std::as_const(m_invalidOptions))
++ appendItem(option, false, true);
++
++ if ((validSelectedOptions.size() + m_invalidOptions.size()) && validUnselectedOptions.size())
++ appendSeparator();
++
++ for (const QString &option : validUnselectedOptions) {
++ appendItem(option, true, false);
++ if (option.isEmpty() && validUnselectedOptions.size() > 1) // special No Option item
++ appendSeparator();
++ }
++ }
++
++ void QOptionsWidget::setNoOptionText(const QString &text)
++ {
++ if (m_noOptionText == text)
++ return;
++
++ m_noOptionText = text;
++
++ // update GUI
++ const auto itEnd = m_optionToItem.constEnd();
++ for (auto it = m_optionToItem.constBegin(); it != itEnd; ++it) {
++ const QString optionName = it.key();
++ if (optionName.isEmpty())
++ it.value()->setText(optionText(optionName, m_validOptions.contains(optionName)));
++ }
++ }
++
++ void QOptionsWidget::setInvalidOptionText(const QString &text)
++ {
++ if (m_invalidOptionText == text)
++ return;
++
++ m_invalidOptionText = text;
++
++ // update GUI
++ for (const QString &option : std::as_const(m_invalidOptions))
++ m_optionToItem.value(option)->setText(optionText(option, false));
++ }
++
++ QString QOptionsWidget::optionText(const QString &optionName, bool valid) const
++ {
++ QString text = optionName;
++ if (optionName.isEmpty())
++ text = u'[' + m_noOptionText + u']';
++ if (!valid)
++ text += "\t["_L1 + m_invalidOptionText + u']';
++ return text;
++ }
++
++ QListWidgetItem *QOptionsWidget::appendItem(const QString &optionName, bool valid, bool selected)
++ {
++ QListWidgetItem *optionItem = new QListWidgetItem(optionText(optionName, valid), m_listWidget);
++ optionItem->setCheckState(selected ? Qt::Checked : Qt::Unchecked);
++ m_listWidget->addItem(optionItem);
++ m_optionToItem[optionName] = optionItem;
++ m_itemToOption[optionItem] = optionName;
++ return optionItem;
++ }
++
++ void QOptionsWidget::appendSeparator()
++ {
++ QListWidgetItem *separatorItem = new QListWidgetItem(m_listWidget);
++ ListWidgetDelegate::setSeparator(separatorItem);
++ m_listWidget->addItem(separatorItem);
++ }
++
++ void QOptionsWidget::itemChanged(QListWidgetItem *item)
++ {
++ const auto it = m_itemToOption.constFind(item);
++ if (it == m_itemToOption.constEnd())
++ return;
++
++ const QString option = *it;
++
++ if (item->checkState() == Qt::Checked && !m_selectedOptions.contains(option)) {
++ m_selectedOptions.append(option);
++ std::sort(m_selectedOptions.begin(), m_selectedOptions.end());
++ } else if (item->checkState() == Qt::Unchecked && m_selectedOptions.contains(option)) {
++ m_selectedOptions.removeOne(option);
++ } else {
++ return;
++ }
++ emit optionSelectionChanged(m_selectedOptions);
++ }
++
++ QT_END_NAMESPACE