1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: Stephan Kulow <coolo@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kfilefiltercombo.h"
9#include "kfilefilter.h"
10#include "kfilefiltercombo_debug.h"
11
12#include <KLocalizedString>
13#include <QDebug>
14#include <QEvent>
15#include <QLineEdit>
16#include <QMimeDatabase>
17
18#include <config-kiofilewidgets.h>
19
20#include <algorithm>
21#include <utility>
22
23class KFileFilterComboPrivate
24{
25public:
26 explicit KFileFilterComboPrivate(KFileFilterCombo *qq)
27 : q(qq)
28 {
29 }
30
31 void slotFilterChanged();
32
33 KFileFilterCombo *const q;
34 // when we have more than 3 mimefilters and no default-filter,
35 // we don't show the comments of all mimefilters in one line,
36 // instead we show "All supported files". We have to translate
37 // that back to the list of mimefilters in currentFilter() tho.
38 bool m_hasAllSupportedFiles = false;
39 // true when setMimeFilter was called
40 bool m_isMimeFilter = false;
41 QString m_lastFilter;
42 KFileFilter m_defaultFilter = KFileFilter::fromFilterString(i18nc("Default mime type filter that shows all file types", "*|All Files")).first();
43
44 QList<KFileFilter> m_filters;
45 bool m_allTypes;
46};
47
48KFileFilterCombo::KFileFilterCombo(QWidget *parent)
49 : KComboBox(true, parent)
50 , d(new KFileFilterComboPrivate(this))
51{
52 setTrapReturnKey(true);
53 setInsertPolicy(QComboBox::NoInsert);
54 connect(sender: this, signal: &QComboBox::activated, context: this, slot: &KFileFilterCombo::filterChanged);
55 connect(sender: this, signal: &KComboBox::returnPressed, context: this, slot: &KFileFilterCombo::filterChanged);
56 connect(sender: this, signal: &KFileFilterCombo::filterChanged, context: this, slot: [this]() {
57 d->slotFilterChanged();
58 });
59 d->m_allTypes = false;
60}
61
62KFileFilterCombo::~KFileFilterCombo() = default;
63
64void KFileFilterCombo::setFilters(const QList<KFileFilter> &types, const KFileFilter &defaultFilter)
65{
66 clear();
67 d->m_filters.clear();
68 QString delim = QStringLiteral(", ");
69 d->m_hasAllSupportedFiles = false;
70 bool hasAllFilesFilter = false;
71 QMimeDatabase db;
72
73 if (types.isEmpty()) {
74 d->m_filters = {d->m_defaultFilter};
75 addItem(atext: d->m_defaultFilter.label());
76
77 d->m_lastFilter = currentText();
78 return;
79 }
80
81 d->m_allTypes = defaultFilter.isEmpty() && (types.count() > 1);
82
83 if (!types.isEmpty() && types.first().mimePatterns().isEmpty()) {
84 d->m_allTypes = false;
85 }
86
87 // If there's MIME types that have the same comment, we will show the extension
88 // in addition to the MIME type comment
89 QHash<QString, int> allTypeComments;
90 for (const KFileFilter &filter : types) {
91 allTypeComments[filter.label()] += 1;
92 }
93
94 for (const KFileFilter &filter : types) {
95 if (!filter.isValid()) {
96 continue;
97 }
98
99 const QStringList mimeTypes = filter.mimePatterns();
100
101 const bool isAllFileFilters = std::any_of(first: mimeTypes.cbegin(), last: mimeTypes.cend(), pred: [&db](const QString &mimeTypeName) {
102 const QMimeType type = db.mimeTypeForName(nameOrAlias: mimeTypeName);
103
104 if (!type.isValid()) {
105 qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << mimeTypeName << "is not a valid MIME type";
106 return false;
107 }
108
109 return type.name().startsWith(s: QLatin1String("all/")) || type.isDefault();
110 });
111
112 if (isAllFileFilters) {
113 hasAllFilesFilter = true;
114 continue;
115 }
116
117 if (allTypeComments.value(key: filter.label()) > 1) {
118 QStringList mimeSuffixes;
119
120 for (const QString &mimeTypeName : filter.mimePatterns()) {
121 const QMimeType type = db.mimeTypeForName(nameOrAlias: mimeTypeName);
122 mimeSuffixes << type.suffixes();
123 }
124
125 const QString label = i18nc("%1 is the mimetype name, %2 is the extensions", "%1 (%2)", filter.label(), mimeSuffixes.join(QLatin1String(", ")));
126 KFileFilter newFilter(label, filter.filePatterns(), filter.mimePatterns());
127
128 d->m_filters.append(t: newFilter);
129 addItem(atext: newFilter.label());
130 } else {
131 d->m_filters.append(t: filter);
132 addItem(atext: filter.label());
133 }
134
135 if (filter == defaultFilter) {
136 setCurrentIndex(count() - 1);
137 }
138 }
139
140 if (count() == 1) {
141 d->m_allTypes = false;
142 }
143
144 if (d->m_allTypes) {
145 QStringList allTypes;
146 for (const KFileFilter &filter : std::as_const(t&: d->m_filters)) {
147 allTypes << filter.mimePatterns().join(sep: QLatin1Char(' '));
148 }
149
150 KFileFilter allSupportedFilesFilter;
151
152 if (count() <= 3) { // show the MIME type comments of at max 3 types
153 QStringList allComments;
154 for (const KFileFilter &filter : std::as_const(t&: d->m_filters)) {
155 allComments << filter.label();
156 }
157
158 allSupportedFilesFilter = KFileFilter(allComments.join(sep: delim), {}, allTypes);
159 } else {
160 allSupportedFilesFilter = KFileFilter(i18n("All Supported Files"), {}, allTypes);
161 d->m_hasAllSupportedFiles = true;
162 }
163
164 insertItem(aindex: 0, atext: allSupportedFilesFilter.label());
165 d->m_filters.prepend(t: allSupportedFilesFilter);
166 setCurrentIndex(0);
167 }
168
169 if (hasAllFilesFilter) {
170 addItem(i18n("All Files"));
171 d->m_filters.append(t: KFileFilter(i18n("All Files"), {}, {QStringLiteral("application/octet-stream")}));
172 }
173
174 d->m_lastFilter = currentText();
175}
176
177KFileFilter KFileFilterCombo::currentFilter() const
178{
179 if (currentText() != itemText(index: currentIndex())) {
180 // The user edited the text
181
182 const QList<KFileFilter> filter = KFileFilter::fromFilterString(filterString: currentText());
183
184 if (!filter.isEmpty()) {
185 return filter.first();
186 } else {
187 return KFileFilter();
188 }
189 } else {
190 if (currentIndex() == -1) {
191 return KFileFilter();
192 }
193
194 return d->m_filters[currentIndex()];
195 }
196}
197
198bool KFileFilterCombo::showsAllTypes() const
199{
200 return d->m_allTypes;
201}
202
203QList<KFileFilter> KFileFilterCombo::filters() const
204{
205 return d->m_filters;
206}
207
208void KFileFilterCombo::setCurrentFilter(const KFileFilter &filter)
209{
210 auto it = std::find(first: d->m_filters.cbegin(), last: d->m_filters.cend(), val: filter);
211
212 if (it == d->m_filters.cend()) {
213 qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << "Could not find file filter";
214 setCurrentIndex(-1);
215 Q_EMIT filterChanged();
216 return;
217 }
218
219 setCurrentIndex(std::distance(first: d->m_filters.cbegin(), last: it));
220 Q_EMIT filterChanged();
221}
222
223void KFileFilterComboPrivate::slotFilterChanged()
224{
225 m_lastFilter = q->currentText();
226}
227
228bool KFileFilterCombo::eventFilter(QObject *o, QEvent *e)
229{
230 if (o == lineEdit() && e->type() == QEvent::FocusOut) {
231 if (currentText() != d->m_lastFilter) {
232 Q_EMIT filterChanged();
233 }
234 }
235
236 return KComboBox::eventFilter(watched: o, event: e);
237}
238
239void KFileFilterCombo::setDefaultFilter(const KFileFilter &filter)
240{
241 d->m_defaultFilter = filter;
242}
243
244KFileFilter KFileFilterCombo::defaultFilter() const
245{
246 return d->m_defaultFilter;
247}
248
249#include "moc_kfilefiltercombo.cpp"
250

source code of kio/src/filewidgets/kfilefiltercombo.cpp