File indexing completed on 2023-11-26 10:44:06

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     QString m_defaultFilter = i18nc("Default mime type filter that shows all file types", "*|All Files");
0043 
0044     QList<KFileFilter> m_fileFilters;
0045     QStringList m_filters;
0046     bool m_allTypes;
0047 };
0048 
0049 KFileFilterCombo::KFileFilterCombo(QWidget *parent)
0050     : KComboBox(true, parent)
0051     , d(new KFileFilterComboPrivate(this))
0052 {
0053     setTrapReturnKey(true);
0054     setInsertPolicy(QComboBox::NoInsert);
0055     connect(this, &QComboBox::activated, this, &KFileFilterCombo::filterChanged);
0056     connect(this, &KComboBox::returnPressed, this, &KFileFilterCombo::filterChanged);
0057     connect(this, &KFileFilterCombo::filterChanged, this, [this]() {
0058         d->slotFilterChanged();
0059     });
0060     d->m_allTypes = false;
0061 }
0062 
0063 KFileFilterCombo::~KFileFilterCombo() = default;
0064 
0065 void KFileFilterCombo::setFilter(const QString &filter)
0066 {
0067     clear();
0068     d->m_filters.clear();
0069     d->m_fileFilters.clear();
0070     d->m_hasAllSupportedFiles = false;
0071 
0072     if (!filter.isEmpty()) {
0073         QString tmp = filter;
0074         int index = tmp.indexOf(QLatin1Char('\n'));
0075         while (index > 0) {
0076             d->m_filters.append(tmp.left(index));
0077             tmp.remove(0, index + 1);
0078             index = tmp.indexOf(QLatin1Char('\n'));
0079         }
0080         d->m_filters.append(tmp);
0081     } else {
0082         d->m_filters.append(d->m_defaultFilter);
0083     }
0084 
0085     QStringList::ConstIterator it;
0086     QStringList::ConstIterator end(d->m_filters.constEnd());
0087     for (it = d->m_filters.constBegin(); it != end; ++it) {
0088         int tab = (*it).indexOf(QLatin1Char('|'));
0089         addItem((tab < 0) ? *it : (*it).mid(tab + 1));
0090     }
0091 
0092     d->m_lastFilter = currentText();
0093     d->m_isMimeFilter = false;
0094 }
0095 
0096 QString KFileFilterCombo::currentFilter() const
0097 {
0098     QString f = currentText();
0099     if (f == itemText(currentIndex())) { // user didn't edit the text
0100 
0101         if (!d->m_filters.isEmpty()) {
0102             f = d->m_filters.value(currentIndex());
0103         } else {
0104             f = d->m_fileFilters.value(currentIndex()).toFilterString();
0105         }
0106 
0107         if (d->m_isMimeFilter || (currentIndex() == 0 && d->m_hasAllSupportedFiles)) {
0108             return f; // we have a MIME type as filter
0109         }
0110     }
0111 
0112     int tab = f.indexOf(QLatin1Char('|'));
0113     if (tab < 0) {
0114         return f;
0115     } else {
0116         return f.left(tab);
0117     }
0118 }
0119 
0120 bool KFileFilterCombo::showsAllTypes() const
0121 {
0122     return d->m_allTypes;
0123 }
0124 
0125 QStringList KFileFilterCombo::filters() const
0126 {
0127     if (!d->m_filters.isEmpty()) {
0128         return d->m_filters;
0129     }
0130 
0131     QStringList result;
0132 
0133     for (const KFileFilter &filter : std::as_const(d->m_fileFilters)) {
0134         result << filter.toFilterString();
0135     }
0136 
0137     return result;
0138 }
0139 
0140 void KFileFilterCombo::setCurrentFilter(const QString &filterString)
0141 {
0142     if (!d->m_filters.isEmpty()) {
0143         setCurrentIndex(d->m_filters.indexOf(filterString));
0144         return;
0145     }
0146 
0147     auto it = std::find_if(d->m_fileFilters.cbegin(), d->m_fileFilters.cend(), [filterString](const KFileFilter &filter) {
0148         return filterString == filter.toFilterString();
0149     });
0150 
0151     if (it == d->m_fileFilters.cend()) {
0152         qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << "Could not find filter" << filterString;
0153         setCurrentIndex(-1);
0154         Q_EMIT filterChanged();
0155         return;
0156     }
0157 
0158     setCurrentIndex(std::distance(d->m_fileFilters.cbegin(), it));
0159     Q_EMIT filterChanged();
0160 }
0161 
0162 void KFileFilterCombo::setMimeFilter(const QStringList &types, const QString &defaultType)
0163 {
0164     clear();
0165     d->m_filters.clear();
0166     d->m_fileFilters.clear();
0167     QString delim = QStringLiteral(", ");
0168     d->m_hasAllSupportedFiles = false;
0169     bool hasAllFilesFilter = false;
0170     QMimeDatabase db;
0171 
0172     d->m_allTypes = defaultType.isEmpty() && (types.count() > 1);
0173 
0174     // If there's MIME types that have the same comment, we will show the extension
0175     // in addition to the MIME type comment
0176     QHash<QString, int> allTypeComments;
0177     for (QStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
0178         const QMimeType type = db.mimeTypeForName(*it);
0179         if (!type.isValid()) {
0180             qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << *it << "is not a valid MIME type";
0181             continue;
0182         }
0183 
0184         allTypeComments[type.comment()] += 1;
0185     }
0186 
0187     for (QStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
0188         // qDebug() << *it;
0189         const QMimeType type = db.mimeTypeForName(*it);
0190         if (!type.isValid()) {
0191             qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << *it << "is not a valid MIME type";
0192             continue;
0193         }
0194 
0195         if (type.name().startsWith(QLatin1String("all/")) || type.isDefault()) {
0196             hasAllFilesFilter = true;
0197             continue;
0198         }
0199 
0200         KFileFilter filter;
0201 
0202         if (allTypeComments.value(type.comment()) > 1) {
0203             const QString label = i18nc("%1 is the mimetype name, %2 is the extensions", "%1 (%2)", type.comment(), type.suffixes().join(QLatin1String(", ")));
0204             filter = KFileFilter(label, {}, {*it});
0205         } else {
0206             filter = KFileFilter::fromMimeType(*it);
0207         }
0208 
0209         d->m_fileFilters.append(filter);
0210         addItem(filter.label());
0211 
0212         if (type.name() == defaultType) {
0213             setCurrentIndex(count() - 1);
0214         }
0215     }
0216 
0217     if (count() == 1) {
0218         d->m_allTypes = false;
0219     }
0220 
0221     if (d->m_allTypes) {
0222         QStringList allTypes;
0223         for (const KFileFilter &filter : std::as_const(d->m_fileFilters)) {
0224             allTypes << filter.mimePatterns().join(QLatin1Char(' '));
0225         }
0226 
0227         KFileFilter allSupportedFilesFilter;
0228 
0229         if (count() <= 3) { // show the MIME type comments of at max 3 types
0230             QStringList allComments;
0231             for (const KFileFilter &filter : std::as_const(d->m_fileFilters)) {
0232                 allComments << filter.label();
0233             }
0234 
0235             allSupportedFilesFilter = KFileFilter(allComments.join(delim), {}, allTypes);
0236         } else {
0237             allSupportedFilesFilter = KFileFilter(i18n("All Supported Files"), {}, allTypes);
0238             d->m_hasAllSupportedFiles = true;
0239         }
0240 
0241         insertItem(0, allSupportedFilesFilter.label());
0242         d->m_fileFilters.prepend(allSupportedFilesFilter);
0243         setCurrentIndex(0);
0244     }
0245 
0246     if (hasAllFilesFilter) {
0247         addItem(i18n("All Files"));
0248         d->m_fileFilters.append(KFileFilter(i18n("All Files"), {}, {QStringLiteral("application/octet-stream")}));
0249     }
0250 
0251     d->m_lastFilter = currentText();
0252     d->m_isMimeFilter = true;
0253 }
0254 
0255 void KFileFilterComboPrivate::slotFilterChanged()
0256 {
0257     m_lastFilter = q->currentText();
0258 }
0259 
0260 bool KFileFilterCombo::eventFilter(QObject *o, QEvent *e)
0261 {
0262     if (o == lineEdit() && e->type() == QEvent::FocusOut) {
0263         if (currentText() != d->m_lastFilter) {
0264             Q_EMIT filterChanged();
0265         }
0266     }
0267 
0268     return KComboBox::eventFilter(o, e);
0269 }
0270 
0271 void KFileFilterCombo::setDefaultFilter(const QString &filter)
0272 {
0273     d->m_defaultFilter = filter;
0274 }
0275 
0276 QString KFileFilterCombo::defaultFilter() const
0277 {
0278     return d->m_defaultFilter;
0279 }
0280 
0281 bool KFileFilterCombo::isMimeFilter() const
0282 {
0283     return d->m_isMimeFilter;
0284 }
0285 
0286 #include "moc_kfilefiltercombo.cpp"