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
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::StringLiterals;
17
18class QHelpFilterSettings final
19{
20public:
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
35private:
36 QMap<QString, QHelpFilterData> m_filterToData;
37 QString m_currentFilter;
38};
39
40static 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
52static 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
65static 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 &currentFilter = 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
92static 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
100static 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
108class QHelpFilterSettingsWidgetPrivate
109{
110 QHelpFilterSettingsWidget *q_ptr;
111 Q_DECLARE_PUBLIC(QHelpFilterSettingsWidget) // TODO: remove Q_DECLARE_PUBLIC
112public:
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
138void 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
173void QHelpFilterSettingsWidgetPrivate::updateCurrentFilter()
174{
175 const QString &currentFilter = 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
189void QHelpFilterSettingsWidgetPrivate::componentsChanged(const QStringList &components)
190{
191 const QString &currentFilter = 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
200void QHelpFilterSettingsWidgetPrivate::versionsChanged(const QStringList &versions)
201{
202 const QString &currentFilter = 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
211void 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
221void QHelpFilterSettingsWidgetPrivate::renameFilterClicked()
222{
223 const QString &currentFilter = 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
239void QHelpFilterSettingsWidgetPrivate::removeFilterClicked()
240{
241 Q_Q(QHelpFilterSettingsWidget);
242
243 const QString &currentFilter = 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
259void 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
272void 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
281QString 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
307QString 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*/
339QHelpFilterSettingsWidget::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*/
401QHelpFilterSettingsWidget::~QHelpFilterSettingsWidget() = default;
402
403/*!
404 Sets the list of all available components to \a components.
405 \sa QHelpFilterEngine::availableComponents()
406*/
407void 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*/
418void 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*/
429void 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*/
441bool QHelpFilterSettingsWidget::applySettings(QHelpFilterEngine *filterEngine) const
442{
443 Q_D(const QHelpFilterSettingsWidget);
444 return applySettingsHelper(filterEngine, settings: d->filterSettings());
445}
446
447QT_END_NAMESPACE
448

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qttools/src/assistant/help/qhelpfiltersettingswidget.cpp