File indexing completed on 2024-04-28 15:26:40

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