File indexing completed on 2024-12-01 06:45:10

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: Stephan Kulow <coolo@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kfilefiltercombo.h"
0009 #include "kfilefilter.h"
0010 #include "kfilefiltercombo_debug.h"
0011 
0012 #include <KLocalizedString>
0013 #include <QDebug>
0014 #include <QEvent>
0015 #include <QLineEdit>
0016 #include <QMimeDatabase>
0017 
0018 #include <config-kiofilewidgets.h>
0019 
0020 #include <algorithm>
0021 #include <utility>
0022 
0023 class KFileFilterComboPrivate
0024 {
0025 public:
0026     explicit KFileFilterComboPrivate(KFileFilterCombo *qq)
0027         : q(qq)
0028     {
0029     }
0030 
0031     void slotFilterChanged();
0032 
0033     KFileFilterCombo *const q;
0034     // when we have more than 3 mimefilters and no default-filter,
0035     // we don't show the comments of all mimefilters in one line,
0036     // instead we show "All supported files". We have to translate
0037     // that back to the list of mimefilters in currentFilter() tho.
0038     bool m_hasAllSupportedFiles = false;
0039     // true when setMimeFilter was called
0040     bool m_isMimeFilter = false;
0041     QString m_lastFilter;
0042     KFileFilter m_defaultFilter = KFileFilter::fromFilterString(i18nc("Default mime type filter that shows all file types", "*|All Files")).first();
0043 
0044     QList<KFileFilter> m_filters;
0045     bool m_allTypes;
0046 };
0047 
0048 KFileFilterCombo::KFileFilterCombo(QWidget *parent)
0049     : KComboBox(true, parent)
0050     , d(new KFileFilterComboPrivate(this))
0051 {
0052     setTrapReturnKey(true);
0053     setInsertPolicy(QComboBox::NoInsert);
0054     connect(this, &QComboBox::activated, this, &KFileFilterCombo::filterChanged);
0055     connect(this, &KComboBox::returnPressed, this, &KFileFilterCombo::filterChanged);
0056     connect(this, &KFileFilterCombo::filterChanged, this, [this]() {
0057         d->slotFilterChanged();
0058     });
0059     d->m_allTypes = false;
0060 }
0061 
0062 KFileFilterCombo::~KFileFilterCombo() = default;
0063 
0064 void KFileFilterCombo::setFilters(const QList<KFileFilter> &types, const KFileFilter &defaultFilter)
0065 {
0066     clear();
0067     d->m_filters.clear();
0068     QString delim = QStringLiteral(", ");
0069     d->m_hasAllSupportedFiles = false;
0070     bool hasAllFilesFilter = false;
0071     QMimeDatabase db;
0072 
0073     if (types.isEmpty()) {
0074         d->m_filters = {d->m_defaultFilter};
0075         addItem(d->m_defaultFilter.label());
0076 
0077         d->m_lastFilter = currentText();
0078         return;
0079     }
0080 
0081     d->m_allTypes = defaultFilter.isEmpty() && (types.count() > 1);
0082 
0083     if (!types.isEmpty() && types.first().mimePatterns().isEmpty()) {
0084         d->m_allTypes = false;
0085     }
0086 
0087     // If there's MIME types that have the same comment, we will show the extension
0088     // in addition to the MIME type comment
0089     QHash<QString, int> allTypeComments;
0090     for (const KFileFilter &filter : types) {
0091         allTypeComments[filter.label()] += 1;
0092     }
0093 
0094     for (const KFileFilter &filter : types) {
0095         if (!filter.isValid()) {
0096             continue;
0097         }
0098 
0099         const QStringList mimeTypes = filter.mimePatterns();
0100 
0101         const bool isAllFileFilters = std::any_of(mimeTypes.cbegin(), mimeTypes.cend(), [&db](const QString &mimeTypeName) {
0102             const QMimeType type = db.mimeTypeForName(mimeTypeName);
0103 
0104             if (!type.isValid()) {
0105                 qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << mimeTypeName << "is not a valid MIME type";
0106                 return false;
0107             }
0108 
0109             return type.name().startsWith(QLatin1String("all/")) || type.isDefault();
0110         });
0111 
0112         if (isAllFileFilters) {
0113             hasAllFilesFilter = true;
0114             continue;
0115         }
0116 
0117         if (allTypeComments.value(filter.label()) > 1) {
0118             QStringList mimeSuffixes;
0119 
0120             for (const QString &mimeTypeName : filter.mimePatterns()) {
0121                 const QMimeType type = db.mimeTypeForName(mimeTypeName);
0122                 mimeSuffixes << type.suffixes();
0123             }
0124 
0125             const QString label = i18nc("%1 is the mimetype name, %2 is the extensions", "%1 (%2)", filter.label(), mimeSuffixes.join(QLatin1String(", ")));
0126             KFileFilter newFilter(label, filter.filePatterns(), filter.mimePatterns());
0127 
0128             d->m_filters.append(newFilter);
0129             addItem(newFilter.label());
0130         } else {
0131             d->m_filters.append(filter);
0132             addItem(filter.label());
0133         }
0134 
0135         if (filter == defaultFilter) {
0136             setCurrentIndex(count() - 1);
0137         }
0138     }
0139 
0140     if (count() == 1) {
0141         d->m_allTypes = false;
0142     }
0143 
0144     if (d->m_allTypes) {
0145         QStringList allTypes;
0146         for (const KFileFilter &filter : std::as_const(d->m_filters)) {
0147             allTypes << filter.mimePatterns().join(QLatin1Char(' '));
0148         }
0149 
0150         KFileFilter allSupportedFilesFilter;
0151 
0152         if (count() <= 3) { // show the MIME type comments of at max 3 types
0153             QStringList allComments;
0154             for (const KFileFilter &filter : std::as_const(d->m_filters)) {
0155                 allComments << filter.label();
0156             }
0157 
0158             allSupportedFilesFilter = KFileFilter(allComments.join(delim), {}, allTypes);
0159         } else {
0160             allSupportedFilesFilter = KFileFilter(i18n("All Supported Files"), {}, allTypes);
0161             d->m_hasAllSupportedFiles = true;
0162         }
0163 
0164         insertItem(0, allSupportedFilesFilter.label());
0165         d->m_filters.prepend(allSupportedFilesFilter);
0166         setCurrentIndex(0);
0167     }
0168 
0169     if (hasAllFilesFilter) {
0170         addItem(i18n("All Files"));
0171         d->m_filters.append(KFileFilter(i18n("All Files"), {}, {QStringLiteral("application/octet-stream")}));
0172     }
0173 
0174     d->m_lastFilter = currentText();
0175 }
0176 
0177 KFileFilter KFileFilterCombo::currentFilter() const
0178 {
0179     if (currentText() != itemText(currentIndex())) {
0180         // The user edited the text
0181 
0182         const QList<KFileFilter> filter = KFileFilter::fromFilterString(currentText());
0183 
0184         if (!filter.isEmpty()) {
0185             return filter.first();
0186         } else {
0187             return KFileFilter();
0188         }
0189     } else {
0190         if (currentIndex() == -1) {
0191             return KFileFilter();
0192         }
0193 
0194         return d->m_filters[currentIndex()];
0195     }
0196 }
0197 
0198 bool KFileFilterCombo::showsAllTypes() const
0199 {
0200     return d->m_allTypes;
0201 }
0202 
0203 QList<KFileFilter> KFileFilterCombo::filters() const
0204 {
0205     return d->m_filters;
0206 }
0207 
0208 void KFileFilterCombo::setCurrentFilter(const KFileFilter &filter)
0209 {
0210     auto it = std::find(d->m_filters.cbegin(), d->m_filters.cend(), filter);
0211 
0212     if (it == d->m_filters.cend()) {
0213         qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << "Could not find file filter";
0214         setCurrentIndex(-1);
0215         Q_EMIT filterChanged();
0216         return;
0217     }
0218 
0219     setCurrentIndex(std::distance(d->m_filters.cbegin(), it));
0220     Q_EMIT filterChanged();
0221 }
0222 
0223 void KFileFilterComboPrivate::slotFilterChanged()
0224 {
0225     m_lastFilter = q->currentText();
0226 }
0227 
0228 bool KFileFilterCombo::eventFilter(QObject *o, QEvent *e)
0229 {
0230     if (o == lineEdit() && e->type() == QEvent::FocusOut) {
0231         if (currentText() != d->m_lastFilter) {
0232             Q_EMIT filterChanged();
0233         }
0234     }
0235 
0236     return KComboBox::eventFilter(o, e);
0237 }
0238 
0239 void KFileFilterCombo::setDefaultFilter(const KFileFilter &filter)
0240 {
0241     d->m_defaultFilter = filter;
0242 }
0243 
0244 KFileFilter KFileFilterCombo::defaultFilter() const
0245 {
0246     return d->m_defaultFilter;
0247 }
0248 
0249 #include "moc_kfilefiltercombo.cpp"