]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Add "Aspect Ratio" and "Frame Rate" to additional video information columns
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index aaa62810299354745ff979c67f21be26155eb195..b14647b42bd28e77114b8afda6ccdad64e0b7a85 100644 (file)
 #include "kfileitemmodel.h"
 
 #include "dolphin_generalsettings.h"
+#include "dolphindebug.h"
+#include "private/kfileitemmodeldirlister.h"
+#include "private/kfileitemmodelsortalgorithm.h"
 
 #include <KLocalizedString>
 #include <KUrlMimeData>
 
-#include "dolphindebug.h"
-
-#include "private/kfileitemmodelsortalgorithm.h"
-#include "private/kfileitemmodeldirlister.h"
-
+#include <QElapsedTimer>
 #include <QMimeData>
 #include <QTimer>
 #include <QWidget>
 
-#include <algorithm>
-#include <vector>
-
 // #define KFILEITEMMODEL_DEBUG
 
 KFileItemModel::KFileItemModel(QObject* parent) :
     KItemModelBase("text", parent),
-    m_dirLister(0),
+    m_dirLister(nullptr),
     m_sortDirsFirst(true),
     m_sortRole(NameRole),
     m_sortingProgressPercent(-1),
@@ -52,8 +48,8 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_filter(),
     m_filteredItems(),
     m_requestRole(),
-    m_maximumUpdateIntervalTimer(0),
-    m_resortAllItemsTimer(0),
+    m_maximumUpdateIntervalTimer(nullptr),
+    m_resortAllItemsTimer(nullptr),
     m_pendingItemsToInsert(),
     m_groups(),
     m_expandedDirs(),
@@ -92,6 +88,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_roles.insert("text");
     m_roles.insert("isDir");
     m_roles.insert("isLink");
+    m_roles.insert("isHidden");
 
     // For slow KIO-slaves like used for searching it makes sense to show results periodically even
     // before the completed() or canceled() signal has been emitted.
@@ -249,8 +246,7 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
     // Copyright (C) 2006 David Faure <faure@kde.org>
     QList<QUrl> urls;
     QList<QUrl> mostLocalUrls;
-    bool canUseMostLocalUrls = true;
-    const ItemData* lastAddedItem = 0;
+    const ItemData* lastAddedItem = nullptr;
 
     for (int index : indexes) {
         const ItemData* itemData = m_itemData.at(index);
@@ -272,9 +268,6 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
 
             bool isLocal;
             mostLocalUrls << item.mostLocalUrl(isLocal);
-            if (!isLocal) {
-                canUseMostLocalUrls = false;
-            }
         }
     }
 
@@ -311,6 +304,9 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
         int count = 0;
         const RoleInfoMap* map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
+           if (!map[i].roleTranslation) {
+                   continue;
+           }
             description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation));
         }
     }
@@ -328,7 +324,26 @@ QList<QPair<int, QVariant> > KFileItemModel::groups() const
         switch (typeForRole(sortRole())) {
         case NameRole:        m_groups = nameRoleGroups(); break;
         case SizeRole:        m_groups = sizeRoleGroups(); break;
-        case DateRole:        m_groups = dateRoleGroups(); break;
+        case ModificationTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::ModificationTime);
+            });
+            break;
+        case CreationTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::CreationTime);
+            });
+            break;
+        case AccessTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::AccessTime);
+            });
+            break;
+        case DeletionTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->values.value("deletiontime").toDateTime();
+            });
+            break;
         case PermissionsRole: m_groups = permissionRoleGroups(); break;
         case RatingRole:      m_groups = ratingRoleGroups(); break;
         default:              m_groups = genericStringRoleGroups(sortRole()); break;
@@ -609,16 +624,24 @@ void KFileItemModel::restoreExpandedDirectories(const QSet<QUrl> &urls)
 
 void KFileItemModel::expandParentDirectories(const QUrl &url)
 {
-    const int pos = m_dirLister->url().path().length();
 
     // Assure that each sub-path of the URL that should be
     // expanded is added to m_urlsToExpand. KDirLister
     // does not care whether the parent-URL has already been
     // expanded.
     QUrl urlToExpand = m_dirLister->url();
-    const QStringList subDirs = url.path().mid(pos).split(QDir::separator());
+    const int pos = urlToExpand.path().length();
+
+    // first subdir can be empty, if m_dirLister->url().path() does not end with '/'
+    // this happens if baseUrl is not root but a home directory, see FoldersPanel,
+    // so using QString::SkipEmptyParts
+    const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), QString::SkipEmptyParts);
     for (int i = 0; i < subDirs.count() - 1; ++i) {
-        urlToExpand.setPath(urlToExpand.path() + '/' + subDirs.at(i));
+        QString path = urlToExpand.path();
+        if (!path.endsWith(QLatin1Char('/'))) {
+            path.append(QLatin1Char('/'));
+        }
+        urlToExpand.setPath(path + subDirs.at(i));
         m_urlsToExpand.insert(urlToExpand);
     }
 
@@ -766,7 +789,7 @@ void KFileItemModel::onGroupedSortingChanged(bool current)
     m_groups.clear();
 }
 
-void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous, bool resortItems)
 {
     Q_UNUSED(previous);
     m_sortRole = typeForRole(current);
@@ -777,7 +800,9 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
         setRoles(newRoles);
     }
 
-    resortAllItems();
+    if (resortItems) {
+        resortAllItems();
+    }
 }
 
 void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
@@ -806,6 +831,9 @@ void KFileItemModel::loadSortingSettings()
     default:
         Q_UNREACHABLE();
     }
+    // Workaround for bug https://bugreports.qt.io/browse/QTBUG-69361
+    // Force the clean state of QCollator in single thread to avoid thread safety problems in sort
+    m_collator.compare(QString(), QString());
 }
 
 void KFileItemModel::resortAllItems()
@@ -1096,7 +1124,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     }
 
     // Extract the item-ranges out of the changed indexes
-    qSort(indexes);
+    std::sort(indexes.begin(), indexes.end());
     const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
     emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
 }
@@ -1185,7 +1213,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
     } else {
         m_itemData.reserve(totalItemCount);
         for (int i = existingItemCount; i < totalItemCount; ++i) {
-            m_itemData.append(0);
+            m_itemData.append(nullptr);
         }
 
         // We build the new list m_itemData in reverse order to minimize
@@ -1255,7 +1283,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
                 delete m_itemData.at(index);
             }
 
-            m_itemData[index] = 0;
+            m_itemData[index] = nullptr;
         }
     }
 
@@ -1296,7 +1324,7 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
     }
 
     const int parentIndex = index(parentUrl);
-    ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+    ItemData* parentItem = parentIndex < 0 ? nullptr : m_itemData.at(parentIndex);
 
     QList<ItemData*> itemDataList;
     itemDataList.reserve(items.count());
@@ -1319,6 +1347,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
     case GroupRole:
     case DestinationRole:
     case PathRole:
+    case DeletionTimeRole:
         // These roles can be determined with retrieveData, and they have to be stored
         // in the QHash "values" for the sorting.
         foreach (ItemData* itemData, itemDataList) {
@@ -1471,6 +1500,7 @@ KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray& role) con
         // with KFileItemModel::roleForType() in case if a change is done).
         roles.insert("isDir", IsDirRole);
         roles.insert("isLink", IsLinkRole);
+        roles.insert("isHidden", IsHiddenRole);
         roles.insert("isExpanded", IsExpandedRole);
         roles.insert("isExpandable", IsExpandableRole);
         roles.insert("expandedParentsCount", ExpandedParentsCountRole);
@@ -1497,6 +1527,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
         // with KFileItemModel::typeForRole() in case if a change is done).
         roles.insert(IsDirRole, "isDir");
         roles.insert(IsLinkRole, "isLink");
+        roles.insert(IsHiddenRole, "isHidden");
         roles.insert(IsExpandedRole, "isExpanded");
         roles.insert(IsExpandableRole, "isExpandable");
         roles.insert(ExpandedParentsCountRole, "expandedParentsCount");
@@ -1524,6 +1555,10 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("isLink"), true);
     }
 
+    if (m_requestRole[IsHiddenRole]) {
+        data.insert(sharedValue("isHidden"), item.isHidden());
+    }
+
     if (m_requestRole[NameRole]) {
         data.insert(sharedValue("text"), item.text());
     }
@@ -1532,12 +1567,28 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("size"), item.size());
     }
 
-    if (m_requestRole[DateRole]) {
-        // Don't use KFileItem::timeString() as this is too expensive when
-        // having several thousands of items. Instead the formatting of the
-        // date-time will be done on-demand by the view when the date will be shown.
-        const QDateTime dateTime = item.time(KFileItem::ModificationTime);
-        data.insert(sharedValue("date"), dateTime);
+    if (m_requestRole[ModificationTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
+        data.insert(sharedValue("modificationtime"), dateTime);
+    }
+
+    if (m_requestRole[CreationTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
+        data.insert(sharedValue("creationtime"), dateTime);
+    }
+
+    if (m_requestRole[AccessTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_ACCESS_TIME, -1);
+        data.insert(sharedValue("accesstime"), dateTime);
     }
 
     if (m_requestRole[PermissionsRole]) {
@@ -1583,6 +1634,14 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("path"), path);
     }
 
+    if (m_requestRole[DeletionTimeRole]) {
+        QDateTime deletionTime;
+        if (item.url().scheme() == QLatin1String("trash")) {
+            deletionTime = QDateTime::fromString(item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA + 1), Qt::ISODate);
+        }
+        data.insert(sharedValue("deletiontime"), deletionTime);
+    }
+
     if (m_requestRole[IsExpandableRole] && isDir) {
         data.insert(sharedValue("isExpandable"), true);
     }
@@ -1658,63 +1717,24 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QColla
     return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
 }
 
-/**
- * Helper class for KFileItemModel::sort().
- */
-class KFileItemModelLessThan
+void KFileItemModel::sort(const QList<KFileItemModel::ItemData*>::iterator &begin,
+                          const QList<KFileItemModel::ItemData*>::iterator &end) const
 {
-public:
-    KFileItemModelLessThan(const KFileItemModel* model, const QCollator& collator) :
-        m_model(model),
-        m_collator(collator)
-    {
-    }
-
-    KFileItemModelLessThan(const KFileItemModelLessThan& other) :
-        m_model(other.m_model),
-        m_collator()
-    {
-        m_collator.setCaseSensitivity(other.m_collator.caseSensitivity());
-        m_collator.setIgnorePunctuation(other.m_collator.ignorePunctuation());
-        m_collator.setLocale(other.m_collator.locale());
-        m_collator.setNumericMode(other.m_collator.numericMode());
-    }
-
-    ~KFileItemModelLessThan() = default;
-    //We do not delete m_model as the pointer was passed from outside ant it will be deleted elsewhere.
-
-    KFileItemModelLessThan& operator=(const KFileItemModelLessThan& other)
+    auto lambdaLessThan = [&] (const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b)
     {
-        m_model = other.m_model;
-        m_collator = other.m_collator;
-        return *this;
-    }
-
-    bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const
-    {
-        return m_model->lessThan(a, b, m_collator);
-    }
-
-private:
-    const KFileItemModel* m_model;
-    QCollator m_collator;
-};
-
-void KFileItemModel::sort(QList<KFileItemModel::ItemData*>::iterator begin,
-                          QList<KFileItemModel::ItemData*>::iterator end) const
-{
-    KFileItemModelLessThan lessThan(this, m_collator);
+        return lessThan(a, b, m_collator);
+    };
 
     if (m_sortRole == NameRole) {
         // Sorting by name can be expensive, in particular if natural sorting is
         // enabled. Use all CPU cores to speed up the sorting process.
         static const int numberOfThreads = QThread::idealThreadCount();
-        parallelMergeSort(begin, end, lessThan, numberOfThreads);
+        parallelMergeSort(begin, end, lambdaLessThan, numberOfThreads);
     } else {
         // Sorting by other roles is quite fast. Use only one thread to prevent
         // problems caused by non-reentrant comparison functions, see
         // https://bugs.kde.org/show_bug.cgi?id=312679
-        mergeSort(begin, end, lessThan);
+        mergeSort(begin, end, lambdaLessThan);
     }
 }
 
@@ -1762,9 +1782,20 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
-    case DateRole: {
-        const QDateTime dateTimeA = itemA.time(KFileItem::ModificationTime);
-        const QDateTime dateTimeB = itemB.time(KFileItem::ModificationTime);
+    case ModificationTimeRole: {
+        const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
+        const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
+        if (dateTimeA < dateTimeB) {
+            result = -1;
+        } else if (dateTimeA > dateTimeB) {
+            result = +1;
+        }
+        break;
+    }
+
+    case CreationTimeRole: {
+        const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
+        const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
         if (dateTimeA < dateTimeB) {
             result = -1;
         } else if (dateTimeA > dateTimeB) {
@@ -1773,16 +1804,25 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
-    case RatingRole: {
-        result = a->values.value("rating").toInt() - b->values.value("rating").toInt();
+    case DeletionTimeRole: {
+        const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime();
+        const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime();
+        if (dateTimeA < dateTimeB) {
+            result = -1;
+        } else if (dateTimeA > dateTimeB) {
+            result = +1;
+        }
         break;
     }
 
-    case ImageSizeRole: {
-        // Alway use a natural comparing to interpret the numbers of a string like
-        // "1600 x 1200" for having a correct sorting.
-        result = collator.compare(a->values.value("imageSize").toString(),
-                                  b->values.value("imageSize").toString());
+    case RatingRole:
+    case WidthRole:
+    case HeightRole:
+    case WordCountRole:
+    case LineCountRole:
+    case TrackRole:
+    case ReleaseYearRole: {
+        result = a->values.value(roleForType(m_sortRole)).toInt() - b->values.value(roleForType(m_sortRole)).toInt();
         break;
     }
 
@@ -1942,7 +1982,7 @@ QList<QPair<int, QVariant> > KFileItemModel::sizeRoleGroups() const
     return groups;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
+QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
 {
     Q_ASSERT(!m_itemData.isEmpty());
 
@@ -1951,26 +1991,26 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
 
     const QDate currentDate = QDate::currentDate();
 
-    QDate previousModifiedDate;
+    QDate previousFileDate;
     QString groupValue;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
         }
 
-        const QDateTime modifiedTime = m_itemData.at(i)->item.time(KFileItem::ModificationTime);
-        const QDate modifiedDate = modifiedTime.date();
-        if (modifiedDate == previousModifiedDate) {
+        const QDateTime fileTime = fileTimeCb(m_itemData.at(i));
+        const QDate fileDate = fileTime.date();
+        if (fileDate == previousFileDate) {
             // The current item is in the same group as the previous item
             continue;
         }
-        previousModifiedDate = modifiedDate;
+        previousFileDate = fileDate;
 
-        const int daysDistance = modifiedDate.daysTo(currentDate);
+        const int daysDistance = fileDate.daysTo(currentDate);
 
         QString newGroupValue;
-        if (currentDate.year() == modifiedDate.year() &&
-            currentDate.month() == modifiedDate.month()) {
+        if (currentDate.year() == fileDate.year() &&
+            currentDate.month() == fileDate.month()) {
 
             switch (daysDistance / 7) {
             case 0:
@@ -1978,7 +2018,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
                 case 0:  newGroupValue = i18nc("@title:group Date", "Today"); break;
                 case 1:  newGroupValue = i18nc("@title:group Date", "Yesterday"); break;
                 default:
-                    newGroupValue = modifiedTime.toString(
+                    newGroupValue = fileTime.toString(
                         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", "%1", newGroupValue);
@@ -2002,18 +2042,26 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
             }
         } else {
             const QDate lastMonthDate = currentDate.addMonths(-1);
-            if  (lastMonthDate.year() == modifiedDate.year() &&
-                 lastMonthDate.month() == modifiedDate.month()) {
+            if  (lastMonthDate.year() == fileDate.year() &&
+                 lastMonthDate.month() == fileDate.month()) {
 
                 if (daysDistance == 1) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
-                        "MMMM is full month name in current locale, and yyyy is "
-                        "full year number", "'Yesterday' (MMMM, yyyy)"));
-                    newGroupValue = i18nc("Can be used to script translation of "
-                        "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
-                        "%1", newGroupValue);
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                    "MMMM is full month name in current locale, and yyyy is "
+                                                    "full year number", "'Yesterday' (MMMM, yyyy)");
+                    const QString translatedFormat = format.toString();
+                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
+                        newGroupValue = fileTime.toString(translatedFormat);
+                        newGroupValue = i18nc("Can be used to script translation of "
+                            "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } else {
+                        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);
+                    }
                 } else if (daysDistance <= 7) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
+                    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)"));
@@ -2021,36 +2069,68 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
                         "\"dddd (MMMM, yyyy)\" with context @title:group Date",
                         "%1", newGroupValue);
                 } else if (daysDistance <= 7 * 2) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
-                        "MMMM is full month name in current locale, and yyyy is "
-                        "full year number", "'One Week Ago' (MMMM, yyyy)"));
-                    newGroupValue = i18nc("Can be used to script translation of "
-                        "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
-                        "%1", newGroupValue);
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                           "MMMM is full month name in current locale, and yyyy is "
+                                                           "full year number", "'One Week Ago' (MMMM, yyyy)");
+                    const QString translatedFormat = format.toString();
+                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
+                        newGroupValue = fileTime.toString(translatedFormat);
+                        newGroupValue = i18nc("Can be used to script translation of "
+                            "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } else {
+                        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);
+                    }
                 } else if (daysDistance <= 7 * 3) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
-                        "MMMM is full month name in current locale, and yyyy is "
-                        "full year number", "'Two Weeks Ago' (MMMM, yyyy)"));
-                    newGroupValue = i18nc("Can be used to script translation of "
-                        "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
-                        "%1", newGroupValue);
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                           "MMMM is full month name in current locale, and yyyy is "
+                                                           "full year number", "'Two Weeks Ago' (MMMM, yyyy)");
+                    const QString translatedFormat = format.toString();
+                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
+                        newGroupValue = fileTime.toString(translatedFormat);
+                        newGroupValue = i18nc("Can be used to script translation of "
+                            "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } else {
+                        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);
+                    }
                 } else if (daysDistance <= 7 * 4) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
-                        "MMMM is full month name in current locale, and yyyy is "
-                        "full year number", "'Three Weeks Ago' (MMMM, yyyy)"));
-                    newGroupValue = i18nc("Can be used to script translation of "
-                        "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
-                        "%1", newGroupValue);
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                           "MMMM is full month name in current locale, and yyyy is "
+                                                           "full year number", "'Three Weeks Ago' (MMMM, yyyy)");
+                    const QString translatedFormat = format.toString();
+                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
+                        newGroupValue = fileTime.toString(translatedFormat);
+                        newGroupValue = i18nc("Can be used to script translation of "
+                            "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } else {
+                        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);
+                    }
                 } else {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: "
-                        "MMMM is full month name in current locale, and yyyy is "
-                        "full year number", "'Earlier on' MMMM, yyyy"));
-                    newGroupValue = i18nc("Can be used to script translation of "
-                        "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
-                        "%1", newGroupValue);
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                           "MMMM is full month name in current locale, and yyyy is "
+                                                           "full year number", "'Earlier on' MMMM, yyyy");
+                    const QString translatedFormat = format.toString();
+                    if (translatedFormat.count(QLatin1Char('\'')) == 2) {
+                        newGroupValue = fileTime.toString(translatedFormat);
+                        newGroupValue = i18nc("Can be used to script translation of "
+                            "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } else {
+                        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);
+                    }
                 }
             } else {
-                newGroupValue = modifiedTime.toString(i18nc("@title:group "
+                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 = i18nc("Can be used to script translation of "
@@ -2218,24 +2298,34 @@ const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
     //  | role         | roleType       | role translation                                | group translation           | requires Baloo   | requires indexer
-        { 0,             NoRole,          0, 0,                                             0, 0,                                     false, false },
-        { "text",        NameRole,        I18N_NOOP2_NOSTRIP("@label", "Name"),             0, 0,                                     false, false },
-        { "size",        SizeRole,        I18N_NOOP2_NOSTRIP("@label", "Size"),             0, 0,                                     false, false },
-        { "date",        DateRole,        I18N_NOOP2_NOSTRIP("@label", "Date"),             0, 0,                                     false, false },
-        { "type",        TypeRole,        I18N_NOOP2_NOSTRIP("@label", "Type"),             0, 0,                                     false, false },
-        { "rating",      RatingRole,      I18N_NOOP2_NOSTRIP("@label", "Rating"),           0, 0,                                     true,  false },
-        { "tags",        TagsRole,        I18N_NOOP2_NOSTRIP("@label", "Tags"),             0, 0,                                     true,  false },
-        { "comment",     CommentRole,     I18N_NOOP2_NOSTRIP("@label", "Comment"),          0, 0,                                     true,  false },
+        { nullptr,             NoRole,          nullptr, nullptr,                                             nullptr, nullptr,                                     false, false },
+        { "text",        NameRole,        I18N_NOOP2_NOSTRIP("@label", "Name"),             nullptr, nullptr,                                     false, false },
+        { "size",        SizeRole,        I18N_NOOP2_NOSTRIP("@label", "Size"),             nullptr, nullptr,                                     false, false },
+        { "modificationtime",        ModificationTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Modified"),             nullptr, nullptr,                                     false, false },
+        { "creationtime",        CreationTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Created"),             nullptr, nullptr,                                     false, false },
+        { "accesstime",        AccessTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Accessed"),             nullptr, nullptr,                                     false, false },
+        { "type",        TypeRole,        I18N_NOOP2_NOSTRIP("@label", "Type"),             nullptr, nullptr,                                     false, false },
+        { "rating",      RatingRole,      I18N_NOOP2_NOSTRIP("@label", "Rating"),           nullptr, nullptr,                                     true,  false },
+        { "tags",        TagsRole,        I18N_NOOP2_NOSTRIP("@label", "Tags"),             nullptr, nullptr,                                     true,  false },
+        { "comment",     CommentRole,     I18N_NOOP2_NOSTRIP("@label", "Comment"),          nullptr, nullptr,                                     true,  false },
         { "title",       TitleRole,       I18N_NOOP2_NOSTRIP("@label", "Title"),            I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
         { "wordCount",   WordCountRole,   I18N_NOOP2_NOSTRIP("@label", "Word Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
         { "lineCount",   LineCountRole,   I18N_NOOP2_NOSTRIP("@label", "Line Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "imageSize",   ImageSizeRole,   I18N_NOOP2_NOSTRIP("@label", "Image Size"),       I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
+        { "imageDateTime",   ImageDateTimeRole,   I18N_NOOP2_NOSTRIP("@label", "Date Photographed"),       I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
+        { "width",       WidthRole,       I18N_NOOP2_NOSTRIP("@label", "Width"),            I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
+        { "height",      HeightRole,      I18N_NOOP2_NOSTRIP("@label", "Height"),           I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
         { "orientation", OrientationRole, I18N_NOOP2_NOSTRIP("@label", "Orientation"),      I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
         { "artist",      ArtistRole,      I18N_NOOP2_NOSTRIP("@label", "Artist"),           I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
+        { "genre",       GenreRole,       I18N_NOOP2_NOSTRIP("@label", "Genre"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
         { "album",       AlbumRole,       I18N_NOOP2_NOSTRIP("@label", "Album"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
         { "duration",    DurationRole,    I18N_NOOP2_NOSTRIP("@label", "Duration"),         I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
+        { "bitrate",     BitrateRole,     I18N_NOOP2_NOSTRIP("@label", "Bitrate"),          I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
         { "track",       TrackRole,       I18N_NOOP2_NOSTRIP("@label", "Track"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
+        { "releaseYear", ReleaseYearRole, I18N_NOOP2_NOSTRIP("@label", "Release Year"),     I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
+        { "aspectRatio", AspectRatioRole, I18N_NOOP2_NOSTRIP("@label", "Aspect Ratio"),     I18N_NOOP2_NOSTRIP("@label", "Video"),    true,  true  },
+        { "frameRate",   FrameRateRole,   I18N_NOOP2_NOSTRIP("@label", "Frame Rate"),       I18N_NOOP2_NOSTRIP("@label", "Video"),    true,  true  },
         { "path",        PathRole,        I18N_NOOP2_NOSTRIP("@label", "Path"),             I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
+        { "deletiontime",DeletionTimeRole,I18N_NOOP2_NOSTRIP("@label", "Deletion Time"),    I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
         { "destination", DestinationRole, I18N_NOOP2_NOSTRIP("@label", "Link Destination"), I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
         { "originUrl",   OriginUrlRole,   I18N_NOOP2_NOSTRIP("@label", "Downloaded From"),  I18N_NOOP2_NOSTRIP("@label", "Other"),    true,  false },
         { "permissions", PermissionsRole, I18N_NOOP2_NOSTRIP("@label", "Permissions"),      I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
@@ -2290,7 +2380,7 @@ bool KFileItemModel::isConsistent() const
         return false;
     }
 
-    for (int i = 0; i < count(); ++i) {
+    for (int i = 0, iMax = count(); i < iMax; ++i) {
         // Check if m_items and m_itemData are consistent.
         const KFileItem item = fileItem(i);
         if (item.isNull()) {