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

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