File indexing completed on 2024-04-21 03:55:27

0001 /*
0002     SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
0004     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
0005     SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kcoreurlnavigator.h"
0011 
0012 #include "urlutil_p.h"
0013 
0014 #include <KIO/StatJob>
0015 #include <KLocalizedString>
0016 #include <kfileitem.h>
0017 #include <kprotocolinfo.h>
0018 
0019 #include <QDir>
0020 #include <QMimeData>
0021 #include <QMimeDatabase>
0022 
0023 #include <algorithm>
0024 #include <numeric>
0025 
0026 struct LocationData {
0027     QUrl url;
0028     QVariant state;
0029 };
0030 
0031 class KCoreUrlNavigatorPrivate
0032 {
0033 public:
0034     KCoreUrlNavigatorPrivate(KCoreUrlNavigator *qq);
0035 
0036     ~KCoreUrlNavigatorPrivate()
0037     {
0038     }
0039 
0040     /**
0041      * Returns true, if the MIME type of the path represents a
0042      * compressed file like TAR or ZIP, as listed in @p archiveMimetypes
0043      */
0044     bool isCompressedPath(const QUrl &path, const QStringList &archiveMimetypes) const;
0045 
0046     /**
0047      * Returns the current history index, if \a historyIndex is
0048      * smaller than 0. If \a historyIndex is greater or equal than
0049      * the number of available history items, the largest possible
0050      * history index is returned. For the other cases just \a historyIndex
0051      * is returned.
0052      */
0053     int adjustedHistoryIndex(int historyIndex) const;
0054 
0055     KCoreUrlNavigator *const q;
0056 
0057     QList<LocationData> m_history;
0058     int m_historyIndex = 0;
0059 };
0060 
0061 KCoreUrlNavigatorPrivate::KCoreUrlNavigatorPrivate(KCoreUrlNavigator *qq)
0062     : q(qq)
0063 {
0064 }
0065 
0066 bool KCoreUrlNavigatorPrivate::isCompressedPath(const QUrl &url, const QStringList &archiveMimetypes) const
0067 {
0068     QMimeDatabase db;
0069     const QMimeType mime = db.mimeTypeForUrl(QUrl(url.toString(QUrl::StripTrailingSlash)));
0070     return std::any_of(archiveMimetypes.begin(), archiveMimetypes.end(), [mime](const QString &archiveType) {
0071         return mime.inherits(archiveType);
0072     });
0073 }
0074 
0075 int KCoreUrlNavigatorPrivate::adjustedHistoryIndex(int historyIndex) const
0076 {
0077     const int historySize = m_history.size();
0078     if (historyIndex < 0) {
0079         historyIndex = m_historyIndex;
0080     } else if (historyIndex >= historySize) {
0081         historyIndex = historySize - 1;
0082         Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0
0083     }
0084     return historyIndex;
0085 }
0086 
0087 // ------------------------------------------------------------------------------------------------
0088 
0089 KCoreUrlNavigator::KCoreUrlNavigator(const QUrl &url, QObject *parent)
0090     : QObject(parent)
0091     , d(new KCoreUrlNavigatorPrivate(this))
0092 {
0093     d->m_history.prepend(LocationData{url.adjusted(QUrl::NormalizePathSegments), {}});
0094 }
0095 
0096 KCoreUrlNavigator::~KCoreUrlNavigator()
0097 {
0098 }
0099 
0100 QUrl KCoreUrlNavigator::locationUrl(int historyIndex) const
0101 {
0102     historyIndex = d->adjustedHistoryIndex(historyIndex);
0103     return d->m_history.at(historyIndex).url;
0104 }
0105 
0106 void KCoreUrlNavigator::saveLocationState(const QVariant &state)
0107 {
0108     d->m_history[d->m_historyIndex].state = state;
0109 }
0110 
0111 QVariant KCoreUrlNavigator::locationState(int historyIndex) const
0112 {
0113     historyIndex = d->adjustedHistoryIndex(historyIndex);
0114     return d->m_history.at(historyIndex).state;
0115 }
0116 
0117 bool KCoreUrlNavigator::goBack()
0118 {
0119     const int count = d->m_history.size();
0120     if (d->m_historyIndex < count - 1) {
0121         const QUrl newUrl = locationUrl(d->m_historyIndex + 1);
0122         Q_EMIT currentUrlAboutToChange(newUrl);
0123 
0124         ++d->m_historyIndex;
0125 
0126         Q_EMIT historyIndexChanged();
0127         Q_EMIT historyChanged();
0128         Q_EMIT currentLocationUrlChanged();
0129         return true;
0130     }
0131 
0132     return false;
0133 }
0134 
0135 bool KCoreUrlNavigator::goForward()
0136 {
0137     if (d->m_historyIndex > 0) {
0138         const QUrl newUrl = locationUrl(d->m_historyIndex - 1);
0139         Q_EMIT currentUrlAboutToChange(newUrl);
0140 
0141         --d->m_historyIndex;
0142 
0143         Q_EMIT historyIndexChanged();
0144         Q_EMIT historyChanged();
0145         Q_EMIT currentLocationUrlChanged();
0146         return true;
0147     }
0148 
0149     return false;
0150 }
0151 
0152 bool KCoreUrlNavigator::goUp()
0153 {
0154     const QUrl currentUrl = locationUrl();
0155     const QUrl upUrl = KIO::upUrl(currentUrl);
0156     if (!currentUrl.matches(upUrl, QUrl::StripTrailingSlash)) {
0157         setCurrentLocationUrl(upUrl);
0158         return true;
0159     }
0160 
0161     return false;
0162 }
0163 
0164 QUrl KCoreUrlNavigator::currentLocationUrl() const
0165 {
0166     return locationUrl();
0167 }
0168 
0169 void KCoreUrlNavigator::setCurrentLocationUrl(const QUrl &newUrl)
0170 {
0171     if (newUrl == locationUrl()) {
0172         return;
0173     }
0174 
0175     QUrl url = newUrl.adjusted(QUrl::NormalizePathSegments);
0176 
0177     // This will be used below; we define it here because in the lower part of the
0178     // code locationUrl() and url become the same URLs
0179     QUrl firstChildUrl = KIO::UrlUtil::firstChildUrl(locationUrl(), url);
0180 
0181     const QString scheme = url.scheme();
0182     if (!scheme.isEmpty()) {
0183         // Check if the URL represents a tar-, zip- or 7z-file, or an archive file supported by krarc.
0184         const QStringList archiveMimetypes = KProtocolInfo::archiveMimetypes(scheme);
0185 
0186         if (!archiveMimetypes.isEmpty()) {
0187             // Check whether the URL is really part of the archive file, otherwise
0188             // replace it by the local path again.
0189             bool insideCompressedPath = d->isCompressedPath(url, archiveMimetypes);
0190             if (!insideCompressedPath) {
0191                 QUrl prevUrl = url;
0192                 QUrl parentUrl = KIO::upUrl(url);
0193                 while (parentUrl != prevUrl) {
0194                     if (d->isCompressedPath(parentUrl, archiveMimetypes)) {
0195                         insideCompressedPath = true;
0196                         break;
0197                     }
0198                     prevUrl = parentUrl;
0199                     parentUrl = KIO::upUrl(parentUrl);
0200                 }
0201             }
0202             if (!insideCompressedPath) {
0203                 // drop the tar:, zip:, sevenz: or krarc: protocol since we are not
0204                 // inside the compressed path
0205                 url.setScheme(QStringLiteral("file"));
0206                 firstChildUrl.setScheme(QStringLiteral("file"));
0207             }
0208         }
0209     }
0210 
0211     // Check whether current history element has the same URL.
0212     // If this is the case, just ignore setting the URL.
0213     const LocationData &data = d->m_history.at(d->m_historyIndex);
0214     const bool isUrlEqual = url.matches(locationUrl(), QUrl::StripTrailingSlash) || (!url.isValid() && url.matches(data.url, QUrl::StripTrailingSlash));
0215     if (isUrlEqual) {
0216         return;
0217     }
0218 
0219     Q_EMIT currentUrlAboutToChange(url);
0220 
0221     if (d->m_historyIndex > 0) {
0222         // If an URL is set when the history index is not at the end (= 0),
0223         // then clear all previous history elements so that a new history
0224         // tree is started from the current position.
0225         auto begin = d->m_history.begin();
0226         auto end = begin + d->m_historyIndex;
0227         d->m_history.erase(begin, end);
0228         d->m_historyIndex = 0;
0229     }
0230 
0231     Q_ASSERT(d->m_historyIndex == 0);
0232     d->m_history.insert(0, LocationData{url, {}});
0233 
0234     // Prevent an endless growing of the history: remembering
0235     // the last 100 Urls should be enough...
0236     const int historyMax = 100;
0237     if (d->m_history.size() > historyMax) {
0238         auto begin = d->m_history.begin() + historyMax;
0239         auto end = d->m_history.end();
0240         d->m_history.erase(begin, end);
0241     }
0242 
0243     Q_EMIT historyIndexChanged();
0244     Q_EMIT historySizeChanged();
0245     Q_EMIT historyChanged();
0246     Q_EMIT currentLocationUrlChanged();
0247     if (firstChildUrl.isValid()) {
0248         Q_EMIT urlSelectionRequested(firstChildUrl);
0249     }
0250 }
0251 
0252 int KCoreUrlNavigator::historySize() const
0253 {
0254     return d->m_history.count();
0255 }
0256 
0257 int KCoreUrlNavigator::historyIndex() const
0258 {
0259     return d->m_historyIndex;
0260 }
0261 
0262 #include "moc_kcoreurlnavigator.cpp"