]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/viewproperties.cpp
viewproperties: remove now obsolete recentdocument reference
[dolphin.git] / src / views / viewproperties.cpp
1 /*
2 * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "viewproperties.h"
9
10 #include "dolphin_directoryviewpropertysettings.h"
11 #include "dolphin_generalsettings.h"
12 #include "dolphindebug.h"
13
14 #include <QCryptographicHash>
15
16 #include <KFileItem>
17
18 namespace
19 {
20 const int AdditionalInfoViewPropertiesVersion = 1;
21 const int NameRolePropertiesVersion = 2;
22 const int DateRolePropertiesVersion = 4;
23 const int CurrentViewPropertiesVersion = 4;
24
25 // String representation to mark the additional properties of
26 // the details view as customized by the user. See
27 // ViewProperties::visibleRoles() for more information.
28 const char CustomizedDetailsString[] = "CustomizedDetails";
29
30 // Filename that is used for storing the properties
31 const char ViewPropertiesFileName[] = ".directory";
32 }
33
34 ViewProperties::ViewProperties(const QUrl &url)
35 : m_changedProps(false)
36 , m_autoSave(true)
37 , m_node(nullptr)
38 {
39 GeneralSettings *settings = GeneralSettings::self();
40 const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty();
41 bool useSearchView = false;
42 bool useTrashView = false;
43 bool useRecentDocumentsView = false;
44 bool useDownloadsView = false;
45
46 // We try and save it to the file .directory in the directory being viewed.
47 // If the directory is not writable by the user or the directory is not local,
48 // we store the properties information in a local file.
49 if (url.scheme().contains(QLatin1String("search"))) {
50 m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url);
51 useSearchView = true;
52 } else if (url.scheme() == QLatin1String("trash")) {
53 m_filePath = destinationDir(QStringLiteral("trash"));
54 useTrashView = true;
55 } else if (url.scheme() == QLatin1String("recentlyused")) {
56 m_filePath = destinationDir(QStringLiteral("recentlyused"));
57 useRecentDocumentsView = true;
58 } else if (url.scheme() == QLatin1String("timeline")) {
59 m_filePath = destinationDir(QStringLiteral("timeline"));
60 useRecentDocumentsView = true;
61 } else if (useGlobalViewProps) {
62 m_filePath = destinationDir(QStringLiteral("global"));
63 } else if (url.isLocalFile()) {
64 m_filePath = url.toLocalFile();
65
66 bool useDestinationDir = !isPartOfHome(m_filePath);
67 if (!useDestinationDir) {
68 const KFileItem fileItem(url);
69 useDestinationDir = fileItem.isSlow();
70 }
71
72 if (!useDestinationDir) {
73 const QFileInfo dirInfo(m_filePath);
74 const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName);
75 useDestinationDir = !dirInfo.isWritable() || (dirInfo.size() > 0 && fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable()));
76 }
77
78 if (useDestinationDir) {
79 #ifdef Q_OS_WIN
80 // m_filePath probably begins with C:/ - the colon is not a valid character for paths though
81 m_filePath = QDir::separator() + m_filePath.remove(QLatin1Char(':'));
82 #endif
83 m_filePath = destinationDir(QStringLiteral("local")) + m_filePath;
84 }
85
86 if (m_filePath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
87 useDownloadsView = true;
88 }
89 } else {
90 m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath;
91 }
92
93 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
94 m_node = new ViewPropertySettings(KSharedConfig::openConfig(file));
95
96 // If the .directory file does not exist or the timestamp is too old,
97 // use default values instead.
98 const bool useDefaultProps = (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView)
99 && (!QFile::exists(file) || (m_node->timestamp() < settings->viewPropsTimestamp()));
100 if (useDefaultProps) {
101 if (useSearchView) {
102 const QString path = url.path();
103
104 if (path == QLatin1String("/images")) {
105 setViewMode(DolphinView::IconsView);
106 setPreviewsShown(true);
107 setVisibleRoles({"text", "dimensions", "imageDateTime"});
108 } else if (path == QLatin1String("/audio")) {
109 setViewMode(DolphinView::DetailsView);
110 setVisibleRoles({"text", "artist", "album", "duration"});
111 } else if (path == QLatin1String("/videos")) {
112 setViewMode(DolphinView::IconsView);
113 setPreviewsShown(true);
114 setVisibleRoles({"text"});
115 } else {
116 setViewMode(DolphinView::DetailsView);
117 setVisibleRoles({"text", "path", "modificationtime"});
118 }
119 } else if (useTrashView) {
120 setViewMode(DolphinView::DetailsView);
121 setVisibleRoles({"text", "path", "deletiontime"});
122 } else if (useRecentDocumentsView || useDownloadsView) {
123 setSortRole(QByteArrayLiteral("modificationtime"));
124 setSortOrder(Qt::DescendingOrder);
125 setSortFoldersFirst(false);
126 setGroupedSorting(true);
127
128 if (useRecentDocumentsView) {
129 setViewMode(DolphinView::DetailsView);
130 setVisibleRoles({"text", "path", "modificationtime"});
131 }
132 } else {
133 // The global view-properties act as default for directories without
134 // any view-property configuration. Constructing a ViewProperties
135 // instance for an empty QUrl ensures that the global view-properties
136 // are loaded.
137 QUrl emptyUrl;
138 ViewProperties defaultProps(emptyUrl);
139 setDirProperties(defaultProps);
140
141 m_changedProps = false;
142 }
143 }
144
145 if (m_node->version() < CurrentViewPropertiesVersion) {
146 // The view-properties have an outdated version. Convert the properties
147 // to the changes of the current version.
148 if (m_node->version() < AdditionalInfoViewPropertiesVersion) {
149 convertAdditionalInfo();
150 Q_ASSERT(m_node->version() == AdditionalInfoViewPropertiesVersion);
151 }
152
153 if (m_node->version() < NameRolePropertiesVersion) {
154 convertNameRoleToTextRole();
155 Q_ASSERT(m_node->version() == NameRolePropertiesVersion);
156 }
157
158 if (m_node->version() < DateRolePropertiesVersion) {
159 convertDateRoleToModificationTimeRole();
160 Q_ASSERT(m_node->version() == DateRolePropertiesVersion);
161 }
162
163 m_node->setVersion(CurrentViewPropertiesVersion);
164 }
165 }
166
167 ViewProperties::~ViewProperties()
168 {
169 if (m_changedProps && m_autoSave) {
170 save();
171 }
172
173 delete m_node;
174 m_node = nullptr;
175 }
176
177 void ViewProperties::setViewMode(DolphinView::Mode mode)
178 {
179 if (m_node->viewMode() != mode) {
180 m_node->setViewMode(mode);
181 update();
182 }
183 }
184
185 DolphinView::Mode ViewProperties::viewMode() const
186 {
187 const int mode = qBound(0, m_node->viewMode(), 2);
188 return static_cast<DolphinView::Mode>(mode);
189 }
190
191 void ViewProperties::setPreviewsShown(bool show)
192 {
193 if (m_node->previewsShown() != show) {
194 m_node->setPreviewsShown(show);
195 update();
196 }
197 }
198
199 bool ViewProperties::previewsShown() const
200 {
201 return m_node->previewsShown();
202 }
203
204 void ViewProperties::setHiddenFilesShown(bool show)
205 {
206 if (m_node->hiddenFilesShown() != show) {
207 m_node->setHiddenFilesShown(show);
208 update();
209 }
210 }
211
212 void ViewProperties::setGroupedSorting(bool grouped)
213 {
214 if (m_node->groupedSorting() != grouped) {
215 m_node->setGroupedSorting(grouped);
216 update();
217 }
218 }
219
220 bool ViewProperties::groupedSorting() const
221 {
222 return m_node->groupedSorting();
223 }
224
225 bool ViewProperties::hiddenFilesShown() const
226 {
227 return m_node->hiddenFilesShown();
228 }
229
230 void ViewProperties::setSortRole(const QByteArray &role)
231 {
232 if (m_node->sortRole() != role) {
233 m_node->setSortRole(role);
234 update();
235 }
236 }
237
238 QByteArray ViewProperties::sortRole() const
239 {
240 return m_node->sortRole().toLatin1();
241 }
242
243 void ViewProperties::setSortOrder(Qt::SortOrder sortOrder)
244 {
245 if (m_node->sortOrder() != sortOrder) {
246 m_node->setSortOrder(sortOrder);
247 update();
248 }
249 }
250
251 Qt::SortOrder ViewProperties::sortOrder() const
252 {
253 return static_cast<Qt::SortOrder>(m_node->sortOrder());
254 }
255
256 void ViewProperties::setSortFoldersFirst(bool foldersFirst)
257 {
258 if (m_node->sortFoldersFirst() != foldersFirst) {
259 m_node->setSortFoldersFirst(foldersFirst);
260 update();
261 }
262 }
263
264 bool ViewProperties::sortFoldersFirst() const
265 {
266 return m_node->sortFoldersFirst();
267 }
268
269 void ViewProperties::setSortHiddenLast(bool hiddenLast)
270 {
271 if (m_node->sortHiddenLast() != hiddenLast) {
272 m_node->setSortHiddenLast(hiddenLast);
273 update();
274 }
275 }
276
277 bool ViewProperties::sortHiddenLast() const
278 {
279 return m_node->sortHiddenLast();
280 }
281
282 void ViewProperties::setVisibleRoles(const QList<QByteArray> &roles)
283 {
284 if (roles == visibleRoles()) {
285 return;
286 }
287
288 // See ViewProperties::visibleRoles() for the storage format
289 // of the additional information.
290
291 // Remove the old values stored for the current view-mode
292 const QStringList oldVisibleRoles = m_node->visibleRoles();
293 const QString prefix = viewModePrefix();
294 QStringList newVisibleRoles = oldVisibleRoles;
295 for (int i = newVisibleRoles.count() - 1; i >= 0; --i) {
296 if (newVisibleRoles[i].startsWith(prefix)) {
297 newVisibleRoles.removeAt(i);
298 }
299 }
300
301 // Add the updated values for the current view-mode
302 newVisibleRoles.reserve(roles.count());
303 for (const QByteArray &role : roles) {
304 newVisibleRoles.append(prefix + role);
305 }
306
307 if (oldVisibleRoles != newVisibleRoles) {
308 const bool markCustomizedDetails = (m_node->viewMode() == DolphinView::DetailsView) && !newVisibleRoles.contains(CustomizedDetailsString);
309 if (markCustomizedDetails) {
310 // The additional information of the details-view has been modified. Set a marker,
311 // so that it is allowed to also show no additional information without doing the
312 // fallback to show the size and date per default.
313 newVisibleRoles.append(CustomizedDetailsString);
314 }
315
316 m_node->setVisibleRoles(newVisibleRoles);
317 update();
318 }
319 }
320
321 QList<QByteArray> ViewProperties::visibleRoles() const
322 {
323 // The shown additional information is stored for each view-mode separately as
324 // string with the view-mode as prefix. Example:
325 //
326 // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size
327 //
328 // To get the representation as QList<QByteArray>, the current
329 // view-mode must be checked and the values of this mode added to the list.
330 //
331 // For the details-view a special case must be respected: Per default the size
332 // and date should be shown without creating a .directory file. Only if
333 // the user explicitly has modified the properties of the details view (marked
334 // by "CustomizedDetails"), also a details-view with no additional information
335 // is accepted.
336
337 QList<QByteArray> roles{"text"};
338
339 // Iterate through all stored keys and append all roles that match to
340 // the current view mode.
341 const QString prefix = viewModePrefix();
342 const int prefixLength = prefix.length();
343
344 const QStringList visibleRoles = m_node->visibleRoles();
345 for (const QString &visibleRole : visibleRoles) {
346 if (visibleRole.startsWith(prefix)) {
347 const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1();
348 if (role != "text") {
349 roles.append(role);
350 }
351 }
352 }
353
354 // For the details view the size and date should be shown per default
355 // until the additional information has been explicitly changed by the user
356 const bool useDefaultValues = roles.count() == 1 // "text"
357 && (m_node->viewMode() == DolphinView::DetailsView) && !visibleRoles.contains(CustomizedDetailsString);
358 if (useDefaultValues) {
359 roles.append("size");
360 roles.append("modificationtime");
361 }
362
363 return roles;
364 }
365
366 void ViewProperties::setHeaderColumnWidths(const QList<int> &widths)
367 {
368 if (m_node->headerColumnWidths() != widths) {
369 m_node->setHeaderColumnWidths(widths);
370 update();
371 }
372 }
373
374 QList<int> ViewProperties::headerColumnWidths() const
375 {
376 return m_node->headerColumnWidths();
377 }
378
379 void ViewProperties::setDirProperties(const ViewProperties &props)
380 {
381 setViewMode(props.viewMode());
382 setPreviewsShown(props.previewsShown());
383 setHiddenFilesShown(props.hiddenFilesShown());
384 setGroupedSorting(props.groupedSorting());
385 setSortRole(props.sortRole());
386 setSortOrder(props.sortOrder());
387 setSortFoldersFirst(props.sortFoldersFirst());
388 setSortHiddenLast(props.sortHiddenLast());
389 setVisibleRoles(props.visibleRoles());
390 setHeaderColumnWidths(props.headerColumnWidths());
391 m_node->setVersion(props.m_node->version());
392 }
393
394 void ViewProperties::setAutoSaveEnabled(bool autoSave)
395 {
396 m_autoSave = autoSave;
397 }
398
399 bool ViewProperties::isAutoSaveEnabled() const
400 {
401 return m_autoSave;
402 }
403
404 void ViewProperties::update()
405 {
406 m_changedProps = true;
407 m_node->setTimestamp(QDateTime::currentDateTime());
408 }
409
410 void ViewProperties::save()
411 {
412 qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath;
413 QDir dir;
414 dir.mkpath(m_filePath);
415 m_node->setVersion(CurrentViewPropertiesVersion);
416 m_node->save();
417 m_changedProps = false;
418 }
419
420 bool ViewProperties::exist() const
421 {
422 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
423 return QFile::exists(file);
424 }
425
426 QString ViewProperties::destinationDir(const QString &subDir) const
427 {
428 QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
429 path.append("/view_properties/").append(subDir);
430 return path;
431 }
432
433 QString ViewProperties::viewModePrefix() const
434 {
435 QString prefix;
436
437 switch (m_node->viewMode()) {
438 case DolphinView::IconsView:
439 prefix = QStringLiteral("Icons_");
440 break;
441 case DolphinView::CompactView:
442 prefix = QStringLiteral("Compact_");
443 break;
444 case DolphinView::DetailsView:
445 prefix = QStringLiteral("Details_");
446 break;
447 default:
448 qCWarning(DolphinDebug) << "Unknown view-mode of the view properties";
449 }
450
451 return prefix;
452 }
453
454 void ViewProperties::convertAdditionalInfo()
455 {
456 QStringList visibleRoles = m_node->visibleRoles();
457
458 const QStringList additionalInfo = m_node->additionalInfo();
459 if (!additionalInfo.isEmpty()) {
460 // Convert the obsolete values like Icons_Size, Details_Date, ...
461 // to Icons_size, Details_date, ... where the suffix just represents
462 // the internal role. One special-case must be handled: "LinkDestination"
463 // has been used for "destination".
464 visibleRoles.reserve(visibleRoles.count() + additionalInfo.count());
465 for (const QString &info : additionalInfo) {
466 QString visibleRole = info;
467 int index = visibleRole.indexOf('_');
468 if (index >= 0 && index + 1 < visibleRole.length()) {
469 ++index;
470 if (visibleRole[index] == QLatin1Char('L')) {
471 visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination"));
472 } else {
473 visibleRole[index] = visibleRole[index].toLower();
474 }
475 }
476 if (!visibleRoles.contains(visibleRole)) {
477 visibleRoles.append(visibleRole);
478 }
479 }
480 }
481
482 m_node->setAdditionalInfo(QStringList());
483 m_node->setVisibleRoles(visibleRoles);
484 m_node->setVersion(AdditionalInfoViewPropertiesVersion);
485 update();
486 }
487
488 void ViewProperties::convertNameRoleToTextRole()
489 {
490 QStringList visibleRoles = m_node->visibleRoles();
491 for (int i = 0; i < visibleRoles.count(); ++i) {
492 if (visibleRoles[i].endsWith(QLatin1String("_name"))) {
493 const int leftLength = visibleRoles[i].length() - 5;
494 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text";
495 }
496 }
497
498 QString sortRole = m_node->sortRole();
499 if (sortRole == QLatin1String("name")) {
500 sortRole = QStringLiteral("text");
501 }
502
503 m_node->setVisibleRoles(visibleRoles);
504 m_node->setSortRole(sortRole);
505 m_node->setVersion(NameRolePropertiesVersion);
506 update();
507 }
508
509 void ViewProperties::convertDateRoleToModificationTimeRole()
510 {
511 QStringList visibleRoles = m_node->visibleRoles();
512 for (int i = 0; i < visibleRoles.count(); ++i) {
513 if (visibleRoles[i].endsWith(QLatin1String("_date"))) {
514 const int leftLength = visibleRoles[i].length() - 5;
515 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime";
516 }
517 }
518
519 QString sortRole = m_node->sortRole();
520 if (sortRole == QLatin1String("date")) {
521 sortRole = QStringLiteral("modificationtime");
522 }
523
524 m_node->setVisibleRoles(visibleRoles);
525 m_node->setSortRole(sortRole);
526 m_node->setVersion(DateRolePropertiesVersion);
527 update();
528 }
529
530 bool ViewProperties::isPartOfHome(const QString &filePath)
531 {
532 // For performance reasons cache the path in a static QString
533 // (see QDir::homePath() for more details)
534 static QString homePath;
535 if (homePath.isEmpty()) {
536 homePath = QDir::homePath();
537 Q_ASSERT(!homePath.isEmpty());
538 }
539
540 return filePath.startsWith(homePath);
541 }
542
543 QString ViewProperties::directoryHashForUrl(const QUrl &url)
544 {
545 const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1);
546 QString hashString = hashValue.toBase64();
547 hashString.replace('/', '-');
548 return hashString;
549 }