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: |
41 | ListWidgetDelegate(QWidget *w) : QItemDelegate(w), m_widget(w) {} |
42 | |
43 | static bool isSeparator(const QModelIndex &index) { |
44 | return index.data(arole: Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator" ); |
45 | } |
46 | static void setSeparator(QListWidgetItem *item) { |
47 | item->setData(role: Qt::AccessibleDescriptionRole, value: QString::fromLatin1(str: "separator" )); |
48 | item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); |
49 | } |
50 | |
51 | protected: |
52 | 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*>(object: option.widget)) |
58 | rect.setWidth(view->viewport()->width()); |
59 | QStyleOption opt; |
60 | opt.rect = rect; |
61 | m_widget->style()->drawPrimitive(pe: QStyle::PE_IndicatorToolBarSeparator, opt: &opt, p: painter, w: m_widget); |
62 | } else { |
63 | QItemDelegate::paint(painter, option, index); |
64 | } |
65 | } |
66 | |
67 | QSize sizeHint(const QStyleOptionViewItem &option, |
68 | const QModelIndex &index) const override { |
69 | if (isSeparator(index)) { |
70 | int pm = m_widget->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: nullptr, widget: m_widget); |
71 | return QSize(pm, pm); |
72 | } |
73 | return QItemDelegate::sizeHint(option, index); |
74 | } |
75 | private: |
76 | QWidget *m_widget; |
77 | }; |
78 | |
79 | static QStringList subtract(const QStringList &minuend, const QStringList &subtrahend) |
80 | { |
81 | QStringList result = minuend; |
82 | for (const QString &str : subtrahend) |
83 | result.removeOne(t: str); |
84 | return result; |
85 | } |
86 | |
87 | QOptionsWidget::QOptionsWidget(QWidget *parent) |
88 | : QWidget(parent) |
89 | , m_noOptionText(tr(s: "No Option" )) |
90 | , m_invalidOptionText(tr(s: "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(sender: m_listWidget, signal: &QListWidget::itemChanged, receiver: this, slot: &QOptionsWidget::itemChanged); |
99 | } |
100 | |
101 | void QOptionsWidget::clear() |
102 | { |
103 | setOptions(validOptions: QStringList(), selectedOptions: QStringList()); |
104 | } |
105 | |
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(first: m_validOptions.begin(), last: m_validOptions.end()); |
116 | |
117 | m_selectedOptions = selectedOptions; |
118 | m_selectedOptions.removeDuplicates(); |
119 | std::sort(first: m_selectedOptions.begin(), last: m_selectedOptions.end()); |
120 | |
121 | m_invalidOptions = subtract(minuend: m_selectedOptions, subtrahend: m_validOptions); |
122 | const QStringList validSelectedOptions = subtract(minuend: m_selectedOptions, subtrahend: m_invalidOptions); |
123 | const QStringList validUnselectedOptions = subtract(minuend: m_validOptions, subtrahend: m_selectedOptions); |
124 | |
125 | for (const QString &option : validSelectedOptions) |
126 | appendItem(optionName: option, valid: true, selected: true); |
127 | |
128 | for (const QString &option : m_invalidOptions) |
129 | appendItem(optionName: option, valid: false, selected: true); |
130 | |
131 | if ((validSelectedOptions.count() + m_invalidOptions.count()) |
132 | && validUnselectedOptions.count()) { |
133 | appendSeparator(); |
134 | } |
135 | |
136 | for (const QString &option : validUnselectedOptions) { |
137 | appendItem(optionName: option, valid: true, selected: false); |
138 | if (option.isEmpty() && validUnselectedOptions.count() > 1) // special No Option item |
139 | appendSeparator(); |
140 | } |
141 | } |
142 | |
143 | QStringList QOptionsWidget::validOptions() const |
144 | { |
145 | return m_validOptions; |
146 | } |
147 | |
148 | QStringList QOptionsWidget::selectedOptions() const |
149 | { |
150 | return m_selectedOptions; |
151 | } |
152 | |
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, valid: m_validOptions.contains(str: optionName))); |
166 | } |
167 | } |
168 | |
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(akey: option)->setText(optionText(optionName: option, valid: false)); |
179 | } |
180 | |
181 | 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 | |
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(row: m_listWidget->count(), item: optionItem); |
196 | m_optionToItem[optionName] = optionItem; |
197 | m_itemToOption[optionItem] = optionName; |
198 | return optionItem; |
199 | } |
200 | |
201 | void QOptionsWidget::appendSeparator() |
202 | { |
203 | QListWidgetItem *separatorItem = new QListWidgetItem(m_listWidget); |
204 | ListWidgetDelegate::setSeparator(separatorItem); |
205 | m_listWidget->insertItem(row: m_listWidget->count(), item: separatorItem); |
206 | } |
207 | |
208 | void QOptionsWidget::itemChanged(QListWidgetItem *item) |
209 | { |
210 | const auto it = m_itemToOption.constFind(akey: 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(str: option)) { |
217 | m_selectedOptions.append(t: option); |
218 | std::sort(first: m_selectedOptions.begin(), last: m_selectedOptions.end()); |
219 | } else if (item->checkState() == Qt::Unchecked && m_selectedOptions.contains(str: option)) { |
220 | m_selectedOptions.removeOne(t: option); |
221 | } else { |
222 | return; |
223 | } |
224 | |
225 | emit optionSelectionChanged(options: m_selectedOptions); |
226 | } |
227 | |
228 | |
229 | QT_END_NAMESPACE |
230 | |