1/*
2 SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
3 SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
4 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
5 SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kcoreurlnavigator.h"
11
12#include "urlutil_p.h"
13
14#include <KIO/StatJob>
15#include <KLocalizedString>
16#include <kfileitem.h>
17#include <kprotocolinfo.h>
18
19#include <QDir>
20#include <QMimeData>
21#include <QMimeDatabase>
22
23#include <algorithm>
24#include <numeric>
25
26struct LocationData {
27 QUrl url;
28 QVariant state;
29};
30
31class KCoreUrlNavigatorPrivate
32{
33public:
34 KCoreUrlNavigatorPrivate(KCoreUrlNavigator *qq);
35
36 ~KCoreUrlNavigatorPrivate()
37 {
38 }
39
40 /**
41 * Returns true, if the MIME type of the path represents a
42 * compressed file like TAR or ZIP, as listed in @p archiveMimetypes
43 */
44 bool isCompressedPath(const QUrl &path, const QStringList &archiveMimetypes) const;
45
46 /**
47 * Returns the current history index, if \a historyIndex is
48 * smaller than 0. If \a historyIndex is greater or equal than
49 * the number of available history items, the largest possible
50 * history index is returned. For the other cases just \a historyIndex
51 * is returned.
52 */
53 int adjustedHistoryIndex(int historyIndex) const;
54
55 KCoreUrlNavigator *const q;
56
57 QList<LocationData> m_history;
58 int m_historyIndex = 0;
59};
60
61KCoreUrlNavigatorPrivate::KCoreUrlNavigatorPrivate(KCoreUrlNavigator *qq)
62 : q(qq)
63{
64}
65
66bool KCoreUrlNavigatorPrivate::isCompressedPath(const QUrl &url, const QStringList &archiveMimetypes) const
67{
68 QMimeDatabase db;
69 const QMimeType mime = db.mimeTypeForUrl(url: QUrl(url.toString(options: QUrl::StripTrailingSlash)));
70 return std::any_of(first: archiveMimetypes.begin(), last: archiveMimetypes.end(), pred: [mime](const QString &archiveType) {
71 return mime.inherits(mimeTypeName: archiveType);
72 });
73}
74
75int KCoreUrlNavigatorPrivate::adjustedHistoryIndex(int historyIndex) const
76{
77 const int historySize = m_history.size();
78 if (historyIndex < 0) {
79 historyIndex = m_historyIndex;
80 } else if (historyIndex >= historySize) {
81 historyIndex = historySize - 1;
82 Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0
83 }
84 return historyIndex;
85}
86
87// ------------------------------------------------------------------------------------------------
88
89KCoreUrlNavigator::KCoreUrlNavigator(const QUrl &url, QObject *parent)
90 : QObject(parent)
91 , d(new KCoreUrlNavigatorPrivate(this))
92{
93 d->m_history.prepend(t: LocationData{.url: url.adjusted(options: QUrl::NormalizePathSegments), .state: {}});
94}
95
96KCoreUrlNavigator::~KCoreUrlNavigator()
97{
98}
99
100QUrl KCoreUrlNavigator::locationUrl(int historyIndex) const
101{
102 historyIndex = d->adjustedHistoryIndex(historyIndex);
103 return d->m_history.at(i: historyIndex).url;
104}
105
106void KCoreUrlNavigator::saveLocationState(const QVariant &state)
107{
108 d->m_history[d->m_historyIndex].state = state;
109}
110
111QVariant KCoreUrlNavigator::locationState(int historyIndex) const
112{
113 historyIndex = d->adjustedHistoryIndex(historyIndex);
114 return d->m_history.at(i: historyIndex).state;
115}
116
117bool KCoreUrlNavigator::goBack()
118{
119 const int count = d->m_history.size();
120 if (d->m_historyIndex < count - 1) {
121 const QUrl newUrl = locationUrl(historyIndex: d->m_historyIndex + 1);
122 Q_EMIT currentUrlAboutToChange(newUrl);
123
124 ++d->m_historyIndex;
125
126 Q_EMIT historyIndexChanged();
127 Q_EMIT historyChanged();
128 Q_EMIT currentLocationUrlChanged();
129 return true;
130 }
131
132 return false;
133}
134
135bool KCoreUrlNavigator::goForward()
136{
137 if (d->m_historyIndex > 0) {
138 const QUrl newUrl = locationUrl(historyIndex: d->m_historyIndex - 1);
139 Q_EMIT currentUrlAboutToChange(newUrl);
140
141 --d->m_historyIndex;
142
143 Q_EMIT historyIndexChanged();
144 Q_EMIT historyChanged();
145 Q_EMIT currentLocationUrlChanged();
146 return true;
147 }
148
149 return false;
150}
151
152bool KCoreUrlNavigator::goUp()
153{
154 const QUrl currentUrl = locationUrl();
155 const QUrl upUrl = KIO::upUrl(url: currentUrl);
156 if (!currentUrl.matches(url: upUrl, options: QUrl::StripTrailingSlash)) {
157 setCurrentLocationUrl(upUrl);
158 return true;
159 }
160
161 return false;
162}
163
164QUrl KCoreUrlNavigator::currentLocationUrl() const
165{
166 return locationUrl();
167}
168
169void KCoreUrlNavigator::setCurrentLocationUrl(const QUrl &newUrl)
170{
171 if (newUrl == locationUrl()) {
172 return;
173 }
174
175 QUrl url = newUrl.adjusted(options: QUrl::NormalizePathSegments);
176
177 // This will be used below; we define it here because in the lower part of the
178 // code locationUrl() and url become the same URLs
179 QUrl firstChildUrl = KIO::UrlUtil::firstChildUrl(lastUrl: locationUrl(), currentUrl: url);
180
181 const QString scheme = url.scheme();
182 if (!scheme.isEmpty()) {
183 // Check if the URL represents a tar-, zip- or 7z-file, or an archive file supported by krarc.
184 const QStringList archiveMimetypes = KProtocolInfo::archiveMimetypes(protocol: scheme);
185
186 if (!archiveMimetypes.isEmpty()) {
187 // Check whether the URL is really part of the archive file, otherwise
188 // replace it by the local path again.
189 bool insideCompressedPath = d->isCompressedPath(url, archiveMimetypes);
190 if (!insideCompressedPath) {
191 QUrl prevUrl = url;
192 QUrl parentUrl = KIO::upUrl(url);
193 while (parentUrl != prevUrl) {
194 if (d->isCompressedPath(url: parentUrl, archiveMimetypes)) {
195 insideCompressedPath = true;
196 break;
197 }
198 prevUrl = parentUrl;
199 parentUrl = KIO::upUrl(url: parentUrl);
200 }
201 }
202 if (!insideCompressedPath) {
203 // drop the tar:, zip:, sevenz: or krarc: protocol since we are not
204 // inside the compressed path
205 url.setScheme(QStringLiteral("file"));
206 firstChildUrl.setScheme(QStringLiteral("file"));
207 }
208 }
209 }
210
211 // Check whether current history element has the same URL.
212 // If this is the case, just ignore setting the URL.
213 const LocationData &data = d->m_history.at(i: d->m_historyIndex);
214 const bool isUrlEqual = url.matches(url: locationUrl(), options: QUrl::StripTrailingSlash) || (!url.isValid() && url.matches(url: data.url, options: QUrl::StripTrailingSlash));
215 if (isUrlEqual) {
216 return;
217 }
218
219 Q_EMIT currentUrlAboutToChange(newUrl: url);
220
221 if (d->m_historyIndex > 0) {
222 // If an URL is set when the history index is not at the end (= 0),
223 // then clear all previous history elements so that a new history
224 // tree is started from the current position.
225 auto begin = d->m_history.begin();
226 auto end = begin + d->m_historyIndex;
227 d->m_history.erase(abegin: begin, aend: end);
228 d->m_historyIndex = 0;
229 }
230
231 Q_ASSERT(d->m_historyIndex == 0);
232 d->m_history.insert(i: 0, t: LocationData{.url: url, .state: {}});
233
234 // Prevent an endless growing of the history: remembering
235 // the last 100 Urls should be enough...
236 const int historyMax = 100;
237 if (d->m_history.size() > historyMax) {
238 auto begin = d->m_history.begin() + historyMax;
239 auto end = d->m_history.end();
240 d->m_history.erase(abegin: begin, aend: end);
241 }
242
243 Q_EMIT historyIndexChanged();
244 Q_EMIT historySizeChanged();
245 Q_EMIT historyChanged();
246 Q_EMIT currentLocationUrlChanged();
247 if (firstChildUrl.isValid()) {
248 Q_EMIT urlSelectionRequested(url: firstChildUrl);
249 }
250}
251
252int KCoreUrlNavigator::historySize() const
253{
254 return d->m_history.count();
255}
256
257int KCoreUrlNavigator::historyIndex() const
258{
259 return d->m_historyIndex;
260}
261
262#include "moc_kcoreurlnavigator.cpp"
263

source code of kio/src/gui/kcoreurlnavigator.cpp