File indexing completed on 2024-04-28 05:45:26
0001 /* 0002 * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com> 0003 * SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "viewproperties.h" 0009 0010 #include "dolphin_directoryviewpropertysettings.h" 0011 #include "dolphin_generalsettings.h" 0012 #include "dolphindebug.h" 0013 0014 #include <QCryptographicHash> 0015 0016 #include <KFileItem> 0017 0018 namespace 0019 { 0020 const int AdditionalInfoViewPropertiesVersion = 1; 0021 const int NameRolePropertiesVersion = 2; 0022 const int DateRolePropertiesVersion = 4; 0023 const int CurrentViewPropertiesVersion = 4; 0024 0025 // String representation to mark the additional properties of 0026 // the details view as customized by the user. See 0027 // ViewProperties::visibleRoles() for more information. 0028 const char CustomizedDetailsString[] = "CustomizedDetails"; 0029 0030 // Filename that is used for storing the properties 0031 const char ViewPropertiesFileName[] = ".directory"; 0032 } 0033 0034 ViewProperties::ViewProperties(const QUrl &url) 0035 : m_changedProps(false) 0036 , m_autoSave(true) 0037 , m_node(nullptr) 0038 { 0039 GeneralSettings *settings = GeneralSettings::self(); 0040 const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty(); 0041 bool useSearchView = false; 0042 bool useTrashView = false; 0043 bool useRecentDocumentsView = false; 0044 bool useDownloadsView = false; 0045 0046 // We try and save it to the file .directory in the directory being viewed. 0047 // If the directory is not writable by the user or the directory is not local, 0048 // we store the properties information in a local file. 0049 if (useGlobalViewProps) { 0050 m_filePath = destinationDir(QStringLiteral("global")); 0051 } else if (url.scheme().contains(QLatin1String("search"))) { 0052 m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url); 0053 useSearchView = true; 0054 } else if (url.scheme() == QLatin1String("trash")) { 0055 m_filePath = destinationDir(QStringLiteral("trash")); 0056 useTrashView = true; 0057 } else if (url.scheme() == QLatin1String("recentdocuments")) { 0058 m_filePath = destinationDir(QStringLiteral("recentdocuments")); 0059 useRecentDocumentsView = true; 0060 } else if (url.scheme() == QLatin1String("recentlyused")) { 0061 m_filePath = destinationDir(QStringLiteral("recentlyused")); 0062 useRecentDocumentsView = true; 0063 } else if (url.scheme() == QLatin1String("timeline")) { 0064 m_filePath = destinationDir(QStringLiteral("timeline")); 0065 useRecentDocumentsView = true; 0066 } else if (url.isLocalFile()) { 0067 m_filePath = url.toLocalFile(); 0068 0069 bool useDestinationDir = !isPartOfHome(m_filePath); 0070 if (!useDestinationDir) { 0071 const KFileItem fileItem(url); 0072 useDestinationDir = fileItem.isSlow(); 0073 } 0074 0075 if (!useDestinationDir) { 0076 const QFileInfo dirInfo(m_filePath); 0077 const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName); 0078 useDestinationDir = !dirInfo.isWritable() || (dirInfo.size() > 0 && fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable())); 0079 } 0080 0081 if (useDestinationDir) { 0082 #ifdef Q_OS_WIN 0083 // m_filePath probably begins with C:/ - the colon is not a valid character for paths though 0084 m_filePath = QDir::separator() + m_filePath.remove(QLatin1Char(':')); 0085 #endif 0086 m_filePath = destinationDir(QStringLiteral("local")) + m_filePath; 0087 } 0088 0089 if (m_filePath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) { 0090 useDownloadsView = true; 0091 } 0092 } else { 0093 m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath; 0094 } 0095 0096 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName; 0097 m_node = new ViewPropertySettings(KSharedConfig::openConfig(file)); 0098 0099 // If the .directory file does not exist or the timestamp is too old, 0100 // use default values instead. 0101 const bool useDefaultProps = (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView) 0102 && (!QFile::exists(file) || (m_node->timestamp() < settings->viewPropsTimestamp())); 0103 if (useDefaultProps) { 0104 if (useSearchView) { 0105 const QString path = url.path(); 0106 0107 if (path == QLatin1String("/images")) { 0108 setViewMode(DolphinView::IconsView); 0109 setPreviewsShown(true); 0110 setVisibleRoles({"text", "dimensions", "imageDateTime"}); 0111 } else if (path == QLatin1String("/audio")) { 0112 setViewMode(DolphinView::DetailsView); 0113 setVisibleRoles({"text", "artist", "album", "duration"}); 0114 } else if (path == QLatin1String("/videos")) { 0115 setViewMode(DolphinView::IconsView); 0116 setPreviewsShown(true); 0117 setVisibleRoles({"text"}); 0118 } else { 0119 setViewMode(DolphinView::DetailsView); 0120 setVisibleRoles({"text", "path", "modificationtime"}); 0121 } 0122 } else if (useTrashView) { 0123 setViewMode(DolphinView::DetailsView); 0124 setVisibleRoles({"text", "path", "deletiontime"}); 0125 } else if (useRecentDocumentsView || useDownloadsView) { 0126 setSortRole(QByteArrayLiteral("modificationtime")); 0127 setSortOrder(Qt::DescendingOrder); 0128 setSortFoldersFirst(false); 0129 setGroupedSorting(true); 0130 0131 if (useRecentDocumentsView) { 0132 setViewMode(DolphinView::DetailsView); 0133 setVisibleRoles({"text", "path", "modificationtime"}); 0134 } 0135 } else { 0136 // The global view-properties act as default for directories without 0137 // any view-property configuration. Constructing a ViewProperties 0138 // instance for an empty QUrl ensures that the global view-properties 0139 // are loaded. 0140 QUrl emptyUrl; 0141 ViewProperties defaultProps(emptyUrl); 0142 setDirProperties(defaultProps); 0143 0144 m_changedProps = false; 0145 } 0146 } 0147 0148 if (m_node->version() < CurrentViewPropertiesVersion) { 0149 // The view-properties have an outdated version. Convert the properties 0150 // to the changes of the current version. 0151 if (m_node->version() < AdditionalInfoViewPropertiesVersion) { 0152 convertAdditionalInfo(); 0153 Q_ASSERT(m_node->version() == AdditionalInfoViewPropertiesVersion); 0154 } 0155 0156 if (m_node->version() < NameRolePropertiesVersion) { 0157 convertNameRoleToTextRole(); 0158 Q_ASSERT(m_node->version() == NameRolePropertiesVersion); 0159 } 0160 0161 if (m_node->version() < DateRolePropertiesVersion) { 0162 convertDateRoleToModificationTimeRole(); 0163 Q_ASSERT(m_node->version() == DateRolePropertiesVersion); 0164 } 0165 0166 m_node->setVersion(CurrentViewPropertiesVersion); 0167 } 0168 } 0169 0170 ViewProperties::~ViewProperties() 0171 { 0172 if (m_changedProps && m_autoSave) { 0173 save(); 0174 } 0175 0176 delete m_node; 0177 m_node = nullptr; 0178 } 0179 0180 void ViewProperties::setViewMode(DolphinView::Mode mode) 0181 { 0182 if (m_node->viewMode() != mode) { 0183 m_node->setViewMode(mode); 0184 update(); 0185 } 0186 } 0187 0188 DolphinView::Mode ViewProperties::viewMode() const 0189 { 0190 const int mode = qBound(0, m_node->viewMode(), 2); 0191 return static_cast<DolphinView::Mode>(mode); 0192 } 0193 0194 void ViewProperties::setPreviewsShown(bool show) 0195 { 0196 if (m_node->previewsShown() != show) { 0197 m_node->setPreviewsShown(show); 0198 update(); 0199 } 0200 } 0201 0202 bool ViewProperties::previewsShown() const 0203 { 0204 return m_node->previewsShown(); 0205 } 0206 0207 void ViewProperties::setHiddenFilesShown(bool show) 0208 { 0209 if (m_node->hiddenFilesShown() != show) { 0210 m_node->setHiddenFilesShown(show); 0211 update(); 0212 } 0213 } 0214 0215 void ViewProperties::setGroupedSorting(bool grouped) 0216 { 0217 if (m_node->groupedSorting() != grouped) { 0218 m_node->setGroupedSorting(grouped); 0219 update(); 0220 } 0221 } 0222 0223 bool ViewProperties::groupedSorting() const 0224 { 0225 return m_node->groupedSorting(); 0226 } 0227 0228 bool ViewProperties::hiddenFilesShown() const 0229 { 0230 return m_node->hiddenFilesShown(); 0231 } 0232 0233 void ViewProperties::setSortRole(const QByteArray &role) 0234 { 0235 if (m_node->sortRole() != role) { 0236 m_node->setSortRole(role); 0237 update(); 0238 } 0239 } 0240 0241 QByteArray ViewProperties::sortRole() const 0242 { 0243 return m_node->sortRole().toLatin1(); 0244 } 0245 0246 void ViewProperties::setSortOrder(Qt::SortOrder sortOrder) 0247 { 0248 if (m_node->sortOrder() != sortOrder) { 0249 m_node->setSortOrder(sortOrder); 0250 update(); 0251 } 0252 } 0253 0254 Qt::SortOrder ViewProperties::sortOrder() const 0255 { 0256 return static_cast<Qt::SortOrder>(m_node->sortOrder()); 0257 } 0258 0259 void ViewProperties::setSortFoldersFirst(bool foldersFirst) 0260 { 0261 if (m_node->sortFoldersFirst() != foldersFirst) { 0262 m_node->setSortFoldersFirst(foldersFirst); 0263 update(); 0264 } 0265 } 0266 0267 bool ViewProperties::sortFoldersFirst() const 0268 { 0269 return m_node->sortFoldersFirst(); 0270 } 0271 0272 void ViewProperties::setSortHiddenLast(bool hiddenLast) 0273 { 0274 if (m_node->sortHiddenLast() != hiddenLast) { 0275 m_node->setSortHiddenLast(hiddenLast); 0276 update(); 0277 } 0278 } 0279 0280 bool ViewProperties::sortHiddenLast() const 0281 { 0282 return m_node->sortHiddenLast(); 0283 } 0284 0285 void ViewProperties::setVisibleRoles(const QList<QByteArray> &roles) 0286 { 0287 if (roles == visibleRoles()) { 0288 return; 0289 } 0290 0291 // See ViewProperties::visibleRoles() for the storage format 0292 // of the additional information. 0293 0294 // Remove the old values stored for the current view-mode 0295 const QStringList oldVisibleRoles = m_node->visibleRoles(); 0296 const QString prefix = viewModePrefix(); 0297 QStringList newVisibleRoles = oldVisibleRoles; 0298 for (int i = newVisibleRoles.count() - 1; i >= 0; --i) { 0299 if (newVisibleRoles[i].startsWith(prefix)) { 0300 newVisibleRoles.removeAt(i); 0301 } 0302 } 0303 0304 // Add the updated values for the current view-mode 0305 newVisibleRoles.reserve(roles.count()); 0306 for (const QByteArray &role : roles) { 0307 newVisibleRoles.append(prefix + role); 0308 } 0309 0310 if (oldVisibleRoles != newVisibleRoles) { 0311 const bool markCustomizedDetails = (m_node->viewMode() == DolphinView::DetailsView) && !newVisibleRoles.contains(CustomizedDetailsString); 0312 if (markCustomizedDetails) { 0313 // The additional information of the details-view has been modified. Set a marker, 0314 // so that it is allowed to also show no additional information without doing the 0315 // fallback to show the size and date per default. 0316 newVisibleRoles.append(CustomizedDetailsString); 0317 } 0318 0319 m_node->setVisibleRoles(newVisibleRoles); 0320 update(); 0321 } 0322 } 0323 0324 QList<QByteArray> ViewProperties::visibleRoles() const 0325 { 0326 // The shown additional information is stored for each view-mode separately as 0327 // string with the view-mode as prefix. Example: 0328 // 0329 // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size 0330 // 0331 // To get the representation as QList<QByteArray>, the current 0332 // view-mode must be checked and the values of this mode added to the list. 0333 // 0334 // For the details-view a special case must be respected: Per default the size 0335 // and date should be shown without creating a .directory file. Only if 0336 // the user explicitly has modified the properties of the details view (marked 0337 // by "CustomizedDetails"), also a details-view with no additional information 0338 // is accepted. 0339 0340 QList<QByteArray> roles{"text"}; 0341 0342 // Iterate through all stored keys and append all roles that match to 0343 // the current view mode. 0344 const QString prefix = viewModePrefix(); 0345 const int prefixLength = prefix.length(); 0346 0347 const QStringList visibleRoles = m_node->visibleRoles(); 0348 for (const QString &visibleRole : visibleRoles) { 0349 if (visibleRole.startsWith(prefix)) { 0350 const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1(); 0351 if (role != "text") { 0352 roles.append(role); 0353 } 0354 } 0355 } 0356 0357 // For the details view the size and date should be shown per default 0358 // until the additional information has been explicitly changed by the user 0359 const bool useDefaultValues = roles.count() == 1 // "text" 0360 && (m_node->viewMode() == DolphinView::DetailsView) && !visibleRoles.contains(CustomizedDetailsString); 0361 if (useDefaultValues) { 0362 roles.append("size"); 0363 roles.append("modificationtime"); 0364 } 0365 0366 return roles; 0367 } 0368 0369 void ViewProperties::setHeaderColumnWidths(const QList<int> &widths) 0370 { 0371 if (m_node->headerColumnWidths() != widths) { 0372 m_node->setHeaderColumnWidths(widths); 0373 update(); 0374 } 0375 } 0376 0377 QList<int> ViewProperties::headerColumnWidths() const 0378 { 0379 return m_node->headerColumnWidths(); 0380 } 0381 0382 void ViewProperties::setDirProperties(const ViewProperties &props) 0383 { 0384 setViewMode(props.viewMode()); 0385 setPreviewsShown(props.previewsShown()); 0386 setHiddenFilesShown(props.hiddenFilesShown()); 0387 setGroupedSorting(props.groupedSorting()); 0388 setSortRole(props.sortRole()); 0389 setSortOrder(props.sortOrder()); 0390 setSortFoldersFirst(props.sortFoldersFirst()); 0391 setSortHiddenLast(props.sortHiddenLast()); 0392 setVisibleRoles(props.visibleRoles()); 0393 setHeaderColumnWidths(props.headerColumnWidths()); 0394 m_node->setVersion(props.m_node->version()); 0395 } 0396 0397 void ViewProperties::setAutoSaveEnabled(bool autoSave) 0398 { 0399 m_autoSave = autoSave; 0400 } 0401 0402 bool ViewProperties::isAutoSaveEnabled() const 0403 { 0404 return m_autoSave; 0405 } 0406 0407 void ViewProperties::update() 0408 { 0409 m_changedProps = true; 0410 m_node->setTimestamp(QDateTime::currentDateTime()); 0411 } 0412 0413 void ViewProperties::save() 0414 { 0415 qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath; 0416 QDir dir; 0417 dir.mkpath(m_filePath); 0418 m_node->setVersion(CurrentViewPropertiesVersion); 0419 m_node->save(); 0420 m_changedProps = false; 0421 } 0422 0423 bool ViewProperties::exist() const 0424 { 0425 const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName; 0426 return QFile::exists(file); 0427 } 0428 0429 QString ViewProperties::destinationDir(const QString &subDir) const 0430 { 0431 QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); 0432 path.append("/view_properties/").append(subDir); 0433 return path; 0434 } 0435 0436 QString ViewProperties::viewModePrefix() const 0437 { 0438 QString prefix; 0439 0440 switch (m_node->viewMode()) { 0441 case DolphinView::IconsView: 0442 prefix = QStringLiteral("Icons_"); 0443 break; 0444 case DolphinView::CompactView: 0445 prefix = QStringLiteral("Compact_"); 0446 break; 0447 case DolphinView::DetailsView: 0448 prefix = QStringLiteral("Details_"); 0449 break; 0450 default: 0451 qCWarning(DolphinDebug) << "Unknown view-mode of the view properties"; 0452 } 0453 0454 return prefix; 0455 } 0456 0457 void ViewProperties::convertAdditionalInfo() 0458 { 0459 QStringList visibleRoles = m_node->visibleRoles(); 0460 0461 const QStringList additionalInfo = m_node->additionalInfo(); 0462 if (!additionalInfo.isEmpty()) { 0463 // Convert the obsolete values like Icons_Size, Details_Date, ... 0464 // to Icons_size, Details_date, ... where the suffix just represents 0465 // the internal role. One special-case must be handled: "LinkDestination" 0466 // has been used for "destination". 0467 visibleRoles.reserve(visibleRoles.count() + additionalInfo.count()); 0468 for (const QString &info : additionalInfo) { 0469 QString visibleRole = info; 0470 int index = visibleRole.indexOf('_'); 0471 if (index >= 0 && index + 1 < visibleRole.length()) { 0472 ++index; 0473 if (visibleRole[index] == QLatin1Char('L')) { 0474 visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination")); 0475 } else { 0476 visibleRole[index] = visibleRole[index].toLower(); 0477 } 0478 } 0479 if (!visibleRoles.contains(visibleRole)) { 0480 visibleRoles.append(visibleRole); 0481 } 0482 } 0483 } 0484 0485 m_node->setAdditionalInfo(QStringList()); 0486 m_node->setVisibleRoles(visibleRoles); 0487 m_node->setVersion(AdditionalInfoViewPropertiesVersion); 0488 update(); 0489 } 0490 0491 void ViewProperties::convertNameRoleToTextRole() 0492 { 0493 QStringList visibleRoles = m_node->visibleRoles(); 0494 for (int i = 0; i < visibleRoles.count(); ++i) { 0495 if (visibleRoles[i].endsWith(QLatin1String("_name"))) { 0496 const int leftLength = visibleRoles[i].length() - 5; 0497 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text"; 0498 } 0499 } 0500 0501 QString sortRole = m_node->sortRole(); 0502 if (sortRole == QLatin1String("name")) { 0503 sortRole = QStringLiteral("text"); 0504 } 0505 0506 m_node->setVisibleRoles(visibleRoles); 0507 m_node->setSortRole(sortRole); 0508 m_node->setVersion(NameRolePropertiesVersion); 0509 update(); 0510 } 0511 0512 void ViewProperties::convertDateRoleToModificationTimeRole() 0513 { 0514 QStringList visibleRoles = m_node->visibleRoles(); 0515 for (int i = 0; i < visibleRoles.count(); ++i) { 0516 if (visibleRoles[i].endsWith(QLatin1String("_date"))) { 0517 const int leftLength = visibleRoles[i].length() - 5; 0518 visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime"; 0519 } 0520 } 0521 0522 QString sortRole = m_node->sortRole(); 0523 if (sortRole == QLatin1String("date")) { 0524 sortRole = QStringLiteral("modificationtime"); 0525 } 0526 0527 m_node->setVisibleRoles(visibleRoles); 0528 m_node->setSortRole(sortRole); 0529 m_node->setVersion(DateRolePropertiesVersion); 0530 update(); 0531 } 0532 0533 bool ViewProperties::isPartOfHome(const QString &filePath) 0534 { 0535 // For performance reasons cache the path in a static QString 0536 // (see QDir::homePath() for more details) 0537 static QString homePath; 0538 if (homePath.isEmpty()) { 0539 homePath = QDir::homePath(); 0540 Q_ASSERT(!homePath.isEmpty()); 0541 } 0542 0543 return filePath.startsWith(homePath); 0544 } 0545 0546 QString ViewProperties::directoryHashForUrl(const QUrl &url) 0547 { 0548 const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1); 0549 QString hashString = hashValue.toBase64(); 0550 hashString.replace('/', '-'); 0551 return hashString; 0552 }