1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qhelpfilterdata.h" |
5 | #include "qhelpfiltersettings_p.h" |
6 | #include "qhelpfiltersettingswidget.h" |
7 | #include "ui_qhelpfiltersettingswidget.h" |
8 | #include "qfilternamedialog_p.h" |
9 | |
10 | #include <QtWidgets/QMessageBox> |
11 | #include <QtCore/QVersionNumber> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | static QStringList versionsToStringList(const QList<QVersionNumber> &versions) |
16 | { |
17 | QStringList versionList; |
18 | for (const QVersionNumber &version : versions) |
19 | versionList.append(t: version.isNull() ? QString() : version.toString()); |
20 | return versionList; |
21 | } |
22 | |
23 | static QList<QVersionNumber> stringListToVersions(const QStringList &versionList) |
24 | { |
25 | QList<QVersionNumber> versions; |
26 | for (const QString &versionString : versionList) |
27 | versions.append(t: QVersionNumber::fromString(string: versionString)); |
28 | return versions; |
29 | } |
30 | |
31 | class QHelpFilterSettingsWidgetPrivate |
32 | { |
33 | QHelpFilterSettingsWidget *q_ptr; |
34 | Q_DECLARE_PUBLIC(QHelpFilterSettingsWidget) |
35 | public: |
36 | QHelpFilterSettingsWidgetPrivate() = default; |
37 | |
38 | QHelpFilterSettings filterSettings() const; |
39 | void setFilterSettings(const QHelpFilterSettings &settings); |
40 | |
41 | void updateCurrentFilter(); |
42 | void componentsChanged(const QStringList &components); |
43 | void versionsChanged(const QStringList &versions); |
44 | void addFilterClicked(); |
45 | void renameFilterClicked(); |
46 | void removeFilterClicked(); |
47 | void addFilter(const QString &filterName, |
48 | const QHelpFilterData &filterData = QHelpFilterData()); |
49 | void removeFilter(const QString &filterName); |
50 | QString getUniqueFilterName(const QString &windowTitle, |
51 | const QString &initialFilterName); |
52 | QString suggestedNewFilterName(const QString &initialFilterName) const; |
53 | |
54 | QMap<QString, QListWidgetItem *> m_filterToItem; |
55 | QHash<QListWidgetItem *, QString> m_itemToFilter; |
56 | |
57 | Ui::QHelpFilterSettingsWidget m_ui; |
58 | QStringList m_components; |
59 | QList<QVersionNumber> m_versions; |
60 | QHelpFilterSettings m_filterSettings; |
61 | }; |
62 | |
63 | void QHelpFilterSettingsWidgetPrivate::setFilterSettings(const QHelpFilterSettings &settings) |
64 | { |
65 | QString currentFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
66 | if (currentFilter.isEmpty()) { |
67 | if (!m_filterSettings.currentFilter().isEmpty()) |
68 | currentFilter = m_filterSettings.currentFilter(); |
69 | else |
70 | currentFilter = settings.currentFilter(); |
71 | } |
72 | |
73 | m_filterSettings = settings; |
74 | |
75 | m_ui.filterWidget->clear(); |
76 | m_ui.componentWidget->clear(); |
77 | m_ui.versionWidget->clear(); |
78 | m_itemToFilter.clear(); |
79 | m_filterToItem.clear(); |
80 | |
81 | for (const QString &filterName : m_filterSettings.filterNames()) { |
82 | QListWidgetItem *item = new QListWidgetItem(filterName); |
83 | m_ui.filterWidget->addItem(item); |
84 | m_itemToFilter.insert(item, filterName); |
85 | m_filterToItem.insert(filterName, item); |
86 | if (filterName == currentFilter) |
87 | m_ui.filterWidget->setCurrentItem(item); |
88 | } |
89 | |
90 | if (!m_ui.filterWidget->currentItem() && !m_filterToItem.isEmpty()) |
91 | m_ui.filterWidget->setCurrentItem(m_filterToItem.first()); |
92 | |
93 | updateCurrentFilter(); |
94 | } |
95 | |
96 | QHelpFilterSettings QHelpFilterSettingsWidgetPrivate::filterSettings() const |
97 | { |
98 | return m_filterSettings; |
99 | } |
100 | |
101 | void QHelpFilterSettingsWidgetPrivate::updateCurrentFilter() |
102 | { |
103 | const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
104 | |
105 | const bool filterSelected = !currentFilter.isEmpty(); |
106 | m_ui.componentWidget->setEnabled(filterSelected); |
107 | m_ui.versionWidget->setEnabled(filterSelected); |
108 | m_ui.renameButton->setEnabled(filterSelected); |
109 | m_ui.removeButton->setEnabled(filterSelected); |
110 | |
111 | m_ui.componentWidget->setOptions(m_components, |
112 | m_filterSettings.filterData(currentFilter).components()); |
113 | m_ui.versionWidget->setOptions(versionsToStringList(m_versions), |
114 | versionsToStringList(m_filterSettings.filterData(currentFilter).versions())); |
115 | } |
116 | |
117 | void QHelpFilterSettingsWidgetPrivate::componentsChanged(const QStringList &components) |
118 | { |
119 | const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
120 | if (currentFilter.isEmpty()) |
121 | return; |
122 | |
123 | QHelpFilterData filterData = m_filterSettings.filterData(filterName: currentFilter); |
124 | filterData.setComponents(components); |
125 | m_filterSettings.setFilter(filterName: currentFilter, filterData); |
126 | } |
127 | |
128 | void QHelpFilterSettingsWidgetPrivate::versionsChanged(const QStringList &versions) |
129 | { |
130 | const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
131 | if (currentFilter.isEmpty()) |
132 | return; |
133 | |
134 | QHelpFilterData filterData = m_filterSettings.filterData(filterName: currentFilter); |
135 | filterData.setVersions(stringListToVersions(versionList: versions)); |
136 | m_filterSettings.setFilter(filterName: currentFilter, filterData); |
137 | } |
138 | |
139 | void QHelpFilterSettingsWidgetPrivate::addFilterClicked() |
140 | { |
141 | const QString newFilterName = getUniqueFilterName(windowTitle: QHelpFilterSettingsWidget::tr(s: "Add Filter" ), |
142 | initialFilterName: suggestedNewFilterName(initialFilterName: QHelpFilterSettingsWidget::tr(s: "New Filter" ))); |
143 | if (newFilterName.isEmpty()) |
144 | return; |
145 | |
146 | addFilter(filterName: newFilterName); |
147 | } |
148 | |
149 | void QHelpFilterSettingsWidgetPrivate::renameFilterClicked() |
150 | { |
151 | const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
152 | if (currentFilter.isEmpty()) |
153 | return; |
154 | |
155 | const QString newFilterName = getUniqueFilterName(windowTitle: QHelpFilterSettingsWidget::tr(s: "Rename Filter" ), initialFilterName: currentFilter); |
156 | if (newFilterName.isEmpty()) |
157 | return; |
158 | |
159 | const QHelpFilterData oldFilterData = m_filterSettings.filterData(filterName: currentFilter); |
160 | removeFilter(filterName: currentFilter); |
161 | addFilter(filterName: newFilterName, filterData: oldFilterData); |
162 | |
163 | if (m_filterSettings.currentFilter() == currentFilter) |
164 | m_filterSettings.setCurrentFilter(newFilterName); |
165 | } |
166 | |
167 | void QHelpFilterSettingsWidgetPrivate::removeFilterClicked() |
168 | { |
169 | Q_Q(QHelpFilterSettingsWidget); |
170 | |
171 | const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); |
172 | if (currentFilter.isEmpty()) |
173 | return; |
174 | |
175 | if (QMessageBox::question(parent: q, title: QHelpFilterSettingsWidget::tr(s: "Remove Filter" ), |
176 | text: QHelpFilterSettingsWidget::tr(s: "Are you sure you want to remove the \"%1\" filter?" ) |
177 | .arg(a: currentFilter), |
178 | buttons: QMessageBox::Yes | QMessageBox::No) |
179 | != QMessageBox::Yes) { |
180 | return; |
181 | } |
182 | |
183 | removeFilter(filterName: currentFilter); |
184 | |
185 | if (m_filterSettings.currentFilter() == currentFilter) |
186 | m_filterSettings.setCurrentFilter(QString()); |
187 | } |
188 | |
189 | void QHelpFilterSettingsWidgetPrivate::addFilter(const QString &filterName, |
190 | const QHelpFilterData &filterData) |
191 | { |
192 | QListWidgetItem *item = new QListWidgetItem(filterName); |
193 | m_filterSettings.setFilter(filterName, filterData); |
194 | m_filterToItem.insert(filterName, item); |
195 | m_itemToFilter.insert(item, filterName); |
196 | m_ui.filterWidget->insertItem(m_filterToItem.keys().indexOf(filterName), item); |
197 | |
198 | m_ui.filterWidget->setCurrentItem(item); |
199 | updateCurrentFilter(); |
200 | } |
201 | |
202 | void QHelpFilterSettingsWidgetPrivate::removeFilter(const QString &filterName) |
203 | { |
204 | QListWidgetItem *item = m_filterToItem.value(filterName); |
205 | m_itemToFilter.remove(item); |
206 | m_filterToItem.remove(filterName); |
207 | delete item; |
208 | |
209 | m_filterSettings.removeFilter(filterName); |
210 | } |
211 | |
212 | QString QHelpFilterSettingsWidgetPrivate::getUniqueFilterName(const QString &windowTitle, |
213 | const QString &initialFilterName) |
214 | { |
215 | Q_Q(QHelpFilterSettingsWidget); |
216 | |
217 | QString newFilterName = initialFilterName; |
218 | while (1) { |
219 | QFilterNameDialog dialog(q); |
220 | dialog.setWindowTitle(windowTitle); |
221 | dialog.setFilterName(newFilterName); |
222 | if (dialog.exec() == QDialog::Rejected) |
223 | return QString(); |
224 | |
225 | newFilterName = dialog.filterName(); |
226 | if (!m_filterToItem.contains(newFilterName)) |
227 | break; |
228 | |
229 | if (QMessageBox::warning(parent: q, title: QHelpFilterSettingsWidget::tr(s: "Filter Exists" ), |
230 | text: QHelpFilterSettingsWidget::tr(s: "The filter \"%1\" already exists." ) |
231 | .arg(a: newFilterName), |
232 | buttons: QMessageBox::Retry | QMessageBox::Cancel) |
233 | == QMessageBox::Cancel) { |
234 | return QString(); |
235 | } |
236 | } |
237 | |
238 | return newFilterName; |
239 | } |
240 | |
241 | QString QHelpFilterSettingsWidgetPrivate::suggestedNewFilterName(const QString &initialFilterName) const |
242 | { |
243 | QString newFilterName = initialFilterName; |
244 | |
245 | int counter = 1; |
246 | while (m_filterToItem.contains(newFilterName)) { |
247 | newFilterName = initialFilterName + QLatin1Char(' ') |
248 | + QString::number(++counter); |
249 | } |
250 | |
251 | return newFilterName; |
252 | } |
253 | |
254 | /*! |
255 | \class QHelpFilterSettingsWidget |
256 | \inmodule QtHelp |
257 | \since 5.15 |
258 | \brief The QHelpFilterSettingsWidget class provides a widget that allows |
259 | for creating, editing and removing filters. |
260 | |
261 | The instance of QHelpFilterSettingsWidget may be a part of |
262 | a preferences dialog. Before showing the dialog, \l setAvailableComponents() |
263 | and \l setAvailableVersions() should be called, otherwise the filter |
264 | settings widget will only offer a creation of empty filters, |
265 | which wouldn't be useful. In addition, \l readSettings should also |
266 | be called to fill up the filter settings widget with the list of filters |
267 | already stored in the filter engine. The creation of new filters, |
268 | modifications to existing filters and removal of unneeded filters are |
269 | handled by the widget automatically. If you want to store the current |
270 | state of the widget and apply it to the filter engine e.g. after |
271 | the user clicked the apply button - call \l applySettings(). |
272 | */ |
273 | |
274 | /*! |
275 | Constructs a filter settings widget with \a parent as parent widget. |
276 | */ |
277 | QHelpFilterSettingsWidget::QHelpFilterSettingsWidget(QWidget *parent) |
278 | : QWidget(parent) |
279 | , d_ptr(new QHelpFilterSettingsWidgetPrivate()) |
280 | { |
281 | Q_D(QHelpFilterSettingsWidget); |
282 | d->q_ptr = this; |
283 | d->m_ui.setupUi(this); |
284 | |
285 | // TODO: make resources configurable |
286 | QString resourcePath = QLatin1String(":/qt-project.org/assistant/images/" ); |
287 | #ifdef Q_OS_MACOS |
288 | resourcePath.append(QLatin1String("mac" )); |
289 | #else |
290 | resourcePath.append(s: QLatin1String("win" )); |
291 | #endif |
292 | d->m_ui.addButton->setIcon(QIcon(resourcePath + QLatin1String("/plus.png" ))); |
293 | d->m_ui.removeButton->setIcon(QIcon(resourcePath + QLatin1String("/minus.png" ))); |
294 | |
295 | connect(d->m_ui.componentWidget, &QOptionsWidget::optionSelectionChanged, |
296 | [this](const QStringList &options) { |
297 | Q_D(QHelpFilterSettingsWidget); |
298 | d->componentsChanged(options); |
299 | }); |
300 | connect(d->m_ui.versionWidget, &QOptionsWidget::optionSelectionChanged, |
301 | [this](const QStringList &options) { |
302 | Q_D(QHelpFilterSettingsWidget); |
303 | d->versionsChanged(options); |
304 | }); |
305 | connect(d->m_ui.filterWidget, &QListWidget::currentItemChanged, |
306 | this, [this](QListWidgetItem *) { |
307 | Q_D(QHelpFilterSettingsWidget); |
308 | d->updateCurrentFilter(); |
309 | }); |
310 | connect(d->m_ui.filterWidget, &QListWidget::itemDoubleClicked, |
311 | [this](QListWidgetItem *) { |
312 | Q_D(QHelpFilterSettingsWidget); |
313 | d->renameFilterClicked(); |
314 | }); |
315 | |
316 | // TODO: repeat these actions on context menu |
317 | connect(d->m_ui.addButton, &QAbstractButton::clicked, |
318 | [this]() { |
319 | Q_D(QHelpFilterSettingsWidget); |
320 | d->addFilterClicked(); |
321 | }); |
322 | connect(d->m_ui.renameButton, &QAbstractButton::clicked, |
323 | [this]() { |
324 | Q_D(QHelpFilterSettingsWidget); |
325 | d->renameFilterClicked(); |
326 | }); |
327 | connect(d->m_ui.removeButton, &QAbstractButton::clicked, |
328 | [this]() { |
329 | Q_D(QHelpFilterSettingsWidget); |
330 | d->removeFilterClicked(); |
331 | }); |
332 | |
333 | d->m_ui.componentWidget->setNoOptionText(tr(s: "No Component" )); |
334 | d->m_ui.componentWidget->setInvalidOptionText(tr(s: "Invalid Component" )); |
335 | d->m_ui.versionWidget->setNoOptionText(tr(s: "No Version" )); |
336 | d->m_ui.versionWidget->setInvalidOptionText(tr(s: "Invalid Version" )); |
337 | } |
338 | |
339 | /*! |
340 | Destroys the filter settings widget. |
341 | */ |
342 | QHelpFilterSettingsWidget::~QHelpFilterSettingsWidget() = default; |
343 | |
344 | /*! |
345 | Sets the list of all available components to \a components. |
346 | \sa QHelpFilterEngine::availableComponents() |
347 | */ |
348 | void QHelpFilterSettingsWidget::setAvailableComponents(const QStringList &components) |
349 | { |
350 | Q_D(QHelpFilterSettingsWidget); |
351 | d->m_components = components; |
352 | d->updateCurrentFilter(); |
353 | } |
354 | |
355 | /*! |
356 | Sets the list of all available version numbers to \a versions. |
357 | \sa QHelpFilterEngine::availableVersions() |
358 | */ |
359 | void QHelpFilterSettingsWidget::setAvailableVersions(const QList<QVersionNumber> &versions) |
360 | { |
361 | Q_D(QHelpFilterSettingsWidget); |
362 | d->m_versions = versions; |
363 | d->updateCurrentFilter(); |
364 | } |
365 | |
366 | /*! |
367 | Reads the filter settings stored inside \a filterEngine and sets up |
368 | this filter settings widget accordingly. |
369 | */ |
370 | void QHelpFilterSettingsWidget::readSettings(const QHelpFilterEngine *filterEngine) |
371 | { |
372 | Q_D(QHelpFilterSettingsWidget); |
373 | const QHelpFilterSettings settings = QHelpFilterSettings::readSettings(filterEngine); |
374 | d->setFilterSettings(settings); |
375 | } |
376 | |
377 | /*! |
378 | Writes the filter settings, currently presented in this filter settings |
379 | widget, to the \a filterEngine. The old settings stored in the filter |
380 | engine will be overwritten. Returns \c true on success. |
381 | */ |
382 | bool QHelpFilterSettingsWidget::applySettings(QHelpFilterEngine *filterEngine) const |
383 | { |
384 | Q_D(const QHelpFilterSettingsWidget); |
385 | return QHelpFilterSettings::applySettings(filterEngine, settings: d->filterSettings()); |
386 | } |
387 | |
388 | QT_END_NAMESPACE |
389 | |