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