#define KFILEITEMMODEL_DEBUG
KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) :
- KItemModelBase(QByteArray(), "name", parent),
+ KItemModelBase("name", parent),
m_dirLister(dirLister),
m_naturalSorting(true),
m_sortFoldersFirst(true),
- m_groupRole(NoRole),
m_sortRole(NameRole),
m_caseSensitivity(Qt::CaseInsensitive),
m_sortedItems(),
m_minimumUpdateIntervalTimer(0),
m_maximumUpdateIntervalTimer(0),
m_pendingItemsToInsert(),
- m_rootExpansionLevel(-1)
+ m_pendingEmitLoadingCompleted(false),
+ m_rootExpansionLevel(-1),
+ m_expandedUrls(),
+ m_restoredExpandedUrls()
{
resetRoles();
m_requestRole[NameRole] = true;
connect(dirLister, SIGNAL(completed()), this, SLOT(slotCompleted()));
connect(dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
connect(dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
+ connect(dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
connect(dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
connect(dirLister, SIGNAL(clear(KUrl)), this, SLOT(slotClear(KUrl)));
return false;
}
-bool KFileItemModel::supportsGrouping() const
+void KFileItemModel::setSortFoldersFirst(bool foldersFirst)
{
- return true;
+ if (foldersFirst != m_sortFoldersFirst) {
+ m_sortFoldersFirst = foldersFirst;
+ resortAllItems();
+ }
}
-bool KFileItemModel::supportsSorting() const
+bool KFileItemModel::sortFoldersFirst() const
{
- return true;
+ return m_sortFoldersFirst;
}
QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const
{
startFromIndex = qMax(0, startFromIndex);
- for (int i = startFromIndex; i < count(); i++) {
+ for (int i = startFromIndex; i < count(); ++i) {
if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) {
- kDebug() << data(i)["name"].toString();
return i;
}
}
- for (int i = 0; i < startFromIndex; i++) {
+ for (int i = 0; i < startFromIndex; ++i) {
if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) {
- kDebug() << data(i)["name"].toString();
return i;
}
}
return item.isNull() ? false : item.isDir();
}
+QString KFileItemModel::roleDescription(const QByteArray& role) const
+{
+ QString descr;
+
+ switch (roleIndex(role)) {
+ case NameRole: descr = i18nc("@item:intable", "Name"); break;
+ case SizeRole: descr = i18nc("@item:intable", "Size"); break;
+ case DateRole: descr = i18nc("@item:intable", "Date"); break;
+ case PermissionsRole: descr = i18nc("@item:intable", "Permissions"); break;
+ case OwnerRole: descr = i18nc("@item:intable", "Owner"); break;
+ case GroupRole: descr = i18nc("@item:intable", "Group"); break;
+ case TypeRole: descr = i18nc("@item:intable", "Type"); break;
+ case DestinationRole: descr = i18nc("@item:intable", "Destination"); break;
+ case PathRole: descr = i18nc("@item:intable", "Path"); break;
+ case NoRole: break;
+ case IsDirRole: break;
+ case IsExpandedRole: break;
+ case ExpansionLevelRole: break;
+ default: Q_ASSERT(false); break;
+ }
+
+ return descr;
+}
+
+QList<QPair<int, QVariant> > KFileItemModel::groups() const
+{
+ // TODO:
+ QPair<int, QVariant> group1(0, "Group 1");
+ QPair<int, QVariant> group2(5, "Group 2");
+ QPair<int, QVariant> group3(10, "Group 3");
+
+ QList<QPair<int, QVariant> > groups;
+ groups.append(group1);
+ groups.append(group2);
+ groups.append(group3);
+ return groups;
+}
+
KFileItem KFileItemModel::fileItem(int index) const
{
if (index >= 0 && index < count()) {
return KFileItem();
}
+KFileItem KFileItemModel::fileItem(const KUrl& url) const
+{
+ const int index = m_items.value(url, -1);
+ if (index >= 0) {
+ return m_sortedItems.at(index);
+ }
+ return KFileItem();
+}
+
int KFileItemModel::index(const KFileItem& item) const
{
if (item.isNull()) {
return -1;
}
- return m_items.value(item, -1);
+ return m_items.value(item.url(), -1);
+}
+
+int KFileItemModel::index(const KUrl& url) const
+{
+ KUrl urlToFind = url;
+ urlToFind.adjustPath(KUrl::RemoveTrailingSlash);
+ return m_items.value(urlToFind, -1);
+}
+
+KFileItem KFileItemModel::rootItem() const
+{
+ const KDirLister* dirLister = m_dirLister.data();
+ if (dirLister) {
+ return dirLister->rootItem();
+ }
+ return KFileItem();
}
void KFileItemModel::clear()
return false;
}
+ const KUrl url = m_sortedItems.at(index).url();
if (expanded) {
- const KUrl url = m_sortedItems.at(index).url();
+ m_expandedUrls.insert(url);
+
KDirLister* dirLister = m_dirLister.data();
if (dirLister) {
dirLister->openUrl(url, KDirLister::Keep);
return true;
}
} else {
+ m_expandedUrls.remove(url);
+
KFileItemList itemsToRemove;
const int expansionLevel = data(index)["expansionLevel"].toInt();
++index;
return false;
}
-void KFileItemModel::onGroupRoleChanged(const QByteArray& current, const QByteArray& previous)
+QSet<KUrl> KFileItemModel::expandedUrls() const
{
- Q_UNUSED(previous);
- m_groupRole = roleIndex(current);
+ return m_expandedUrls;
+}
+
+void KFileItemModel::restoreExpandedUrls(const QSet<KUrl>& urls)
+{
+ m_restoredExpandedUrls = urls;
+}
+
+void KFileItemModel::onGroupedSortingChanged(bool current)
+{
+ Q_UNUSED(current);
}
void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
{
Q_UNUSED(previous);
- const int itemCount = count();
- if (itemCount <= 0) {
- return;
- }
-
m_sortRole = roleIndex(current);
+ resortAllItems();
+}
- KFileItemList sortedItems = m_sortedItems;
- m_sortedItems.clear();
- m_items.clear();
- m_data.clear();
- emit itemsRemoved(KItemRangeList() << KItemRange(0, itemCount));
-
- sort(sortedItems.begin(), sortedItems.end());
- int index = 0;
- foreach (const KFileItem& item, sortedItems) {
- m_sortedItems.append(item);
- m_items.insert(item, index);
- m_data.append(retrieveData(item));
-
- ++index;
- }
-
- emit itemsInserted(KItemRangeList() << KItemRange(0, itemCount));
+void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ resortAllItems();
}
void KFileItemModel::slotCompleted()
{
- if (m_minimumUpdateIntervalTimer->isActive()) {
+ if (m_restoredExpandedUrls.isEmpty() && m_minimumUpdateIntervalTimer->isActive()) {
// dispatchPendingItems() will be called when the timer
// has been expired.
+ m_pendingEmitLoadingCompleted = true;
return;
}
+ m_pendingEmitLoadingCompleted = false;
dispatchPendingItemsToInsert();
+
+ if (!m_restoredExpandedUrls.isEmpty()) {
+ // Try to find a URL that can be expanded.
+ // Note that the parent folder must be expanded before any of its subfolders become visible.
+ // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
+ // -> we expand the first visible URL we find in m_restoredExpandedUrls.
+ foreach(const KUrl& url, m_restoredExpandedUrls) {
+ const int index = m_items.value(url, -1);
+ if (index >= 0) {
+ // We have found an expandable URL. Expand it and return - when
+ // the dir lister has finished, this slot will be called again.
+ m_restoredExpandedUrls.remove(url);
+ setExpanded(index, true);
+ return;
+ }
+ }
+
+ // None of the URLs in m_restoredExpandedUrls could be found in the model. This can happen
+ // if these URLs have been deleted in the meantime.
+ m_restoredExpandedUrls.clear();
+ }
+
+ emit loadingCompleted();
m_minimumUpdateIntervalTimer->start();
}
removeItems(items);
}
+void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
+{
+ Q_ASSERT(!items.isEmpty());
+#ifdef KFILEITEMMODEL_DEBUG
+ kDebug() << "Refreshing" << items.count() << "items";
+#endif
+
+ // Get the indexes of all items that have been refreshed
+ QList<int> indexes;
+ indexes.reserve(items.count());
+
+ QListIterator<QPair<KFileItem, KFileItem> > it(items);
+ while (it.hasNext()) {
+ const QPair<KFileItem, KFileItem>& itemPair = it.next();
+ const int index = m_items.value(itemPair.second.url(), -1);
+ if (index >= 0) {
+ indexes.append(index);
+ }
+ }
+
+ // If the changed items have been created recently, they might not be in m_items yet.
+ // In that case, the list 'indexes' might be empty.
+ if (indexes.isEmpty()) {
+ return;
+ }
+
+ // Extract the item-ranges out of the changed indexes
+ qSort(indexes);
+
+ KItemRangeList itemRangeList;
+ int rangeIndex = 0;
+ int rangeCount = 1;
+ int previousIndex = indexes.at(0);
+
+ const int maxIndex = indexes.count() - 1;
+ for (int i = 1; i <= maxIndex; ++i) {
+ const int currentIndex = indexes.at(i);
+ if (currentIndex == previousIndex + 1) {
+ ++rangeCount;
+ } else {
+ itemRangeList.append(KItemRange(rangeIndex, rangeCount));
+
+ rangeIndex = currentIndex;
+ rangeCount = 1;
+ }
+ previousIndex = currentIndex;
+ }
+
+ if (rangeCount > 0) {
+ itemRangeList.append(KItemRange(rangeIndex, rangeCount));
+ }
+
+ emit itemsChanged(itemRangeList, QSet<QByteArray>());
+}
+
void KFileItemModel::slotClear()
{
#ifdef KFILEITEMMODEL_DEBUG
m_data.clear();
emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
}
+
+ m_expandedUrls.clear();
}
void KFileItemModel::slotClear(const KUrl& url)
insertItems(m_pendingItemsToInsert);
m_pendingItemsToInsert.clear();
}
+
+ if (m_pendingEmitLoadingCompleted) {
+ emit loadingCompleted();
+ }
}
void KFileItemModel::insertItems(const KFileItemList& items)
// The indexes of all m_items must be adjusted, not only the index
// of the new items
for (int i = 0; i < m_sortedItems.count(); ++i) {
- m_items.insert(m_sortedItems.at(i), i);
+ m_items.insert(m_sortedItems.at(i).url(), i);
}
itemRanges << KItemRange(insertedAtIndex, insertedCount);
// Delete the items
for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
const int indexToRemove = indexesToRemove.at(i);
- m_items.remove(m_sortedItems.at(indexToRemove));
+ m_items.remove(m_sortedItems.at(indexToRemove).url());
m_sortedItems.removeAt(indexToRemove);
m_data.removeAt(indexToRemove);
}
// The indexes of all m_items must be adjusted, not only the index
// of the removed items
for (int i = 0; i < m_sortedItems.count(); ++i) {
- m_items.insert(m_sortedItems.at(i), i);
+ m_items.insert(m_sortedItems.at(i).url(), i);
}
if (count() <= 0) {
emit itemsRemoved(itemRanges);
}
-void KFileItemModel::removeExpandedItems()
+void KFileItemModel::resortAllItems()
{
+ const int itemCount = count();
+ if (itemCount <= 0) {
+ return;
+ }
+
+ const KFileItemList oldSortedItems = m_sortedItems;
+ const QHash<KUrl, int> oldItems = m_items;
+ const QList<QHash<QByteArray, QVariant> > oldData = m_data;
+
+ m_items.clear();
+ m_data.clear();
+ sort(m_sortedItems.begin(), m_sortedItems.end());
+ int index = 0;
+ foreach (const KFileItem& item, m_sortedItems) {
+ m_items.insert(item.url(), index);
+
+ const int oldItemIndex = oldItems.value(item.url());
+ m_data.append(oldData.at(oldItemIndex));
+
+ ++index;
+ }
+
+ bool emitItemsMoved = false;
+ QList<int> movedToIndexes;
+ movedToIndexes.reserve(m_sortedItems.count());
+ for (int i = 0; i < itemCount; i++) {
+ const int newIndex = m_items.value(oldSortedItems.at(i).url());
+ movedToIndexes.append(newIndex);
+ if (!emitItemsMoved && newIndex != i) {
+ emitItemsMoved = true;
+ }
+ }
+
+ if (emitItemsMoved) {
+ emit itemsMoved(KItemRange(0, itemCount), movedToIndexes);
+ }
+}
+
+void KFileItemModel::removeExpandedItems()
+{
KFileItemList expandedItems;
const int maxIndex = m_data.count() - 1;
removeItems(expandedItems);
m_rootExpansionLevel = -1;
+ m_expandedUrls.clear();
}
void KFileItemModel::resetRoles()
result = expansionLevelsCompare(a, b);
if (result != 0) {
// The items have parents with different expansion levels
- return result < 0;
+ return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
}
}
- if (m_sortFoldersFirst) {
+ if (m_sortFoldersFirst || m_sortRole == SizeRole) {
const bool isDirA = a.isDir();
const bool isDirB = b.isDir();
if (isDirA && !isDirB) {
break;
}
+ case SizeRole: {
+ // TODO: Implement sorting folders by the number of items inside.
+ // This is more tricky to get right because this number is retrieved
+ // asynchronously by KFileItemModelRolesUpdater.
+ const KIO::filesize_t sizeA = a.size();
+ const KIO::filesize_t sizeB = b.size();
+ if (sizeA < sizeB) {
+ result = -1;
+ } else if (sizeA > sizeB) {
+ result = +1;
+ }
+ break;
+ }
+
default:
break;
}
result = QString::compare(a.url().url(), b.url().url(), Qt::CaseSensitive);
}
- return result < 0;
+ return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
}
void KFileItemModel::sort(const KFileItemList::iterator& startIterator, const KFileItemList::iterator& endIterator)