viewproperties.cpp 16.3 KB
Newer Older
1 2 3 4 5 6
/*
 * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com>
 * SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
7

Peter Penz's avatar
Peter Penz committed
8
#include "viewproperties.h"
9

Peter Penz's avatar
Peter Penz committed
10 11
#include "dolphin_directoryviewpropertysettings.h"
#include "dolphin_generalsettings.h"
12
#include "dolphindebug.h"
13

14
#include <QCryptographicHash>
15

16 17
#include <KFileItem>

18
namespace {
19
    const int AdditionalInfoViewPropertiesVersion = 1;
20
    const int NameRolePropertiesVersion = 2;
21 22
    const int DateRolePropertiesVersion = 4;
    const int CurrentViewPropertiesVersion = 4;
23

24 25
    // String representation to mark the additional properties of
    // 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
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
33
ViewProperties::ViewProperties(const QUrl& url) :
34 35
    m_changedProps(false),
    m_autoSave(true),
Kevin Funk's avatar
Kevin Funk committed
36
    m_node(nullptr)
37
{
38
    GeneralSettings* settings = GeneralSettings::self();
39
    const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty();
40
    bool useDetailsViewWithPath = false;
41 42
    bool useRecentDocumentsView = false;
    bool useDownloadsView = false;
43

44
    // We try and save it to the file .directory in the directory being viewed.
45 46
    // If the directory is not writable by the user or the directory is not local,
    // we store the properties information in a local file.
47
    if (useGlobalViewProps) {
48
        m_filePath = destinationDir(QStringLiteral("global"));
49
    } else if (url.scheme().contains(QLatin1String("search"))) {
50
        m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url);
51
        useDetailsViewWithPath = true;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
52
    } else if (url.scheme() == QLatin1String("trash")) {
53
        m_filePath = destinationDir(QStringLiteral("trash"));
54
        useDetailsViewWithPath = true;
55 56 57
    } else if (url.scheme() == QLatin1String("recentdocuments")) {
        m_filePath = destinationDir(QStringLiteral("recentdocuments"));
        useRecentDocumentsView = true;
58
    } else if (url.isLocalFile()) {
59
        m_filePath = url.toLocalFile();
60 61

        bool useDestinationDir = !isPartOfHome(m_filePath);
62 63 64 65 66
        if (!useDestinationDir) {
            const KFileItem fileItem(url);
            useDestinationDir = fileItem.isSlow();
        }

67 68 69 70 71 72 73 74 75 76 77
        if (!useDestinationDir) {
            const QFileInfo dirInfo(m_filePath);
            const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName);
            useDestinationDir = !dirInfo.isWritable() || (dirInfo.size() > 0 && fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable()));
        }

        if (useDestinationDir) {
    #ifdef Q_OS_WIN
            // m_filePath probably begins with C:/ - the colon is not a valid character for paths though
            m_filePath =  QDir::separator() + m_filePath.remove(QLatin1Char(':'));
    #endif
78
            m_filePath = destinationDir(QStringLiteral("local")) + m_filePath;
79
        }
80 81 82 83

        if (m_filePath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
            useDownloadsView = true;
        }
84
    } else {
85
        m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath;
86 87
    }

88
    const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
89 90
    m_node = new ViewPropertySettings(KSharedConfig::openConfig(file));

91 92
    // If the .directory file does not exist or the timestamp is too old,
    // use default values instead.
93
    const bool useDefaultProps = (!useGlobalViewProps || useDetailsViewWithPath) &&
94
                                 (!QFile::exists(file) ||
95 96
                                  (m_node->timestamp() < settings->viewPropsTimestamp()));
    if (useDefaultProps) {
97
        if (useDetailsViewWithPath) {
98
            setViewMode(DolphinView::DetailsView);
99
            setVisibleRoles({"path"});
100 101 102 103 104 105 106 107 108 109 110
        } else if (useRecentDocumentsView || useDownloadsView) {
            setSortRole(QByteArrayLiteral("modificationtime"));
            setSortOrder(Qt::DescendingOrder);

            if (useRecentDocumentsView) {
                setViewMode(DolphinView::DetailsView);
                setVisibleRoles({QByteArrayLiteral("path")});
            } else if (useDownloadsView) {
                setSortFoldersFirst(false);
                setGroupedSorting(true);
            }
111 112
        } else {
            // The global view-properties act as default for directories without
113
            // any view-property configuration. Constructing a ViewProperties 
Lukáš Tinkl's avatar
Lukáš Tinkl committed
114
            // instance for an empty QUrl ensures that the global view-properties
115
            // are loaded.
Lukáš Tinkl's avatar
Lukáš Tinkl committed
116
            QUrl emptyUrl;
117
            ViewProperties defaultProps(emptyUrl);
118 119 120 121
            setDirProperties(defaultProps);

            m_changedProps = false;
        }
122
    }
123 124 125 126 127 128 129 130 131 132 133 134 135 136

    if (m_node->version() < CurrentViewPropertiesVersion) {
        // The view-properties have an outdated version. Convert the properties
        // to the changes of the current version.
        if (m_node->version() < AdditionalInfoViewPropertiesVersion) {
            convertAdditionalInfo();
            Q_ASSERT(m_node->version() == AdditionalInfoViewPropertiesVersion);
        }

        if (m_node->version() < NameRolePropertiesVersion) {
            convertNameRoleToTextRole();
            Q_ASSERT(m_node->version() == NameRolePropertiesVersion);
        }

137 138 139 140 141
        if (m_node->version() < DateRolePropertiesVersion) {
            convertDateRoleToModificationTimeRole();
            Q_ASSERT(m_node->version() == DateRolePropertiesVersion);
        }

142 143
        m_node->setVersion(CurrentViewPropertiesVersion);
    }
144 145 146 147 148 149 150 151 152
}

ViewProperties::~ViewProperties()
{
    if (m_changedProps && m_autoSave) {
        save();
    }

    delete m_node;
Kevin Funk's avatar
Kevin Funk committed
153
    m_node = nullptr;
154 155 156 157 158 159
}

void ViewProperties::setViewMode(DolphinView::Mode mode)
{
    if (m_node->viewMode() != mode) {
        m_node->setViewMode(mode);
160
        update();
161 162 163 164 165
    }
}

DolphinView::Mode ViewProperties::viewMode() const
{
Peter Penz's avatar
Peter Penz committed
166 167
    const int mode = qBound(0, m_node->viewMode(), 2);
    return static_cast<DolphinView::Mode>(mode);
168 169
}

170
void ViewProperties::setPreviewsShown(bool show)
171
{
172 173
    if (m_node->previewsShown() != show) {
        m_node->setPreviewsShown(show);
174
        update();
175 176 177
    }
}

178
bool ViewProperties::previewsShown() const
179
{
180
    return m_node->previewsShown();
181 182
}

183
void ViewProperties::setHiddenFilesShown(bool show)
184
{
185 186
    if (m_node->hiddenFilesShown() != show) {
        m_node->setHiddenFilesShown(show);
187
        update();
188 189 190
    }
}

191
void ViewProperties::setGroupedSorting(bool grouped)
192
{
193 194
    if (m_node->groupedSorting() != grouped) {
        m_node->setGroupedSorting(grouped);
195
        update();
196 197 198
    }
}

199
bool ViewProperties::groupedSorting() const
200
{
201
    return m_node->groupedSorting();
202 203
}

204
bool ViewProperties::hiddenFilesShown() const
205
{
206
    return m_node->hiddenFilesShown();
207 208
}

209
void ViewProperties::setSortRole(const QByteArray& role)
210
{
211 212
    if (m_node->sortRole() != role) {
        m_node->setSortRole(role);
213
        update();
214 215 216
    }
}

217
QByteArray ViewProperties::sortRole() const
218
{
219
    return m_node->sortRole().toLatin1();
220 221 222 223 224 225
}

void ViewProperties::setSortOrder(Qt::SortOrder sortOrder)
{
    if (m_node->sortOrder() != sortOrder) {
        m_node->setSortOrder(sortOrder);
226
        update();
227 228 229 230 231 232 233 234
    }
}

Qt::SortOrder ViewProperties::sortOrder() const
{
    return static_cast<Qt::SortOrder>(m_node->sortOrder());
}

235 236 237 238
void ViewProperties::setSortFoldersFirst(bool foldersFirst)
{
    if (m_node->sortFoldersFirst() != foldersFirst) {
        m_node->setSortFoldersFirst(foldersFirst);
239
        update();
240 241 242 243 244 245 246 247
    }
}

bool ViewProperties::sortFoldersFirst() const
{
    return m_node->sortFoldersFirst();
}

248
void ViewProperties::setVisibleRoles(const QList<QByteArray>& roles)
249
{
250 251 252 253
    if (roles == visibleRoles()) {
        return;
    }

254
    // See ViewProperties::visibleRoles() for the storage format
255 256 257
    // of the additional information.

    // Remove the old values stored for the current view-mode
258
    const QStringList oldVisibleRoles = m_node->visibleRoles();
259
    const QString prefix = viewModePrefix();
260 261 262 263
    QStringList newVisibleRoles = oldVisibleRoles;
    for (int i = newVisibleRoles.count() - 1; i >= 0; --i) {
        if (newVisibleRoles[i].startsWith(prefix)) {
            newVisibleRoles.removeAt(i);
264 265 266 267
        }
    }

    // Add the updated values for the current view-mode
268
    newVisibleRoles.reserve(roles.count());
Alexander Lohnau's avatar
Alexander Lohnau committed
269
    for (const QByteArray& role : roles) {
270
        newVisibleRoles.append(prefix + role);
271
    }
272

273
    if (oldVisibleRoles != newVisibleRoles) {
274
        const bool markCustomizedDetails = (m_node->viewMode() == DolphinView::DetailsView)
275
                                           && !newVisibleRoles.contains(CustomizedDetailsString);
276 277
        if (markCustomizedDetails) {
            // The additional information of the details-view has been modified. Set a marker,
278 279 280
            // so that it is allowed to also show no additional information without doing the
            // fallback to show the size and date per default.
            newVisibleRoles.append(CustomizedDetailsString);
281 282
        }

283
        m_node->setVisibleRoles(newVisibleRoles);
284
        update();
285 286 287
    }
}

288
QList<QByteArray> ViewProperties::visibleRoles() const
289
{
290 291 292
    // The shown additional information is stored for each view-mode separately as
    // string with the view-mode as prefix. Example:
    //
293
    // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size
294
    //
295
    // To get the representation as QList<QByteArray>, the current
296 297 298 299
    // view-mode must be checked and the values of this mode added to the list.
    //
    // For the details-view a special case must be respected: Per default the size
    // and date should be shown without creating a .directory file. Only if
Yuri Chornoivan's avatar
Yuri Chornoivan committed
300
    // the user explicitly has modified the properties of the details view (marked
301 302 303
    // by "CustomizedDetails"), also a details-view with no additional information
    // is accepted.

304
    QList<QByteArray> roles{"text"};
305

306
    // Iterate through all stored keys and append all roles that match to
Peter Penz's avatar
Peter Penz committed
307
    // the current view mode.
308 309
    const QString prefix = viewModePrefix();
    const int prefixLength = prefix.length();
310

311
    const QStringList visibleRoles = m_node->visibleRoles();
Alexander Lohnau's avatar
Alexander Lohnau committed
312
    for (const QString& visibleRole : visibleRoles) {
313 314
        if (visibleRole.startsWith(prefix)) {
            const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1();
315
            if (role != "text") {
316
                roles.append(role);
317 318 319
            }
        }
    }
320

321 322
    // For the details view the size and date should be shown per default
    // until the additional information has been explicitly changed by the user
323
    const bool useDefaultValues = roles.count() == 1 // "text"
324
                                  && (m_node->viewMode() == DolphinView::DetailsView)
325
                                  && !visibleRoles.contains(CustomizedDetailsString);
326
    if (useDefaultValues) {
327
        roles.append("size");
328
        roles.append("modificationtime");
329
    }
330

331
    return roles;
332 333
}

334 335 336 337 338 339 340 341 342 343 344 345 346
void ViewProperties::setHeaderColumnWidths(const QList<int>& widths)
{
    if (m_node->headerColumnWidths() != widths) {
        m_node->setHeaderColumnWidths(widths);
        update();
    }
}

QList<int> ViewProperties::headerColumnWidths() const
{
    return m_node->headerColumnWidths();
}

347 348 349
void ViewProperties::setDirProperties(const ViewProperties& props)
{
    setViewMode(props.viewMode());
350 351
    setPreviewsShown(props.previewsShown());
    setHiddenFilesShown(props.hiddenFilesShown());
352
    setGroupedSorting(props.groupedSorting());
353
    setSortRole(props.sortRole());
354
    setSortOrder(props.sortOrder());
355
    setSortFoldersFirst(props.sortFoldersFirst());
356
    setVisibleRoles(props.visibleRoles());
357
    setHeaderColumnWidths(props.headerColumnWidths());
358
    m_node->setVersion(props.m_node->version());
359 360
}

361 362 363 364 365 366 367 368 369 370
void ViewProperties::setAutoSaveEnabled(bool autoSave)
{
    m_autoSave = autoSave;
}

bool ViewProperties::isAutoSaveEnabled() const
{
    return m_autoSave;
}

371
void ViewProperties::update()
372 373 374 375 376 377 378
{
    m_changedProps = true;
    m_node->setTimestamp(QDateTime::currentDateTime());
}

void ViewProperties::save()
{
379
    qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath;
380 381
    QDir dir;
    dir.mkpath(m_filePath);
382
    m_node->setVersion(CurrentViewPropertiesVersion);
383
    m_node->save();
384 385 386
    m_changedProps = false;
}

387
bool ViewProperties::exist() const
388
{
389 390
    const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
    return QFile::exists(file);
391 392
}

393 394
QString ViewProperties::destinationDir(const QString& subDir) const
{
395
    QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
396 397
    path.append("/view_properties/").append(subDir);
    return path;
398
}
399

400 401 402 403 404
QString ViewProperties::viewModePrefix() const
{
    QString prefix;

    switch (m_node->viewMode()) {
405 406 407
    case DolphinView::IconsView:   prefix = QStringLiteral("Icons_"); break;
    case DolphinView::CompactView: prefix = QStringLiteral("Compact_"); break;
    case DolphinView::DetailsView: prefix = QStringLiteral("Details_"); break;
408
    default: qCWarning(DolphinDebug) << "Unknown view-mode of the view properties";
409 410 411
    }

    return prefix;
412
}
413

414
void ViewProperties::convertAdditionalInfo()
415 416 417 418 419 420 421 422 423 424
{
    QStringList visibleRoles;

    const QStringList additionalInfo = m_node->additionalInfo();
    if (!additionalInfo.isEmpty()) {
        // Convert the obsolete values like Icons_Size, Details_Date, ...
        // to Icons_size, Details_date, ... where the suffix just represents
        // the internal role. One special-case must be handled: "LinkDestination"
        // has been used for "destination".
        visibleRoles.reserve(additionalInfo.count());
Alexander Lohnau's avatar
Alexander Lohnau committed
425
        for (const QString& info : additionalInfo) {
426 427 428 429 430
            QString visibleRole = info;
            int index = visibleRole.indexOf('_');
            if (index >= 0 && index + 1 < visibleRole.length()) {
                ++index;
                if (visibleRole[index] == QLatin1Char('L')) {
431
                    visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination"));
432 433 434 435 436 437 438 439 440 441
                } else {
                    visibleRole[index] = visibleRole[index].toLower();
                }
            }
            visibleRoles.append(visibleRole);
        }
    }

    m_node->setAdditionalInfo(QStringList());
    m_node->setVisibleRoles(visibleRoles);
442
    m_node->setVersion(AdditionalInfoViewPropertiesVersion);
443 444 445
    update();
}

446
void ViewProperties::convertNameRoleToTextRole()
447 448 449
{
    QStringList visibleRoles = m_node->visibleRoles();
    for (int i = 0; i < visibleRoles.count(); ++i) {
Peter Penz's avatar
Peter Penz committed
450
        if (visibleRoles[i].endsWith(QLatin1String("_name"))) {
451 452 453 454 455
            const int leftLength = visibleRoles[i].length() - 5;
            visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text";
        }
    }

456 457
    QString sortRole = m_node->sortRole();
    if (sortRole == QLatin1String("name")) {
458
        sortRole = QStringLiteral("text");
459 460
    }

461
    m_node->setVisibleRoles(visibleRoles);
462
    m_node->setSortRole(sortRole);
463
    m_node->setVersion(NameRolePropertiesVersion);
464 465 466
    update();
}

467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
void ViewProperties::convertDateRoleToModificationTimeRole()
{
    QStringList visibleRoles = m_node->visibleRoles();
    for (int i = 0; i < visibleRoles.count(); ++i) {
        if (visibleRoles[i].endsWith(QLatin1String("_date"))) {
            const int leftLength = visibleRoles[i].length() - 5;
            visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime";
        }
    }

    QString sortRole = m_node->sortRole();
    if (sortRole == QLatin1String("date")) {
        sortRole = QStringLiteral("modificationtime");
    }

    m_node->setVisibleRoles(visibleRoles);
    m_node->setSortRole(sortRole);
    m_node->setVersion(DateRolePropertiesVersion);
    update();
}

488 489 490 491 492 493 494 495 496 497 498 499
bool ViewProperties::isPartOfHome(const QString& filePath)
{
    // For performance reasons cache the path in a static QString
    // (see QDir::homePath() for more details)
    static QString homePath;
    if (homePath.isEmpty()) {
        homePath = QDir::homePath();
        Q_ASSERT(!homePath.isEmpty());
    }

    return filePath.startsWith(homePath);
}
500

Lukáš Tinkl's avatar
Lukáš Tinkl committed
501
QString ViewProperties::directoryHashForUrl(const QUrl& url)
502
{
Lukáš Tinkl's avatar
Lukáš Tinkl committed
503
    const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1);
504 505 506 507
    QString hashString = hashValue.toBase64();
    hashString.replace('/', '-');
    return hashString;
}