1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Assistant of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qoptionswidget_p.h"
30 
31 #include <QtWidgets/QComboBox>
32 #include <QtWidgets/QItemDelegate>
33 #include <QtWidgets/QListWidget>
34 #include <QtWidgets/QVBoxLayout>
35 
36 QT_BEGIN_NAMESPACE
37 
38 class ListWidgetDelegate : public QItemDelegate
39 {
40 public:
ListWidgetDelegate(QWidget * w)41     ListWidgetDelegate(QWidget *w) : QItemDelegate(w), m_widget(w) {}
42 
isSeparator(const QModelIndex & index)43     static bool isSeparator(const QModelIndex &index) {
44         return index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator");
45     }
setSeparator(QListWidgetItem * item)46     static void setSeparator(QListWidgetItem *item) {
47         item->setData(Qt::AccessibleDescriptionRole, QString::fromLatin1("separator"));
48         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
49     }
50 
51 protected:
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const52     void paint(QPainter *painter,
53                const QStyleOptionViewItem &option,
54                const QModelIndex &index) const override {
55         if (isSeparator(index)) {
56             QRect rect = option.rect;
57             if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(option.widget))
58                 rect.setWidth(view->viewport()->width());
59             QStyleOption opt;
60             opt.rect = rect;
61             m_widget->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, m_widget);
62         } else {
63             QItemDelegate::paint(painter, option, index);
64         }
65     }
66 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const67     QSize sizeHint(const QStyleOptionViewItem &option,
68                    const QModelIndex &index) const override {
69         if (isSeparator(index)) {
70             int pm = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget);
71             return QSize(pm, pm);
72         }
73         return QItemDelegate::sizeHint(option, index);
74     }
75 private:
76     QWidget *m_widget;
77 };
78 
subtract(const QStringList & minuend,const QStringList & subtrahend)79 static QStringList subtract(const QStringList &minuend, const QStringList &subtrahend)
80 {
81     QStringList result = minuend;
82     for (const QString &str : subtrahend)
83         result.removeOne(str);
84     return result;
85 }
86 
QOptionsWidget(QWidget * parent)87 QOptionsWidget::QOptionsWidget(QWidget *parent)
88     : QWidget(parent)
89     , m_noOptionText(tr("No Option"))
90     , m_invalidOptionText(tr("Invalid Option"))
91 {
92     m_listWidget = new QListWidget(this);
93     m_listWidget->setItemDelegate(new ListWidgetDelegate(m_listWidget));
94     QVBoxLayout *layout = new QVBoxLayout(this);
95     layout->addWidget(m_listWidget);
96     layout->setContentsMargins(QMargins());
97 
98     connect(m_listWidget, &QListWidget::itemChanged, this, &QOptionsWidget::itemChanged);
99 }
100 
clear()101 void QOptionsWidget::clear()
102 {
103     setOptions(QStringList(), QStringList());
104 }
105 
setOptions(const QStringList & validOptions,const QStringList & selectedOptions)106 void QOptionsWidget::setOptions(const QStringList &validOptions,
107                                 const QStringList &selectedOptions)
108 {
109     m_listWidget->clear();
110     m_optionToItem.clear();
111     m_itemToOption.clear();
112 
113     m_validOptions = validOptions;
114     m_validOptions.removeDuplicates();
115     std::sort(m_validOptions.begin(), m_validOptions.end());
116 
117     m_selectedOptions = selectedOptions;
118     m_selectedOptions.removeDuplicates();
119     std::sort(m_selectedOptions.begin(), m_selectedOptions.end());
120 
121     m_invalidOptions = subtract(m_selectedOptions, m_validOptions);
122     const QStringList validSelectedOptions = subtract(m_selectedOptions, m_invalidOptions);
123     const QStringList validUnselectedOptions = subtract(m_validOptions, m_selectedOptions);
124 
125     for (const QString &option : validSelectedOptions)
126         appendItem(option, true, true);
127 
128     for (const QString &option : m_invalidOptions)
129         appendItem(option, false, true);
130 
131     if ((validSelectedOptions.count() + m_invalidOptions.count())
132             && validUnselectedOptions.count()) {
133         appendSeparator();
134     }
135 
136     for (const QString &option : validUnselectedOptions) {
137         appendItem(option, true, false);
138         if (option.isEmpty() && validUnselectedOptions.count() > 1) // special No Option item
139             appendSeparator();
140     }
141 }
142 
validOptions() const143 QStringList QOptionsWidget::validOptions() const
144 {
145     return m_validOptions;
146 }
147 
selectedOptions() const148 QStringList QOptionsWidget::selectedOptions() const
149 {
150     return m_selectedOptions;
151 }
152 
setNoOptionText(const QString & text)153 void QOptionsWidget::setNoOptionText(const QString &text)
154 {
155     if (m_noOptionText == text)
156         return;
157 
158     m_noOptionText = text;
159 
160     // update GUI
161     const auto itEnd = m_optionToItem.constEnd();
162     for (auto it = m_optionToItem.constBegin(); it != itEnd; ++it) {
163         const QString optionName = it.key();
164         if (optionName.isEmpty())
165             it.value()->setText(optionText(optionName, m_validOptions.contains(optionName)));
166     }
167 }
168 
setInvalidOptionText(const QString & text)169 void QOptionsWidget::setInvalidOptionText(const QString &text)
170 {
171     if (m_invalidOptionText == text)
172         return;
173 
174     m_invalidOptionText = text;
175 
176     // update GUI
177     for (const QString &option : m_invalidOptions)
178         m_optionToItem.value(option)->setText(optionText(option, false));
179 }
180 
optionText(const QString & optionName,bool valid) const181 QString QOptionsWidget::optionText(const QString &optionName, bool valid) const
182 {
183     QString text = optionName;
184     if (optionName.isEmpty())
185         text = QLatin1Char('[') + m_noOptionText + QLatin1Char(']');
186     if (!valid)
187         text += QLatin1String("\t[") + m_invalidOptionText + QLatin1Char(']');
188     return text;
189 }
190 
appendItem(const QString & optionName,bool valid,bool selected)191 QListWidgetItem *QOptionsWidget::appendItem(const QString &optionName, bool valid, bool selected)
192 {
193     QListWidgetItem *optionItem = new QListWidgetItem(optionText(optionName, valid), m_listWidget);
194     optionItem->setCheckState(selected ? Qt::Checked : Qt::Unchecked);
195     m_listWidget->insertItem(m_listWidget->count(), optionItem);
196     m_optionToItem[optionName] = optionItem;
197     m_itemToOption[optionItem] = optionName;
198     return optionItem;
199 }
200 
appendSeparator()201 void QOptionsWidget::appendSeparator()
202 {
203     QListWidgetItem *separatorItem = new QListWidgetItem(m_listWidget);
204     ListWidgetDelegate::setSeparator(separatorItem);
205     m_listWidget->insertItem(m_listWidget->count(), separatorItem);
206 }
207 
itemChanged(QListWidgetItem * item)208 void QOptionsWidget::itemChanged(QListWidgetItem *item)
209 {
210     const auto it = m_itemToOption.constFind(item);
211     if (it == m_itemToOption.constEnd())
212         return;
213 
214     const QString option = *it;
215 
216     if (item->checkState() == Qt::Checked && !m_selectedOptions.contains(option)) {
217         m_selectedOptions.append(option);
218         std::sort(m_selectedOptions.begin(), m_selectedOptions.end());
219     } else if (item->checkState() == Qt::Unchecked && m_selectedOptions.contains(option)) {
220         m_selectedOptions.removeOne(option);
221     } else {
222         return;
223     }
224 
225     emit optionSelectionChanged(m_selectedOptions);
226 }
227 
228 
229 QT_END_NAMESPACE
230