]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/viewproperties.cpp
[ViewProperties] Apply better default roles for special views
[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 const int AdditionalInfoViewPropertiesVersion = 1;
20 const int NameRolePropertiesVersion = 2;
21 const int DateRolePropertiesVersion = 4;
22 const int CurrentViewPropertiesVersion = 4;
23
24 // String representation to mark the additional properties of
25 // the details view as customized by the user. See
26 // ViewProperties::visibleRoles() for more information.
27 const char CustomizedDetailsString[] = "CustomizedDetails";
28
29 // Filename that is used for storing the properties
30 const char ViewPropertiesFileName[] = ".directory";
31 }
32
33 ViewProperties::ViewProperties(const QUrl& url) :
34 m_changedProps(false),
35 m_autoSave(true),
36 m_node(nullptr)
37 {
38 GeneralSettings* settings = GeneralSettings::self();
39 const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty();
40 bool useSearchView = false;
41 bool useTrashView = false;
42 bool useRecentDocumentsView = false;
43 bool useDownloadsView = false;
44
45 // We try and save it to the file .directory in the directory being viewed.
46 // If the directory is not writable by the user or the directory is not local,
47 // we store the properties information in a local file.
48 if (useGlobalViewProps) {
49 m_filePath = destinationDir(QStringLiteral("global"));
50 } else if (url.scheme().contains(QLatin1String("search"))) {
51 m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url);
52 useSearchView = true;
53 } else if (url.scheme() == QLatin1String("trash")) {
54 m_filePath = destinationDir(QStringLiteral("trash"));
55 useTrashView = true;
56 } else if (url.scheme() == QLatin1String("recentdocuments")) {
57 m_filePath = destinationDir(QStringLiteral("recentdocuments"));
58 useRecentDocumentsView = true;
59 } else if (url.scheme() == QLatin1String("recentlyused")) {
60 m_filePath = destinationDir(QStringLiteral("recentlyused"));
61 useRecentDocumentsView = true;
62 } else if (url.scheme() == QLatin1String("timeline")) {
63 m_filePath = destinationDir(QStringLiteral("timeline"));
64 useRecentDocumentsView = true;
65 } else if (url.isLocalFile()) {
66 m_filePath = url.toLocalFile();
67
68 bool useDestinationDir = !isPartOfHome(m_filePath);
69 if (!useDestinationDir) {
70 const KFileItem fileItem(url);
71 useDestinationDir = fileItem.isSlow();
72 }
73
74 if (!useDestinationDir) {
75 const QFileInfo dirInfo(m_filePath);
76 const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName);
77 useDestinationDir = !dirInfo.isWritable() || (dirInfo.size() > 0 && fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable()));
78 }
79
80 if (useDestinationDir) {
81 #ifdef Q_OS_WIN
82 // m_filePath probably begins with C:/ - the colon is not a valid character for paths though
83 m_filePath = QDir::separator() + m_filePath.remove(QLatin1Char(':'));
84 #endif
85 m_filePath = destinationDir(QStringLiteral("local")) + m_filePath;
86 }
87
88 if (m_filePath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
89 useDownloadsView = true;
90 }
91 } else {
92 m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath;
93 }
94
95 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
96 m_node = new ViewPropertySettings(KSharedConfig::openConfig(file));
97
98 // If the .directory file does not exist or the timestamp is too old,
99 // use default values instead.
100 const bool useDefaultProps = (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView) &&
101 (!QFile::exists(file) ||
102 (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)
312 && !newVisibleRoles.contains(CustomizedDetailsString);
313 if (markCustomizedDetails) {
314 // The additional information of the details-view has been modified. Set a marker,
315 // so that it is allowed to also show no additional information without doing the
316 // fallback to show the size and date per default.
317 newVisibleRoles.append(CustomizedDetailsString);
318 }
319
320 m_node->setVisibleRoles(newVisibleRoles);
321 update();
322 }
323 }
324
325 QList<QByteArray> ViewProperties::visibleRoles() const
326 {
327 // The shown additional information is stored for each view-mode separately as
328 // string with the view-mode as prefix. Example:
329 //
330 // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size
331 //
332 // To get the representation as QList<QByteArray>, the current
333 // view-mode must be checked and the values of this mode added to the list.
334 //
335 // For the details-view a special case must be respected: Per default the size
336 // and date should be shown without creating a .directory file. Only if
337 // the user explicitly has modified the properties of the details view (marked
338 // by "CustomizedDetails"), also a details-view with no additional information
339 // is accepted.
340
341 QList<QByteArray> roles{"text"};
342
343 // Iterate through all stored keys and append all roles that match to
344 // the current view mode.
345 const QString prefix = viewModePrefix();
346 const int prefixLength = prefix.length();
347
348 const QStringList visibleRoles = m_node->visibleRoles();
349 for (const QString& visibleRole : visibleRoles) {
350 if (visibleRole.startsWith(prefix)) {
351 const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1();
352 if (role != "text") {
353 roles.append(role);
354 }
355 }
356 }
357
358 // For the details view the size and date should be shown per default
359 // until the additional information has been explicitly changed by the user
360 const bool useDefaultValues = roles.count() == 1 // "text"
361 && (m_node->viewMode() == DolphinView::DetailsView)
362 && !visibleRoles.contains(CustomizedDetailsString);
363 if (useDefaultValues) {
364 roles.append("size");
365 roles.append("modificationtime");
366 }
367
368 return roles;
369 }
370
371 void ViewProperties::setHeaderColumnWidths(const QList<int>& widths)
372 {
373 if (m_node->headerColumnWidths() != widths) {
374 m_node->setHeaderColumnWidths(widths);
375 update();
376 }
377 }
378
379 QList<int> ViewProperties::headerColumnWidths() const
380 {
381 return m_node->headerColumnWidths();
382 }
383
384 void ViewProperties::setDirProperties(const ViewProperties& props)
385 {
386 setViewMode(props.viewMode());
387 setPreviewsShown(props.previewsShown());
388 setHiddenFilesShown(props.hiddenFilesShown());
389 setGroupedSorting(props.groupedSorting());
390 setSortRole(props.sortRole());
391 setSortOrder(props.sortOrder());
392 setSortFoldersFirst(props.sortFoldersFirst());
393 setSortHiddenLast(props.sortHiddenLast());
394 setVisibleRoles(props.visibleRoles());
395 setHeaderColumnWidths(props.headerColumnWidths());
396 m_node->setVersion(props.m_node->version());
397 }
398
399 void ViewProperties::setAutoSaveEnabled(bool autoSave)
400 {
401 m_autoSave = autoSave;
402 }
403
404 bool ViewProperties::isAutoSaveEnabled() const
405 {
406 return m_autoSave;
407 }
408
409 void ViewProperties::update()
410 {
411 m_changedProps = true;
412 m_node->setTimestamp(QDateTime::currentDateTime());
413 }
414
415 void ViewProperties::save()
416 {
417 qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath;
418 QDir dir;
419 dir.mkpath(m_filePath);
420 m_node->setVersion(CurrentViewPropertiesVersion);
421 m_node->save();
422 m_changedProps = false;
423 }
424
425 bool ViewProperties::exist() const
426 {
427 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
428 return QFile::exists(file);
429 }
430
431 QString ViewProperties::destinationDir(const QString& subDir) const
432 {
433 QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
434 path.append("/view_properties/").append(subDir);
435 return path;
436 }
437
438 QString ViewProperties::viewModePrefix() const
439 {
440 QString prefix;
441
442 switch (m_node->viewMode()) {
443 case DolphinView::IconsView: prefix = QStringLiteral("Icons_"); break;
444 case DolphinView::CompactView: prefix = QStringLiteral("Compact_"); break;
445 case DolphinView::DetailsView: prefix = QStringLiteral("Details_"); break;
446 default: qCWarning(DolphinDebug) << "Unknown view-mode of the view properties";
447 }
448
449 return prefix;
450 }
451
452 void ViewProperties::convertAdditionalInfo()
453 {
454 QStringList visibleRoles = m_node->visibleRoles();
455
456 const QStringList additionalInfo = m_node->additionalInfo();
457 if (!additionalInfo.isEmpty()) {
458 // Convert the obsolete values like Icons_Size, Details_Date, ...
459 // to Icons_size, Details_date, ... where the suffix just represents
460 // the internal role. One special-case must be handled: "LinkDestination"
461 // has been used for "destination".
462 visibleRoles.reserve(visibleRoles.count() + additionalInfo.count());
463 for (const QString& info : additionalInfo) {
464 QString visibleRole = info;
465 int index = visibleRole.indexOf('_');
466 if (index >= 0 && index + 1 < visibleRole.length()) {
467 ++index;
468 if (visibleRole[index] == QLatin1Char('L')) {
469 visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination"));
470 } else {
471 visibleRole[index] = visibleRole[index].toLower();
472 }
473 }
474 if (!visibleRoles.contains(visibleRole)) {
475 visibleRoles.append(visibleRole);
476 }
477 }
478 }
479
480 m_node->setAdditionalInfo(QStringList());
481 m_node->setVisibleRoles(visibleRoles);
482 m_node->setVersion(AdditionalInfoViewPropertiesVersion);
483 update();
484 }
485
486 void ViewProperties::convertNameRoleToTextRole()
487 {
488 QStringList visibleRoles = m_node->visibleRoles();
489 for (int i = 0; i < visibleRoles.count(); ++i) {
490 if (visibleRoles[i].endsWith(QLatin1String("_name"))) {
491 const int leftLength = visibleRoles[i].length() - 5;
492 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text";
493 }
494 }
495
496 QString sortRole = m_node->sortRole();
497 if (sortRole == QLatin1String("name")) {
498 sortRole = QStringLiteral("text");
499 }
500
501 m_node->setVisibleRoles(visibleRoles);
502 m_node->setSortRole(sortRole);
503 m_node->setVersion(NameRolePropertiesVersion);
504 update();
505 }
506
507 void ViewProperties::convertDateRoleToModificationTimeRole()
508 {
509 QStringList visibleRoles = m_node->visibleRoles();
510 for (int i = 0; i < visibleRoles.count(); ++i) {
511 if (visibleRoles[i].endsWith(QLatin1String("_date"))) {
512 const int leftLength = visibleRoles[i].length() - 5;
513 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime";
514 }
515 }
516
517 QString sortRole = m_node->sortRole();
518 if (sortRole == QLatin1String("date")) {
519 sortRole = QStringLiteral("modificationtime");
520 }
521
522 m_node->setVisibleRoles(visibleRoles);
523 m_node->setSortRole(sortRole);
524 m_node->setVersion(DateRolePropertiesVersion);
525 update();
526 }
527
528 bool ViewProperties::isPartOfHome(const QString& filePath)
529 {
530 // For performance reasons cache the path in a static QString
531 // (see QDir::homePath() for more details)
532 static QString homePath;
533 if (homePath.isEmpty()) {
534 homePath = QDir::homePath();
535 Q_ASSERT(!homePath.isEmpty());
536 }
537
538 return filePath.startsWith(homePath);
539 }
540
541 QString ViewProperties::directoryHashForUrl(const QUrl& url)
542 {
543 const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1);
544 QString hashString = hashValue.toBase64();
545 hashString.replace('/', '-');
546 return hashString;
547 }