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 | |
26 | struct LocationData { |
27 | QUrl url; |
28 | QVariant state; |
29 | }; |
30 | |
31 | class KCoreUrlNavigatorPrivate |
32 | { |
33 | public: |
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 | |
61 | KCoreUrlNavigatorPrivate::KCoreUrlNavigatorPrivate(KCoreUrlNavigator *qq) |
62 | : q(qq) |
63 | { |
64 | } |
65 | |
66 | bool 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 | |
75 | int 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 | |
89 | KCoreUrlNavigator::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 | |
96 | KCoreUrlNavigator::~KCoreUrlNavigator() |
97 | { |
98 | } |
99 | |
100 | QUrl KCoreUrlNavigator::locationUrl(int historyIndex) const |
101 | { |
102 | historyIndex = d->adjustedHistoryIndex(historyIndex); |
103 | return d->m_history.at(i: historyIndex).url; |
104 | } |
105 | |
106 | void KCoreUrlNavigator::saveLocationState(const QVariant &state) |
107 | { |
108 | d->m_history[d->m_historyIndex].state = state; |
109 | } |
110 | |
111 | QVariant KCoreUrlNavigator::locationState(int historyIndex) const |
112 | { |
113 | historyIndex = d->adjustedHistoryIndex(historyIndex); |
114 | return d->m_history.at(i: historyIndex).state; |
115 | } |
116 | |
117 | bool 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 | |
135 | bool 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 | |
152 | bool 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 | |
164 | QUrl KCoreUrlNavigator::currentLocationUrl() const |
165 | { |
166 | return locationUrl(); |
167 | } |
168 | |
169 | void 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 | |
252 | int KCoreUrlNavigator::historySize() const |
253 | { |
254 | return d->m_history.count(); |
255 | } |
256 | |
257 | int KCoreUrlNavigator::historyIndex() const |
258 | { |
259 | return d->m_historyIndex; |
260 | } |
261 | |
262 | #include "moc_kcoreurlnavigator.cpp" |
263 | |