]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
GIT_SILENT Sync po/docbooks with svn
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 6617fbec69fb9a74442aecce49ed489430fd9ef0..4386bca16b6ada60a808a9764ff83ed8d74826ed 100644 (file)
@@ -8,17 +8,21 @@
 
 #include "kfileitemmodel.h"
 
-#include "dolphin_detailsmodesettings.h"
+#include "dolphin_contentdisplaysettings.h"
 #include "dolphin_generalsettings.h"
 #include "dolphindebug.h"
 #include "private/kfileitemmodelsortalgorithm.h"
+#include "views/draganddrophelper.h"
 
 #include <KDirLister>
 #include <KIO/Job>
+#include <KIO/ListJob>
 #include <KLocalizedString>
 #include <KUrlMimeData>
-#include <kio_version.h>
 
+#ifndef QT_NO_ACCESSIBILITY
+#include <QAccessible>
+#endif
 #include <QElapsedTimer>
 #include <QIcon>
 #include <QMimeData>
@@ -26,7 +30,6 @@
 #include <QRecursiveMutex>
 #include <QTimer>
 #include <QWidget>
-#include <algorithm>
 #include <klazylocalizedstring.h>
 
 Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
@@ -100,11 +103,14 @@ KFileItemModel::KFileItemModel(QObject *parent)
     // for a lot of items within a quite small timeslot. To prevent expensive resortings the
     // resorting is postponed until the timer has been exceeded.
     m_resortAllItemsTimer = new QTimer(this);
-    m_resortAllItemsTimer->setInterval(500);
+    m_resortAllItemsTimer->setInterval(100); // 100 is a middle ground between sorting too frequently which makes the view unreadable
+                                             // and sorting too infrequently which leads to users seeing an outdated sort order.
     m_resortAllItemsTimer->setSingleShot(true);
     connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems);
 
     connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged);
+
+    setShowTrashMime(m_dirLister->showHiddenFiles() || !GeneralSettings::hideXTrashFile());
 }
 
 KFileItemModel::~KFileItemModel()
@@ -199,13 +205,20 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant> &value
         return false;
     }
 
-    m_itemData[index]->values = currentValues;
     if (changedRoles.contains("text")) {
         QUrl url = m_itemData[index]->item.url();
+        m_items.remove(url);
         url = url.adjusted(QUrl::RemoveFilename);
         url.setPath(url.path() + currentValues["text"].toString());
         m_itemData[index]->item.setUrl(url);
+        m_items.insert(url, index);
+
+        if (!changedRoles.contains("url")) {
+            changedRoles.insert("url");
+            currentValues["url"] = url;
+        }
     }
+    m_itemData[index]->values = currentValues;
 
     emitItemsChangedAndTriggerResorting(KItemRangeList() << KItemRange(index, 1), changedRoles);
 
@@ -238,13 +251,31 @@ bool KFileItemModel::sortHiddenLast() const
     return m_sortHiddenLast;
 }
 
+void KFileItemModel::setShowTrashMime(bool showTrashMime)
+{
+    const auto trashMime = QStringLiteral("application/x-trash");
+    QStringList excludeFilter = m_filter.excludeMimeTypes();
+
+    if (showTrashMime) {
+        excludeFilter.removeAll(trashMime);
+    } else if (!excludeFilter.contains(trashMime)) {
+        excludeFilter.append(trashMime);
+    }
+
+    setExcludeMimeTypeFilter(excludeFilter);
+}
+
+void KFileItemModel::scheduleResortAllItems()
+{
+    if (!m_resortAllItemsTimer->isActive()) {
+        m_resortAllItemsTimer->start();
+    }
+}
+
 void KFileItemModel::setShowHiddenFiles(bool show)
 {
-#if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0)
-    m_dirLister->setShowingDotFiles(show);
-#else
     m_dirLister->setShowHiddenFiles(show);
-#endif
+    setShowTrashMime(show || !GeneralSettings::hideXTrashFile());
     m_dirLister->emitChanges();
     if (show) {
         dispatchPendingItemsToInsert();
@@ -253,11 +284,7 @@ void KFileItemModel::setShowHiddenFiles(bool show)
 
 bool KFileItemModel::showHiddenFiles() const
 {
-#if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0)
-    return m_dirLister->showingDotFiles();
-#else
     return m_dirLister->showHiddenFiles();
-#endif
 }
 
 void KFileItemModel::setShowDirectoriesOnly(bool enabled)
@@ -308,16 +335,32 @@ QMimeData *KFileItemModel::createMimeData(const KItemSet &indexes) const
     return data;
 }
 
+namespace
+{
+QString removeMarks(const QString &original)
+{
+    const auto normalized = original.normalized(QString::NormalizationForm_D);
+    QString res;
+    for (auto ch : normalized) {
+        if (!ch.isMark()) {
+            res.append(ch);
+        }
+    }
+    return res;
+}
+}
+
 int KFileItemModel::indexForKeyboardSearch(const QString &text, int startFromIndex) const
 {
+    const auto noMarkText = removeMarks(text);
     startFromIndex = qMax(0, startFromIndex);
     for (int i = startFromIndex; i < count(); ++i) {
-        if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) {
+        if (removeMarks(fileItem(i).text()).startsWith(noMarkText, Qt::CaseInsensitive)) {
             return i;
         }
     }
     for (int i = 0; i < startFromIndex; ++i) {
-        if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) {
+        if (removeMarks(fileItem(i).text()).startsWith(noMarkText, Qt::CaseInsensitive)) {
             return i;
         }
     }
@@ -332,7 +375,18 @@ bool KFileItemModel::supportsDropping(int index) const
     } else {
         item = fileItem(index);
     }
-    return !item.isNull() && ((item.isDir() && item.isWritable()) || item.isDesktopFile());
+    return !item.isNull() && DragAndDropHelper::supportsDropping(item);
+}
+
+bool KFileItemModel::canEnterOnHover(int index) const
+{
+    KFileItem item;
+    if (index == -1) {
+        item = rootItem();
+    } else {
+        item = fileItem(index);
+    }
+    return !item.isNull() && (item.isDir() || item.isDesktopFile());
 }
 
 QString KFileItemModel::roleDescription(const QByteArray &role) const
@@ -600,9 +654,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
 
         m_expandedDirs.remove(targetUrl);
         m_dirLister->stop(url);
-#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0)
         m_dirLister->forgetDirs(url);
-#endif
 
         const int parentLevel = expandedParentsCount(index);
         const int itemCount = m_itemData.count();
@@ -618,9 +670,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
                 const QUrl url = itemData->item.url();
                 m_expandedDirs.remove(targetUrl);
                 m_dirLister->stop(url); // TODO: try to unit-test this, see https://bugs.kde.org/show_bug.cgi?id=332102#c11
-#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0)
                 m_dirLister->forgetDirs(url);
-#endif
                 expandedChildren.append(targetUrl);
             }
             ++childIndex;
@@ -740,6 +790,20 @@ QStringList KFileItemModel::mimeTypeFilters() const
     return m_filter.mimeTypes();
 }
 
+void KFileItemModel::setExcludeMimeTypeFilter(const QStringList &filters)
+{
+    if (m_filter.excludeMimeTypes() != filters) {
+        dispatchPendingItemsToInsert();
+        m_filter.setExcludeMimeTypes(filters);
+        applyFilters();
+    }
+}
+
+QStringList KFileItemModel::excludeMimeTypeFilter() const
+{
+    return m_filter.excludeMimeTypes();
+}
+
 void KFileItemModel::applyFilters()
 {
     // ===STEP 1===
@@ -956,7 +1020,7 @@ void KFileItemModel::resortAllItems()
     // been moved because of the resorting.
     QList<QUrl> oldUrls;
     oldUrls.reserve(itemCount);
-    for (const ItemData *itemData : qAsConst(m_itemData)) {
+    for (const ItemData *itemData : std::as_const(m_itemData)) {
         oldUrls.append(itemData->item.url());
     }
 
@@ -1201,7 +1265,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList &items)
         indexesToRemoveWithChildren.reserve(m_itemData.count());
 
         const int itemCount = m_itemData.count();
-        for (int index : qAsConst(indexesToRemove)) {
+        for (int index : std::as_const(indexesToRemove)) {
             indexesToRemoveWithChildren.append(index);
 
             const int parentLevel = expandedParentsCount(index);
@@ -1619,7 +1683,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
     case DeletionTimeRole:
         // These roles can be determined with retrieveData, and they have to be stored
         // in the QHash "values" for the sorting.
-        for (ItemData *itemData : qAsConst(itemDataList)) {
+        for (ItemData *itemData : std::as_const(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 itemData->values = retrieveData(itemData->item, itemData->parent);
             }
@@ -1628,7 +1692,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
 
     case TypeRole:
         // At least store the data including the file type for items with known MIME type.
-        for (ItemData *itemData : qAsConst(itemDataList)) {
+        for (ItemData *itemData : std::as_const(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 const KFileItem item = itemData->item;
                 if (item.isDir() || item.isMimeTypeKnown()) {
@@ -1700,7 +1764,8 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i
 
     // Trigger a resorting if necessary. Note that this can happen even if the sort
     // role has not changed at all because the file name can be used as a fallback.
-    if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))) {
+    if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))
+        || (changedRoles.contains("count") && sortRole() == "size")) { // "count" is used in the "size" sort role, so this might require a resorting.
         for (const KItemRange &range : itemRanges) {
             bool needsResorting = false;
 
@@ -1725,7 +1790,7 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i
             }
 
             if (needsResorting) {
-                m_resortAllItemsTimer->start();
+                scheduleResortAllItems();
                 return;
             }
         }
@@ -1823,7 +1888,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item,
     }
 
     if (m_requestRole[IsHiddenRole]) {
-        data.insert(sharedValue("isHidden"), item.isHidden());
+        data.insert(sharedValue("isHidden"), item.isHidden() || item.mimetype() == QStringLiteral("application/x-trash"));
     }
 
     if (m_requestRole[NameRole]) {
@@ -1991,7 +2056,8 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla
         }
     }
 
-    if (m_sortDirsFirst || (DetailsModeSettings::directorySizeCount() && m_sortRole == SizeRole)) {
+    if (m_sortDirsFirst
+        || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
         const bool isDirA = a->item.isDir();
         const bool isDirB = b->item.isDir();
         if (isDirA && !isDirB) {
@@ -2043,7 +2109,7 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const
         break;
 
     case SizeRole: {
-        if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
+        if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) {
             // folders first then
             // items A and B are folders thanks to lessThan checks
             auto valueA = a->values.value("count");
@@ -2199,7 +2265,24 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol
     QMutexLocker collatorLock(s_collatorMutex());
 
     if (m_naturalSorting) {
-        return collator.compare(a, b);
+        // Split extension, taking into account it can be empty
+        constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep;
+
+        // Sort by baseName first
+        const QString aBaseName = a.section('.', 0, 0, flags);
+        const QString bBaseName = b.section('.', 0, 0, flags);
+
+        const int res = collator.compare(aBaseName, bBaseName);
+        if (res != 0 || (aBaseName.length() == a.length() && bBaseName.length() == b.length())) {
+            return res;
+        }
+
+        // sliced() has undefined behavior when pos < 0 or pos > size().
+        Q_ASSERT(aBaseName.length() <= a.length() && aBaseName.length() >= 0);
+        Q_ASSERT(bBaseName.length() <= b.length() && bBaseName.length() >= 0);
+
+        // baseNames were equal, sort by extension
+        return collator.compare(a.sliced(aBaseName.length()), b.sliced(bBaseName.length()));
     }
 
     const int result = QString::compare(a, b, collator.caseSensitivity());
@@ -2303,7 +2386,7 @@ QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
         KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
         QString newGroupValue;
         if (!item.isNull() && item.isDir()) {
-            if (DetailsModeSettings::directorySizeCount() || m_sortDirsFirst) {
+            if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) {
                 newGroupValue = i18nc("@title:group Size", "Folders");
             } else {
                 fileSize = m_itemData.at(i)->values.value("size").toULongLong();
@@ -2345,6 +2428,7 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
             continue;
         }
 
+        const QLocale locale;
         const QDateTime fileTime = fileTimeCb(m_itemData.at(i));
         const QDate fileDate = fileTime.date();
         if (fileDate == previousFileDate) {
@@ -2367,7 +2451,7 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                     newGroupValue = i18nc("@title:group Date", "Yesterday");
                     break;
                 default:
-                    newGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd"));
+                    newGroupValue = locale.toString(fileTime, i18nc("@title:group Date: The week day name: dddd", "dddd"));
                     newGroupValue = i18nc(
                         "Can be used to script translation of \"dddd\""
                         "with context @title:group Date",
@@ -2402,8 +2486,8 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "part of the text that should not be formatted as a date",
                         "'Yesterday' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
-                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
+                    if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) {
+                        newGroupValue = locale.toString(fileTime, translatedFormat);
                         newGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
@@ -2413,14 +2497,14 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        newGroupValue = locale.toString(fileTime, untranslatedFormat);
                     }
                 } else if (daysDistance <= 7) {
-                    newGroupValue =
-                        fileTime.toString(i18nc("@title:group Date: "
-                                                "The week day name: dddd, MMMM is full month name "
-                                                "in current locale, and yyyy is full year number.",
-                                                "dddd (MMMM, yyyy)"));
+                    newGroupValue = locale.toString(fileTime,
+                                                    i18nc("@title:group Date: "
+                                                          "The week day name: dddd, MMMM is full month name "
+                                                          "in current locale, and yyyy is full year number.",
+                                                          "dddd (MMMM, yyyy)"));
                     newGroupValue = i18nc(
                         "Can be used to script translation of "
                         "\"dddd (MMMM, yyyy)\" with context @title:group Date",
@@ -2434,8 +2518,8 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "part of the text that should not be formatted as a date",
                         "'One Week Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
-                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
+                    if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) {
+                        newGroupValue = locale.toString(fileTime, translatedFormat);
                         newGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
@@ -2445,7 +2529,7 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        newGroupValue = locale.toString(fileTime, untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 3) {
                     const KLocalizedString format = ki18nc(
@@ -2455,8 +2539,8 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "part of the text that should not be formatted as a date",
                         "'Two Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
-                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
+                    if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) {
+                        newGroupValue = locale.toString(fileTime, translatedFormat);
                         newGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
@@ -2466,7 +2550,7 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        newGroupValue = locale.toString(fileTime, untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 4) {
                     const KLocalizedString format = ki18nc(
@@ -2476,8 +2560,8 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "part of the text that should not be formatted as a date",
                         "'Three Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
-                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
+                    if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) {
+                        newGroupValue = locale.toString(fileTime, translatedFormat);
                         newGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
@@ -2487,7 +2571,7 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        newGroupValue = locale.toString(fileTime, untranslatedFormat);
                     }
                 } else {
                     const KLocalizedString format = ki18nc(
@@ -2497,8 +2581,8 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "part of the text that should not be formatted as a date",
                         "'Earlier on' MMMM, yyyy");
                     const QString translatedFormat = format.toString();
-                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
+                    if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) {
+                        newGroupValue = locale.toString(fileTime, translatedFormat);
                         newGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
@@ -2508,15 +2592,15 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        newGroupValue = locale.toString(fileTime, untranslatedFormat);
                     }
                 }
             } else {
-                newGroupValue =
-                    fileTime.toString(i18nc("@title:group "
-                                            "The month and year: MMMM is full month name in current locale, "
-                                            "and yyyy is full year number",
-                                            "MMMM, yyyy"));
+                newGroupValue = locale.toString(fileTime,
+                                                i18nc("@title:group "
+                                                      "The month and year: MMMM is full month name in current locale, "
+                                                      "and yyyy is full year number",
+                                                      "MMMM, yyyy"));
                 newGroupValue = i18nc(
                     "Can be used to script translation of "
                     "\"MMMM, yyyy\" with context @title:group Date",
@@ -2568,7 +2652,7 @@ QList<QPair<int, QVariant>> KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeUser)) {
             user += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.count() - 2);
+        user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.length() - 2);
 
         // Set group string
         QString group;
@@ -2581,7 +2665,7 @@ QList<QPair<int, QVariant>> KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeGroup)) {
             group += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.count() - 2);
+        group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.length() - 2);
 
         // Set others string
         QString others;
@@ -2594,7 +2678,7 @@ QList<QPair<int, QVariant>> KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeOther)) {
             others += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.count() - 2);
+        others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2);
 
         const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others);
         if (newGroupValue != groupValue) {
@@ -2715,6 +2799,7 @@ const KFileItemModel::RoleInfoMap *KFileItemModel::rolesInfoMap(int &count)
         { "releaseYear",         ReleaseYearRole,         kli18nc("@label", "Release Year"),         kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
         { "aspectRatio",         AspectRatioRole,         kli18nc("@label", "Aspect Ratio"),         kli18nc("@label", "Video"),    KLazyLocalizedString(),                    true,            true  },
         { "frameRate",           FrameRateRole,           kli18nc("@label", "Frame Rate"),           kli18nc("@label", "Video"),    KLazyLocalizedString(),                    true,            true  },
+        { "duration",            DurationRole,            kli18nc("@label", "Duration"),             kli18nc("@label", "Video"),    KLazyLocalizedString(),                    true,            true  },
         { "path",                PathRole,                kli18nc("@label", "Path"),                 kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
         { "extension",           ExtensionRole,           kli18nc("@label", "File Extension"),       kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
         { "deletiontime",        DeletionTimeRole,        kli18nc("@label", "Deletion Time"),        kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
@@ -2816,12 +2901,15 @@ bool KFileItemModel::isConsistent() const
 
 void KFileItemModel::slotListerError(KIO::Job *job)
 {
-    if (job->error() == KIO::ERR_IS_FILE) {
+    const int jobError = job->error();
+    if (jobError == KIO::ERR_IS_FILE) {
         if (auto *listJob = qobject_cast<KIO::ListJob *>(job)) {
             Q_EMIT urlIsFileError(listJob->url());
         }
     } else {
         const QString errorString = job->errorString();
-        Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."));
+        Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."), jobError);
     }
 }
+
+#include "moc_kfileitemmodel.cpp"