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