]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Use the target url of a item when creating the QMimeData in KFileItemModel::createMim...
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 62881e132dbe86c500528f88e87e7abc55e80c9a..787d36ae61e756e17c0b488cd06cef332156223b 100644 (file)
 #include <QApplication>
 #include <QMimeData>
 #include <QTimer>
+#include <QWidget>
+
+#include <algorithm>
+#include <vector>
 
 // #define KFILEITEMMODEL_DEBUG
 
@@ -59,7 +63,11 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_dirLister = new KFileItemModelDirLister(this);
     m_dirLister->setAutoUpdate(true);
     m_dirLister->setDelayedMimeTypes(true);
-    m_dirLister->setMainWindow(qApp->activeWindow());
+
+    const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
+    if (parentWidget) {
+        m_dirLister->setMainWindow(parentWidget->window());
+    }
 
     connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted()));
     connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
@@ -72,13 +80,16 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     connect(m_dirLister, SIGNAL(infoMessage(QString)), this, SIGNAL(infoMessage(QString)));
     connect(m_dirLister, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString)));
     connect(m_dirLister, SIGNAL(redirection(KUrl,KUrl)), this, SIGNAL(directoryRedirection(KUrl,KUrl)));
+    connect(m_dirLister, SIGNAL(urlIsFileError(KUrl)), this, SIGNAL(urlIsFileError(KUrl)));
 
     // Apply default roles that should be determined
     resetRoles();
     m_requestRole[NameRole] = true;
     m_requestRole[IsDirRole] = true;
+    m_requestRole[IsLinkRole] = true;
     m_roles.insert("text");
     m_roles.insert("isDir");
+    m_roles.insert("isLink");
 
     // 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.
@@ -112,6 +123,11 @@ void KFileItemModel::loadDirectory(const KUrl& url)
 
 void KFileItemModel::refreshDirectory(const KUrl& url)
 {
+    // Refresh all expanded directories first (Bug 295300)
+    foreach (const KUrl& expandedUrl, m_expandedDirs) {
+        m_dirLister->openUrl(expandedUrl, KDirLister::Reload);
+    }
+
     m_dirLister->openUrl(url, KDirLister::Reload);
 }
 
@@ -165,6 +181,12 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
     }
 
     m_itemData[index]->values = currentValues;
+    if (changedRoles.contains("text")) {
+        KUrl url = m_itemData[index]->item.url();
+        url.setFileName(currentValues["text"].toString());
+        m_itemData[index]->item.setUrl(url);
+    }
+
     emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
 
     if (changedRoles.contains(sortRole())) {
@@ -227,7 +249,7 @@ QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
         const int index = it.next();
         const KFileItem item = fileItem(index);
         if (!item.isNull()) {
-            urls << item.url();
+            urls << item.targetUrl();
 
             bool isLocal;
             mostLocalUrls << item.mostLocalUrl(isLocal);
@@ -278,7 +300,7 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
         int count = 0;
         const RoleInfoMap* map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
-            description.insert(map[i].role, map[i].roleTranslation);
+            description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation));
         }
     }
 
@@ -424,6 +446,29 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
             itemsToRemove.append(m_itemData.at(index)->item);
             ++index;
         }
+
+        QSet<KUrl> urlsToRemove;
+        urlsToRemove.reserve(itemsToRemove.count() + 1);
+        urlsToRemove.insert(url);
+        foreach (const KFileItem& item, itemsToRemove) {
+            KUrl url = item.url();
+            url.adjustPath(KUrl::RemoveTrailingSlash);
+            urlsToRemove.insert(url);
+        }
+
+        QSet<KFileItem>::iterator it = m_filteredItems.begin();
+        while (it != m_filteredItems.end()) {
+            const KUrl url = it->url();
+            KUrl parentUrl = url.upUrl();
+            parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+
+            if (urlsToRemove.contains(parentUrl)) {
+                it = m_filteredItems.erase(it);
+            } else {
+                ++it;
+            }
+        }
+
         removeItems(itemsToRemove);
     }
 
@@ -499,46 +544,64 @@ void KFileItemModel::setNameFilter(const QString& nameFilter)
 {
     if (m_filter.pattern() != nameFilter) {
         dispatchPendingItemsToInsert();
-
         m_filter.setPattern(nameFilter);
+        applyFilters();
+    }
+}
+
+QString KFileItemModel::nameFilter() const
+{
+    return m_filter.pattern();
+}
+
+void KFileItemModel::setMimeTypeFilters(const QStringList& filters)
+{
+    if (m_filter.mimeTypes() != filters) {
+        dispatchPendingItemsToInsert();
+        m_filter.setMimeTypes(filters);
+        applyFilters();
+    }
+}
+
+QStringList KFileItemModel::mimeTypeFilters() const
+{
+    return m_filter.mimeTypes();
+}
 
-        // Check which shown items from m_itemData must get
-        // hidden and hence moved to m_filteredItems.
-        KFileItemList newFilteredItems;
 
-        foreach (ItemData* itemData, m_itemData) {
+void KFileItemModel::applyFilters()
+{
+    // Check which shown items from m_itemData must get
+    // hidden and hence moved to m_filteredItems.
+    KFileItemList newFilteredItems;
+
+    foreach (ItemData* itemData, m_itemData) {
+        // Only filter non-expanded items as child items may never
+        // exist without a parent item
+        if (!itemData->values.value("isExpanded").toBool()) {
             if (!m_filter.matches(itemData->item)) {
-                // Only filter non-expanded items as child items may never
-                // exist without a parent item
-                if (!itemData->values.value("isExpanded").toBool()) {
-                    newFilteredItems.append(itemData->item);
-                    m_filteredItems.insert(itemData->item);
-                }
+                newFilteredItems.append(itemData->item);
+                m_filteredItems.insert(itemData->item);
             }
         }
+    }
 
-        removeItems(newFilteredItems);
+    removeItems(newFilteredItems);
 
-        // Check which hidden items from m_filteredItems should
-        // get visible again and hence removed from m_filteredItems.
-        KFileItemList newVisibleItems;
+    // Check which hidden items from m_filteredItems should
+    // get visible again and hence removed from m_filteredItems.
+    KFileItemList newVisibleItems;
 
-        QMutableSetIterator<KFileItem> it(m_filteredItems);
-        while (it.hasNext()) {
-            const KFileItem item = it.next();
-            if (m_filter.matches(item)) {
-                newVisibleItems.append(item);
-                m_filteredItems.remove(item);
-            }
+    QMutableSetIterator<KFileItem> it(m_filteredItems);
+    while (it.hasNext()) {
+        const KFileItem item = it.next();
+        if (m_filter.matches(item)) {
+            newVisibleItems.append(item);
+            it.remove();
         }
-
-        insertItems(newVisibleItems);
     }
-}
 
-QString KFileItemModel::nameFilter() const
-{
-    return m_filter.pattern();
+    insertItems(newVisibleItems);
 }
 
 QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
@@ -551,8 +614,15 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
             if (map[i].roleType != NoRole) {
                 RoleInfo info;
                 info.role = map[i].role;
-                info.translation = map[i].roleTranslation;
-                info.group = map[i].groupTranslation;
+                info.translation = i18nc(map[i].roleTranslationContext, map[i].roleTranslation);
+                if (map[i].groupTranslation) {
+                    info.group = i18nc(map[i].groupTranslationContext, map[i].groupTranslation);
+                } else {
+                    // For top level roles, groupTranslation is 0. We must make sure that
+                    // info.group is an empty string then because the code that generates
+                    // menus tries to put the actions into sub menus otherwise.
+                    info.group = QString();
+                }
                 info.requiresNepomuk = map[i].requiresNepomuk;
                 info.requiresIndexer = map[i].requiresIndexer;
                 rolesInfo.append(info);
@@ -574,11 +644,11 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
     Q_UNUSED(previous);
     m_sortRole = typeForRole(current);
 
-#ifdef KFILEITEMMODEL_DEBUG
     if (!m_requestRole[m_sortRole]) {
-        kWarning() << "The sort-role has been changed to a role that has not been received yet";
+        QSet<QByteArray> newRoles = m_roles;
+        newRoles << current;
+        setRoles(newRoles);
     }
-#endif
 
     resortAllItems();
 }
@@ -677,6 +747,8 @@ void KFileItemModel::slotCanceled()
 {
     m_maximumUpdateIntervalTimer->stop();
     dispatchPendingItemsToInsert();
+
+    emit directoryLoadingCanceled();
 }
 
 void KFileItemModel::slotNewItems(const KFileItemList& items)
@@ -713,10 +785,10 @@ void KFileItemModel::slotNewItems(const KFileItemList& items)
         }
     }
 
-    if (m_filter.pattern().isEmpty()) {
+    if (!m_filter.hasSetFilters()) {
         m_pendingItemsToInsert.append(items);
     } else {
-        // The name-filter is active. Hide filtered items
+        // The name or type filter is active. Hide filtered items
         // before inserting them into the model and remember
         // the filtered items in m_filteredItems.
         KFileItemList filteredItems;
@@ -754,6 +826,33 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
         foreach (const KFileItem& item, itemsToRemove) {
             m_filteredItems.remove(item);
         }
+
+        if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
+            // Remove all filtered children of deleted items. First, we put the
+            // deleted URLs into a set to provide fast lookup while iterating
+            // over m_filteredItems and prevent quadratic complexity if there
+            // are N removed items and N filtered items.
+            QSet<KUrl> urlsToRemove;
+            urlsToRemove.reserve(itemsToRemove.count());
+            foreach (const KFileItem& item, itemsToRemove) {
+                KUrl url = item.url();
+                url.adjustPath(KUrl::RemoveTrailingSlash);
+                urlsToRemove.insert(url);
+            }
+
+            QSet<KFileItem>::iterator it = m_filteredItems.begin();
+            while (it != m_filteredItems.end()) {
+                const KUrl url = it->url();
+                KUrl parentUrl = url.upUrl();
+                parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+
+                if (urlsToRemove.contains(parentUrl)) {
+                    it = m_filteredItems.erase(it);
+                } else {
+                    ++it;
+                }
+            }
+        }
     }
 
     removeItems(itemsToRemove);
@@ -930,7 +1029,7 @@ void KFileItemModel::insertItems(const KFileItemList& items)
             insertedCount = 0;
         }
 
-        // Insert item at the position targetIndex by transfering
+        // Insert item at the position targetIndex by transferring
         // the ownership of the item-data from sortedItems to m_itemData.
         // m_items will be inserted after the loop (see comment below)
         m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
@@ -1116,6 +1215,7 @@ KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray& role) con
         // Insert internal roles (take care to synchronize the implementation
         // with KFileItemModel::roleForType() in case if a change is done).
         roles.insert("isDir", IsDirRole);
+        roles.insert("isLink", IsLinkRole);
         roles.insert("isExpanded", IsExpandedRole);
         roles.insert("isExpandable", IsExpandableRole);
         roles.insert("expandedParentsCount", ExpandedParentsCountRole);
@@ -1141,6 +1241,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
         // Insert internal roles (take care to synchronize the implementation
         // with KFileItemModel::typeForRole() in case if a change is done).
         roles.insert(IsDirRole, "isDir");
+        roles.insert(IsLinkRole, "isLink");
         roles.insert(IsExpandedRole, "isExpanded");
         roles.insert(IsExpandableRole, "isExpandable");
         roles.insert(ExpandedParentsCountRole, "expandedParentsCount");
@@ -1164,6 +1265,11 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
         data.insert("isDir", isDir);
     }
 
+    if (m_requestRole[IsLinkRole]) {
+        const bool isLink = item.isLink();
+        data.insert("isLink", isLink);
+    }
+
     if (m_requestRole[NameRole]) {
         data.insert("text", item.text());
     }
@@ -1227,10 +1333,6 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
         data.insert("path", path);
     }
 
-    if (m_requestRole[IsExpandedRole]) {
-        data.insert("isExpanded", false);
-    }
-
     if (m_requestRole[IsExpandableRole]) {
         data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
     }
@@ -1451,7 +1553,7 @@ int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemDat
     if (index > maxIndex) {
         index = maxIndex;
     }
-    while ((pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/')) && index > 0) {
+    while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) {
         --index;
     }
 
@@ -1509,6 +1611,11 @@ bool KFileItemModel::useMaximumUpdateInterval() const
     return !m_dirLister->url().isLocalFile();
 }
 
+static bool localeAwareLessThan(const QChar& c1, const QChar& c2)
+{
+    return QString::localeAwareCompare(c1, c2) < 0;
+}
+
 QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 {
     Q_ASSERT(!m_itemData.isEmpty());
@@ -1518,7 +1625,6 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 
     QString groupValue;
     QChar firstChar;
-    bool isLetter = false;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
@@ -1534,31 +1640,31 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 
         if (firstChar != newFirstChar) {
             QString newGroupValue;
-            if (newFirstChar >= QLatin1Char('A') && newFirstChar <= QLatin1Char('Z')) {
-                // Apply group 'A' - 'Z'
-                newGroupValue = newFirstChar;
-                isLetter = true;
+            if (newFirstChar.isLetter()) {
+                // Try to find a matching group in the range 'A' to 'Z'.
+                static std::vector<QChar> lettersAtoZ;
+                if (lettersAtoZ.empty()) {
+                    for (char c = 'A'; c <= 'Z'; ++c) {
+                        lettersAtoZ.push_back(QLatin1Char(c));
+                    }
+                }
+
+                std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
+                if (it != lettersAtoZ.end()) {
+                    if (localeAwareLessThan(newFirstChar, *it) && it != lettersAtoZ.begin()) {
+                        // newFirstChar belongs to the group preceding *it.
+                        // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
+                        --it;
+                    }
+                    newGroupValue = *it;
+                } else {
+                    newGroupValue = newFirstChar;
+                }
             } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
                 // Apply group '0 - 9' for any name that starts with a digit
                 newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
-                isLetter = false;
             } else {
-                if (isLetter) {
-                    // If the current group is 'A' - 'Z' check whether a locale character
-                    // fits into the existing group.
-                    // TODO: This does not work in the case if e.g. the group 'O' starts with
-                    // an umlaut 'O' -> provide unit-test to document this known issue
-                    const QChar prevChar(firstChar.unicode() - ushort(1));
-                    const QChar nextChar(firstChar.unicode() + ushort(1));
-                    const QString currChar(newFirstChar);
-                    const bool partOfCurrentGroup = currChar.localeAwareCompare(prevChar) > 0 &&
-                                                    currChar.localeAwareCompare(nextChar) < 0;
-                    if (partOfCurrentGroup) {
-                        continue;
-                    }
-                }
                 newGroupValue = i18nc("@title:group", "Others");
-                isLetter = false;
             }
 
             if (newGroupValue != groupValue) {
@@ -1905,7 +2011,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
 {
     QElapsedTimer timer;
     timer.start();
-    foreach (KFileItem item, items) {
+    foreach (KFileItem item, items) { // krazy:exclude=foreach
         item.determineMimeType();
         if (timer.elapsed() > timeout) {
             // Don't block the user interface, let the remaining items