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