diff --git a/src/widgets/kdirmodel.cpp b/src/widgets/kdirmodel.cpp index 66c24ee5..e82ca080 100644 --- a/src/widgets/kdirmodel.cpp +++ b/src/widgets/kdirmodel.cpp @@ -1,1298 +1,1298 @@ /* This file is part of the KDE project Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirmodel.h" #include "kdirlister.h" #include "kfileitem.h" #include "kio_widgets_debug.h" #include #include #include #include #include #include "joburlcache_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif class KDirModelNode; class KDirModelDirNode; static QUrl cleanupUrl(const QUrl &url) { QUrl u = url; u.setPath(QDir::cleanPath(u.path())); // remove double slashes in the path, simplify "foo/." to "foo/", etc. u = u.adjusted(QUrl::StripTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url. u.setQuery(QString()); u.setFragment(QString()); return u; } // We create our own tree behind the scenes to have fast lookup from an item to its parent, // and also to get the children of an item fast. class KDirModelNode { public: KDirModelNode(KDirModelDirNode *parent, const KFileItem &item) : m_item(item), m_parent(parent), m_preview() { } virtual ~KDirModelNode() { // Required, code will delete ptrs to this or a subclass. } // m_item is KFileItem() for the root item const KFileItem &item() const { return m_item; } void setItem(const KFileItem &item) { m_item = item; } KDirModelDirNode *parent() const { return m_parent; } // linear search int rowNumber() const; // O(n) QIcon preview() const { return m_preview; } void setPreview(const QPixmap &pix) { m_preview = QIcon(); m_preview.addPixmap(pix); } void setPreview(const QIcon &icn) { m_preview = icn; } private: KFileItem m_item; KDirModelDirNode *const m_parent; QIcon m_preview; }; // Specialization for directory nodes class KDirModelDirNode : public KDirModelNode { public: KDirModelDirNode(KDirModelDirNode *parent, const KFileItem &item) : KDirModelNode(parent, item), m_childNodes(), m_childCount(KDirModel::ChildCountUnknown), m_populated(false) {} virtual ~KDirModelDirNode() Q_DECL_OVERRIDE { qDeleteAll(m_childNodes); } QList m_childNodes; // owns the nodes // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount. int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); } void setChildCount(int count) { m_childCount = count; } bool isPopulated() const { return m_populated; } void setPopulated(bool populated) { m_populated = populated; } bool isSlow() const { return item().isSlow(); } // For removing all child urls from the global hash. void collectAllChildUrls(QList &urls) const { Q_FOREACH (KDirModelNode *node, m_childNodes) { const KFileItem &item = node->item(); urls.append(cleanupUrl(item.url())); if (item.isDir()) { static_cast(node)->collectAllChildUrls(urls); } } } private: int m_childCount: 31; bool m_populated: 1; }; int KDirModelNode::rowNumber() const { if (!m_parent) { return 0; } return m_parent->m_childNodes.indexOf(const_cast(this)); } //// class KDirModelPrivate { public: KDirModelPrivate(KDirModel *model) : q(model), m_dirLister(nullptr), m_rootNode(new KDirModelDirNode(nullptr, KFileItem())), m_dropsAllowed(KDirModel::NoDrops), m_jobTransfersVisible(false) { } ~KDirModelPrivate() { delete m_rootNode; } void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &); void _k_slotDeleteItems(const KFileItemList &); void _k_slotRefreshItems(const QList > &); void _k_slotClear(); void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl); void _k_slotJobUrlsChanged(const QStringList &urlList); void clear() { delete m_rootNode; m_rootNode = new KDirModelDirNode(nullptr, KFileItem()); } // Emit expand for each parent and then return the // last known parent if there is no node for this url KDirModelNode *expandAllParentsUntil(const QUrl &url) const; // Return the node for a given url, using the hash. KDirModelNode *nodeForUrl(const QUrl &url) const; KDirModelNode *nodeForIndex(const QModelIndex &index) const; QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const; bool isDir(KDirModelNode *node) const { return (node == m_rootNode) || node->item().isDir(); } QUrl urlForNode(KDirModelNode *node) const { /** * Queries and fragments are removed from the URL, so that the URL of * child items really starts with the URL of the parent. * * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100 * so we have to remove the query in both to be able to compare the URLs */ QUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url()); if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary. url.setQuery(QString()); url.setFragment(QString()); // kill ref (#171117) } return url; } void removeFromNodeHash(KDirModelNode *node, const QUrl &url); void clearAllPreviews(KDirModelDirNode *node); #ifndef NDEBUG void dump(); #endif KDirModel *q; KDirLister *m_dirLister; KDirModelDirNode *m_rootNode; KDirModel::DropsAllowed m_dropsAllowed; bool m_jobTransfersVisible; // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient), // value = final url[s] being fetched QMap > m_urlsBeingFetched; QHash m_nodeHash; // global node hash: url -> node QStringList m_allCurrentDestUrls; //list of all dest urls that have jobs on them (e.g. copy, download) }; KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string) { QUrl url = cleanupUrl(_url); if (url == urlForNode(m_rootNode)) { return m_rootNode; } return m_nodeHash.value(url); } void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url) { if (node->item().isDir()) { QList urls; static_cast(node)->collectAllChildUrls(urls); Q_FOREACH (const QUrl &u, urls) { m_nodeHash.remove(u); } } m_nodeHash.remove(cleanupUrl(url)); } KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth) { QUrl url = cleanupUrl(_url); //qDebug() << url; QUrl nodeUrl = urlForNode(m_rootNode); if (url == nodeUrl) { return m_rootNode; } // Protocol mismatch? Don't even start comparing paths then. #171721 if (url.scheme() != nodeUrl.scheme()) { return nullptr; } const QString pathStr = url.path(); // no trailing slash KDirModelDirNode *dirNode = m_rootNode; if (!pathStr.startsWith(nodeUrl.path())) { return nullptr; } for (;;) { QString nodePath = nodeUrl.path(); if (!nodePath.endsWith('/')) { nodePath += '/'; } if (!pathStr.startsWith(nodePath)) { qCWarning(KIO_WIDGETS) << "The kioslave for" << url.scheme() << "violates the hierarchy structure:" << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path."; return nullptr; } // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b const int nextSlash = pathStr.indexOf('/', nodePath.length()); const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1 nodeUrl.setPath(newPath); nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508 KDirModelNode *node = nodeForUrl(nodeUrl); if (!node) { //qDebug() << "child equal or starting with" << url << "not found"; // return last parent found: return dirNode; } emit q->expand(indexForNode(node)); //qDebug() << " nodeUrl=" << nodeUrl; if (nodeUrl == url) { //qDebug() << "Found node" << node << "for" << url; return node; } //qDebug() << "going into" << node->item().url(); Q_ASSERT(isDir(node)); dirNode = static_cast(node); } // NOTREACHED //return 0; } #ifndef NDEBUG void KDirModelPrivate::dump() { qCDebug(KIO_WIDGETS) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url(); QHashIterator it(m_nodeHash); while (it.hasNext()) { it.next(); qDebug(KIO_WIDGETS) << it.key() << it.value(); } } #endif // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n). QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const { if (node == m_rootNode) { return QModelIndex(); } Q_ASSERT(node->parent()); return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node); } // index -> node. O(1) KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : m_rootNode; } /* * This model wraps the data held by KDirLister. * * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree. * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer= * * Invalid parent index means root of the tree, m_rootNode */ #ifndef NDEBUG static QString debugIndex(const QModelIndex &index) { QString str; if (!index.isValid()) { str = QStringLiteral("[invalid index, i.e. root]"); } else { KDirModelNode *node = static_cast(index.internalPointer()); str = "[index for " + node->item().url().toString(); if (index.column() > 0) { str += ", column " + QString::number(index.column()); } str += ']'; } return str; } #endif KDirModel::KDirModel(QObject *parent) : QAbstractItemModel(parent), d(new KDirModelPrivate(this)) { setDirLister(new KDirLister(this)); } KDirModel::~KDirModel() { delete d; } void KDirModel::setDirLister(KDirLister *dirLister) { if (d->m_dirLister) { d->clear(); delete d->m_dirLister; } d->m_dirLister = dirLister; d->m_dirLister->setParent(this); - connect(d->m_dirLister, SIGNAL(itemsAdded(QUrl,KFileItemList)), - this, SLOT(_k_slotNewItems(QUrl,KFileItemList))); - connect(d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), - this, SLOT(_k_slotDeleteItems(KFileItemList))); - connect(d->m_dirLister, SIGNAL(refreshItems(QList >)), - this, SLOT(_k_slotRefreshItems(QList >))); - connect(d->m_dirLister, SIGNAL(clear()), - this, SLOT(_k_slotClear())); - connect(d->m_dirLister, SIGNAL(redirection(QUrl,QUrl)), - this, SLOT(_k_slotRedirection(QUrl,QUrl))); + connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, + [this](const QUrl &dirUrl, const KFileItemList &items){d->_k_slotNewItems(dirUrl, items);} ); + connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, + [this](const KFileItemList &items){d->_k_slotDeleteItems(items);} ); + connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, + [this](const QList > &items){d->_k_slotRefreshItems(items);} ); + connect(d->m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, + [this](){d->_k_slotClear();} ); + connect(d->m_dirLister, QOverload::of(&KCoreDirLister::redirection), this, + [this](const QUrl &oldUrl, const QUrl &newUrl){d->_k_slotRedirection(oldUrl, newUrl);} ); } KDirLister *KDirModel::dirLister() const { return d->m_dirLister; } void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items) { //qDebug() << "directoryUrl=" << directoryUrl; KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) // If the directory containing the items wasn't found, then we have a big problem. // Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead. if (!result) { qCWarning(KIO_WIDGETS) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!" << "Root directory:" << urlForNode(m_rootNode); Q_FOREACH (const KFileItem &item, items) { qDebug() << "Item:" << item.url(); } #ifndef NDEBUG dump(); #endif Q_ASSERT(result); } Q_ASSERT(isDir(result)); KDirModelDirNode *dirNode = static_cast(result); const QModelIndex index = indexForNode(dirNode); // O(n) const int newItemsCount = items.count(); const int newRowCount = dirNode->m_childNodes.count() + newItemsCount; #if 0 #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount; #endif #endif q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last const QList urlsBeingFetched = m_urlsBeingFetched.value(dirNode); //qDebug() << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched; QList emitExpandFor; KFileItemList::const_iterator it = items.begin(); KFileItemList::const_iterator end = items.end(); for (; it != end; ++it) { const bool isDir = it->isDir(); KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, *it) : new KDirModelNode(dirNode, *it); #ifndef NDEBUG // Test code for possible duplication of items in the childnodes list, // not sure if/how it ever happened. //if (dirNode->m_childNodes.count() && // dirNode->m_childNodes.last()->item().name() == (*it).name()) { // qCWarning(KIO_WIDGETS) << "Already having" << (*it).name() << "in" << directoryUrl // << "url=" << dirNode->m_childNodes.last()->item().url(); // abort(); //} #endif dirNode->m_childNodes.append(node); const QUrl url = it->url(); m_nodeHash.insert(cleanupUrl(url), node); //qDebug() << url; if (!urlsBeingFetched.isEmpty()) { const QUrl dirUrl(url); foreach (const QUrl &urlFetched, urlsBeingFetched) { if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) { //qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched; const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1); Q_ASSERT(parentIndex.isValid()); emitExpandFor.append(parentIndex); if (isDir && dirUrl != urlFetched) { q->fetchMore(parentIndex); m_urlsBeingFetched[node].append(urlFetched); } } } } } m_urlsBeingFetched.remove(dirNode); q->endInsertRows(); // Emit expand signal after rowsInserted signal has been emitted, // so that any proxy model will have updated its mapping already Q_FOREACH (const QModelIndex &idx, emitExpandFor) { emit q->expand(idx); } } void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items) { //qDebug() << items.count(); // I assume all items are from the same directory. // From KDirLister's code, this should be the case, except maybe emitChanges? const KFileItem item = items.first(); Q_ASSERT(!item.isNull()); QUrl url = item.url(); KDirModelNode *node = nodeForUrl(url); // O(depth) if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; return; } KDirModelDirNode *dirNode = node->parent(); if (!dirNode) { return; } QModelIndex parentIndex = indexForNode(dirNode); // O(n) // Short path for deleting a single item if (items.count() == 1) { const int r = node->rowNumber(); q->beginRemoveRows(parentIndex, r, r); removeFromNodeHash(node, url); delete dirNode->m_childNodes.takeAt(r); q->endRemoveRows(); return; } // We need to make lists of consecutive row numbers, for the beginRemoveRows call. // Let's use a bit array where each bit represents a given child node. const int childCount = dirNode->m_childNodes.count(); QBitArray rowNumbers(childCount, false); Q_FOREACH (const KFileItem &item, items) { if (!node) { // don't lookup the first item twice url = item.url(); node = nodeForUrl(url); if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; continue; } if (!node->parent()) { // The root node has been deleted, but it was not first in the list 'items'. // see https://bugs.kde.org/show_bug.cgi?id=196695 return; } } rowNumbers.setBit(node->rowNumber(), 1); // O(n) removeFromNodeHash(node, url); node = nullptr; } int start = -1; int end = -1; bool lastVal = false; // Start from the end, otherwise all the row numbers are offset while we go for (int i = childCount - 1; i >= 0; --i) { const bool val = rowNumbers.testBit(i); if (!lastVal && val) { end = i; //qDebug() << "end=" << end; } if ((lastVal && !val) || (i == 0 && val)) { start = val ? i : i + 1; //qDebug() << "beginRemoveRows" << start << end; q->beginRemoveRows(parentIndex, start, end); for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;) //qDebug() << "Removing from m_childNodes at" << r; delete dirNode->m_childNodes.takeAt(r); } q->endRemoveRows(); } lastVal = val; } } void KDirModelPrivate::_k_slotRefreshItems(const QList > &items) { QModelIndex topLeft, bottomRight; // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows // Solution 2: more fine-grained, actually figure out the beginning and end rows. for (QList >::const_iterator fit = items.begin(), fend = items.end(); fit != fend; ++fit) { Q_ASSERT(!fit->first.isNull()); Q_ASSERT(!fit->second.isNull()); const QUrl oldUrl = fit->first.url(); const QUrl newUrl = fit->second.url(); KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once //qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node; if (!node) { // not found [can happen when renaming a dir, redirection was emitted already] continue; } if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead. bool hasNewNode = false; // A file became directory (well, it was overwritten) if (fit->first.isDir() != fit->second.isDir()) { //qDebug() << "DIR/FILE STATUS CHANGE"; const int r = node->rowNumber(); removeFromNodeHash(node, oldUrl); KDirModelDirNode *dirNode = node->parent(); delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node" node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second) : new KDirModelNode(dirNode, fit->second); dirNode->m_childNodes.insert(r, node); // same position! hasNewNode = true; } else { node->setItem(fit->second); } if (oldUrl != newUrl || hasNewNode) { // What if a renamed dir had children? -> kdirlister takes care of emitting for each item //qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash"; m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); } // Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13) if (fit->first.determineMimeType().name() != fit->second.determineMimeType().name()) { node->setPreview(QIcon()); } const QModelIndex index = indexForNode(node); if (!topLeft.isValid() || index.row() < topLeft.row()) { topLeft = index; } if (!bottomRight.isValid() || index.row() > bottomRight.row()) { bottomRight = index; } } } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight); Q_UNUSED(debugIndex(QModelIndex())); // fix compiler warning #endif bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1); emit q->dataChanged(topLeft, bottomRight); } // Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup) // and when renaming a directory. void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl) { KDirModelNode *node = nodeForUrl(oldUrl); if (!node) { return; } m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); // Ensure the node's URL is updated. In case of a listjob redirection // we won't get a refreshItem, and in case of renaming a directory // we'll get it too late (so the hash won't find the old url anymore). KFileItem item = node->item(); if (!item.isNull()) { // null if root item, #180156 item.setUrl(newUrl); node->setItem(item); } // The items inside the renamed directory have been handled before, // KDirLister took care of emitting refreshItem for each of them. } void KDirModelPrivate::_k_slotClear() { const int numRows = m_rootNode->m_childNodes.count(); if (numRows > 0) { q->beginRemoveRows(QModelIndex(), 0, numRows - 1); q->endRemoveRows(); } m_nodeHash.clear(); //emit layoutAboutToBeChanged(); clear(); //emit layoutChanged(); } void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList) { QStringList dirtyUrls; std::set_symmetric_difference(urlList.begin(), urlList.end(), m_allCurrentDestUrls.constBegin(), m_allCurrentDestUrls.constEnd(), std::back_inserter(dirtyUrls)); m_allCurrentDestUrls = urlList; for (const QString &dirtyUrl : dirtyUrls) { if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) { const QModelIndex idx = indexForNode(node); emit q->dataChanged(idx, idx, {KDirModel::HasJobRole}); } } } void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode) { const int numRows = dirNode->m_childNodes.count(); if (numRows > 0) { KDirModelNode *lastNode = nullptr; for (KDirModelNode *node : dirNode->m_childNodes) { node->setPreview(QIcon()); //node->setPreview(QIcon::fromTheme(node->item().iconName())); if (isDir(node)) { // recurse into child dirs clearAllPreviews(static_cast(node)); } lastNode = node; } emit q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1) indexForNode(lastNode, numRows - 1)); // O(1) } } void KDirModel::clearAllPreviews() { d->clearAllPreviews(d->m_rootNode); } void KDirModel::itemChanged(const QModelIndex &index) { // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator. // When the mimetype is determined, clear the old "preview" (could be // mimetype dependent like when cutting files, #164185) KDirModelNode *node = d->nodeForIndex(index); if (node) { node->setPreview(QIcon()); } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(index); #endif emit dataChanged(index, index); } int KDirModel::columnCount(const QModelIndex &) const { return ColumnCount; } QVariant KDirModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item(node->item()); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Name: return item.text(); case Size: return KIO::convertSize(item.size()); // size formatted as QString case ModifiedTime: { QDateTime dt = item.time(KFileItem::ModificationTime); return dt.toString(Qt::SystemLocaleShortDate); } case Permissions: return item.permissionsString(); case Owner: return item.user(); case Group: return item.group(); case Type: return item.mimeComment(); } break; case Qt::EditRole: switch (index.column()) { case Name: return item.text(); } break; case Qt::DecorationRole: if (index.column() == Name) { if (!node->preview().isNull()) { //qDebug() << item->url() << " preview found"; return node->preview(); } Q_ASSERT(!item.isNull()); //qDebug() << item->url() << " overlays=" << item->overlays(); return KDE::icon(item.iconName(), item.overlays()); } break; case Qt::TextAlignmentRole: if (index.column() == Size) { // use a right alignment for L2R and R2L languages const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter; return int(alignment); } break; case Qt::ToolTipRole: return item.text(); case FileItemRole: return QVariant::fromValue(item); case ChildCountRole: if (!item.isDir()) { return ChildCountUnknown; } else { KDirModelDirNode *dirNode = static_cast(node); int count = dirNode->childCount(); if (count == ChildCountUnknown && item.isReadable() && !dirNode->isSlow()) { const QString path = item.localPath(); if (!path.isEmpty()) { // slow // QDir dir(path); // count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count(); #ifdef Q_OS_WIN QString s = path + QLatin1String("\\*.*"); s.replace('/', '\\'); count = 0; WIN32_FIND_DATA findData; HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData); if (hFile != INVALID_HANDLE_VALUE) { do { if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0') && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) { ++count; } } while (FindNextFile(hFile, &findData) != 0); FindClose(hFile); } #else DIR *dir = QT_OPENDIR(QFile::encodeName(path)); if (dir) { count = 0; QT_DIRENT *dirEntry = nullptr; while ((dirEntry = QT_READDIR(dir))) { if (dirEntry->d_name[0] == '.') { if (dirEntry->d_name[1] == '\0') { // skip "." continue; } if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".." continue; } } ++count; } QT_CLOSEDIR(dir); } #endif //qDebug() << "child count for " << path << ":" << count; dirNode->setChildCount(count); } } return count; } case HasJobRole: if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) { KDirModelNode *node = d->nodeForIndex(index); const QString url = node->item().url().toString(); //return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint. return QVariant(d->m_allCurrentDestUrls.contains(url)); } } } return QVariant(); } void KDirModel::sort(int column, Qt::SortOrder order) { // Not implemented - we should probably use QSortFilterProxyModel instead. return QAbstractItemModel::sort(column, order); } bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role) { switch (role) { case Qt::EditRole: if (index.column() == Name && value.type() == QVariant::String) { Q_ASSERT(index.isValid()); KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item = node->item(); const QString newName = value.toString(); if (newName.isEmpty() || newName == item.text() || (newName == QLatin1String(".")) || (newName == QLatin1String(".."))) { return true; } QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); job->uiDelegate()->setAutoErrorHandlingEnabled(true); // undo handling KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList() << item.url(), newUrl, job); return true; } break; case Qt::DecorationRole: if (index.column() == Name) { Q_ASSERT(index.isValid()); // Set new pixmap - e.g. preview KDirModelNode *node = static_cast(index.internalPointer()); //qDebug() << "setting icon for " << node->item()->url(); Q_ASSERT(node); if (value.type() == QVariant::Icon) { const QIcon icon(qvariant_cast(value)); node->setPreview(icon); } else if (value.type() == QVariant::Pixmap) { node->setPreview(qvariant_cast(value)); } emit dataChanged(index, index); return true; } break; default: break; } return false; } int KDirModel::rowCount(const QModelIndex &parent) const { KDirModelNode *node = d->nodeForIndex(parent); if (!node || !d->isDir(node)) { // #176555 return 0; } KDirModelDirNode *parentNode = static_cast(node); Q_ASSERT(parentNode); const int count = parentNode->m_childNodes.count(); #if 0 QStringList filenames; for (int i = 0; i < count; ++i) { filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName(); } //qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames; #endif return count; } QModelIndex KDirModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *childNode = static_cast(index.internalPointer()); Q_ASSERT(childNode); KDirModelNode *parentNode = childNode->parent(); Q_ASSERT(parentNode); return d->indexForNode(parentNode); // O(n) } // Reimplemented to avoid the default implementation which calls parent // (O(n) for finding the parent's row number for nothing). This implementation is O(1). QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *oldChildNode = static_cast(index.internalPointer()); Q_ASSERT(oldChildNode); KDirModelNode *parentNode = oldChildNode->parent(); Q_ASSERT(parentNode); Q_ASSERT(d->isDir(parentNode)); KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } return QModelIndex(); } static bool lessThan(const QUrl &left, const QUrl &right) { return left.toString().compare(right.toString()) < 0; } void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex) { emit needSequenceIcon(index, sequenceIndex); } void KDirModel::setJobTransfersVisible(bool value) { if (value) { d->m_jobTransfersVisible = true; connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection); JobUrlCache::instance().requestJobUrlsChanged(); } else { disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList))); } } bool KDirModel::jobTransfersVisible() const { return d->m_jobTransfersVisible; } QList KDirModel::simplifiedUrlList(const QList &urls) { if (!urls.count()) { return urls; } QList ret(urls); std::sort(ret.begin(), ret.end(), lessThan); QList::iterator it = ret.begin(); QUrl url = *it; ++it; while (it != ret.end()) { if (url.isParentOf(*it) || url == *it) { it = ret.erase(it); } else { url = *it; ++it; } } return ret; } QStringList KDirModel::mimeTypes() const { return KUrlMimeData::mimeDataTypes(); } QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const { QList urls, mostLocalUrls; bool canUseMostLocalUrls = true; foreach (const QModelIndex &index, indexes) { const KFileItem &item = d->nodeForIndex(index)->item(); urls << item.url(); bool isLocal; mostLocalUrls << item.mostLocalUrl(isLocal); if (!isLocal) { canUseMostLocalUrls = false; } } QMimeData *data = new QMimeData(); const bool different = canUseMostLocalUrls && (mostLocalUrls != urls); urls = simplifiedUrlList(urls); if (different) { mostLocalUrls = simplifiedUrlList(mostLocalUrls); KUrlMimeData::setUrls(urls, mostLocalUrls, data); } else { data->setUrls(urls); } return data; } // Public API; not much point in calling it internally KFileItem KDirModel::itemForIndex(const QModelIndex &index) const { if (!index.isValid()) { return d->m_dirLister->rootItem(); } else { return static_cast(index.internalPointer())->item(); } } #ifndef KIOWIDGETS_NO_DEPRECATED QModelIndex KDirModel::indexForItem(const KFileItem *item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item->url()); // O(n) } #endif QModelIndex KDirModel::indexForItem(const KFileItem &item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item.url()); // O(n) } // url -> index. O(n) QModelIndex KDirModel::indexForUrl(const QUrl &url) const { KDirModelNode *node = d->nodeForUrl(url); // O(depth) if (!node) { //qDebug() << url << "not found"; return QModelIndex(); } return d->indexForNode(node); // O(n) } QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const { KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1) Q_ASSERT(parentNode); if (d->isDir(parentNode)) { KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } } return QModelIndex(); } QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case Qt::DisplayRole: switch (section) { case Name: return i18nc("@title:column", "Name"); case Size: return i18nc("@title:column", "Size"); case ModifiedTime: return i18nc("@title:column", "Date"); case Permissions: return i18nc("@title:column", "Permissions"); case Owner: return i18nc("@title:column", "Owner"); case Group: return i18nc("@title:column", "Group"); case Type: return i18nc("@title:column", "Type"); } } } return QVariant(); } bool KDirModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } const KFileItem &parentItem = static_cast(parent.internalPointer())->item(); Q_ASSERT(!parentItem.isNull()); return parentItem.isDir(); } Qt::ItemFlags KDirModel::flags(const QModelIndex &index) const { Qt::ItemFlags f = Qt::ItemIsEnabled; if (index.column() == Name) { f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; } // Allow dropping onto this item? if (d->m_dropsAllowed != NoDrops) { if (!index.isValid()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { KFileItem item = itemForIndex(index); if (item.isNull()) { qCWarning(KIO_WIDGETS) << "Invalid item returned for index"; } else if (item.isDir()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { // regular file item if (d->m_dropsAllowed & DropOnAnyFile) { f |= Qt::ItemIsDropEnabled; } else if (d->m_dropsAllowed & DropOnLocalExecutable) { if (!item.localPath().isEmpty()) { // Desktop file? if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) { f |= Qt::ItemIsDropEnabled; } // Executable, shell script ... ? else if (QFileInfo(item.localPath()).isExecutable()) { f |= Qt::ItemIsDropEnabled; } } } } } } return f; } bool KDirModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } // We now have a bool KDirModelNode::m_populated, // to avoid calling fetchMore more than once on empty dirs. // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview? // Maybe we can ask KDirLister "have you listed already"? (to discuss with M. Brade) KDirModelNode *node = static_cast(parent.internalPointer()); const KFileItem &item = node->item(); return item.isDir() && !static_cast(node)->isPopulated() && static_cast(node)->m_childNodes.isEmpty(); } void KDirModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } KDirModelNode *parentNode = static_cast(parent.internalPointer()); KFileItem parentItem = parentNode->item(); Q_ASSERT(!parentItem.isNull()); if (!parentItem.isDir()) { return; } KDirModelDirNode *dirNode = static_cast(parentNode); if (dirNode->isPopulated()) { return; } dirNode->setPopulated(true); const QUrl parentUrl = parentItem.url(); d->m_dirLister->openUrl(parentUrl, KDirLister::Keep); } bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { // Not sure we want to implement any drop handling at this level, // but for sure the default QAbstractItemModel implementation makes no sense for a dir model. Q_UNUSED(data); Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(column); Q_UNUSED(parent); return false; } void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed) { d->m_dropsAllowed = dropsAllowed; } void KDirModel::expandToUrl(const QUrl &url) { // emit expand for each parent and return last parent KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth) //qDebug() << url << result; if (!result) { // doesn't seem related to our base url? return; } if (!(result->item().isNull()) && result->item().url() == url) { // We have it already, nothing to do //qDebug() << "have it already item=" <item()*/; return; } d->m_urlsBeingFetched[result].append(url); if (result == d->m_rootNode) { //qDebug() << "Remembering to emit expand after listing the root url"; // the root is fetched by default, so it must be currently being fetched return; } //qDebug() << "Remembering to emit expand after listing" << result->item().url(); // start a new fetch to look for the next level down the URL const QModelIndex parentIndex = d->indexForNode(result); // O(n) Q_ASSERT(parentIndex.isValid()); fetchMore(parentIndex); } bool KDirModel::insertRows(int, int, const QModelIndex &) { return false; } bool KDirModel::insertColumns(int, int, const QModelIndex &) { return false; } bool KDirModel::removeRows(int, int, const QModelIndex &) { return false; } bool KDirModel::removeColumns(int, int, const QModelIndex &) { return false; } #include "moc_kdirmodel.cpp" diff --git a/src/widgets/kdirmodel.h b/src/widgets/kdirmodel.h index c66df755..6f8b56f2 100644 --- a/src/widgets/kdirmodel.h +++ b/src/widgets/kdirmodel.h @@ -1,286 +1,279 @@ /* This file is part of the KDE project Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDIRMODEL_H #define KDIRMODEL_H #include #include "kiowidgets_export.h" #include class KDirLister; class KDirModelPrivate; class JobUrlCache; /** * @class KDirModel kdirmodel.h * * @short A model for a KIO-based directory tree. * * KDirModel implements the QAbstractItemModel interface (for use with Qt's model/view widgets) * around the directory listing for one directory or a tree of directories. * * Note that there are some cases when using QPersistentModelIndexes from this model will not give * expected results. QPersistentIndexes will remain valid and updated if its siblings are added or * removed. However, if the QPersistentIndex or one of its ancestors is moved, the QPersistentIndex will become * invalid. For example, if a file or directory is renamed after storing a QPersistentModelIndex for it, * the index (along with any stored children) will become invalid even though it is still in the model. The reason * for this is that moves of files and directories are treated as separate insert and remove actions. * * @see KDirSortFilterProxyModel * * @author David Faure * Based on work by Hamish Rodda and Pascal Letourneau */ class KIOWIDGETS_EXPORT KDirModel : public QAbstractItemModel { Q_OBJECT public: /** * @param parent parent qobject */ explicit KDirModel(QObject *parent = nullptr); ~KDirModel(); /** * Set the directory lister to use by this model, instead of the default KDirLister created internally. * The model takes ownership. */ void setDirLister(KDirLister *dirLister); /** * Return the directory lister used by this model. */ KDirLister *dirLister() const; /** * Return the fileitem for a given index. This is O(1), i.e. fast. */ KFileItem itemForIndex(const QModelIndex &index) const; /** * Return the index for a given kfileitem. This can be slow. * @deprecated use the method that takes a KFileItem by value */ #ifndef KIOWIDGETS_NO_DEPRECATED KIOWIDGETS_DEPRECATED QModelIndex indexForItem(const KFileItem *) const; #endif /** * Return the index for a given kfileitem. This can be slow. */ QModelIndex indexForItem(const KFileItem &) const; /** * Return the index for a given url. This can be slow. */ QModelIndex indexForUrl(const QUrl &url) const; /** * @short Lists subdirectories using fetchMore() as needed until the given @p url exists in the model. * * When the model is used by a treeview, call KDirLister::openUrl with the base url of the tree, * then the treeview will take care of calling fetchMore() when the user opens directories. * However if you want the tree to show a given URL (i.e. open the tree recursively until that URL), * call expandToUrl(). * Note that this is asynchronous; the necessary listing of subdirectories will take time so * the model will not immediately have this url available. * The model emits the signal expand() when an index has become available; this can be connected * to the treeview in order to let it open that index. * @param url the url of a subdirectory of the directory model (or a file in a subdirectory) */ void expandToUrl(const QUrl &url); /** * Notify the model that the item at this index has changed. * For instance because KMimeTypeResolver called determineMimeType on it. * This makes the model emit its dataChanged signal at this index, so that views repaint. * Note that for most things (renaming, changing size etc.), KDirLister's signals tell the model already. */ void itemChanged(const QModelIndex &index); /** * Forget all previews (optimization for turning previews off). * The items will again have their default appearance (not controlled by the model). * @since 5.28 */ void clearAllPreviews(); /** * Useful "default" columns. Views can use a proxy to have more control over this. */ enum ModelColumns { Name = 0, Size, ModifiedTime, Permissions, Owner, Group, Type, ColumnCount }; /// Possible return value for data(ChildCountRole), meaning the item isn't a directory, /// or we haven't calculated its child count yet enum { ChildCountUnknown = -1 }; enum AdditionalRoles { // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) // to define additional roles. FileItemRole = 0x07A263FF, ///< returns the KFileItem for a given index ChildCountRole = 0x2C4D0A40, ///< returns the number of items in a directory, or ChildCountUnknown HasJobRole = 0x01E555A5 ///< returns whether or not there is a job on an item (file/directory) }; enum DropsAllowedFlag { NoDrops = 0, DropOnDirectory = 1, ///< allow drops on any directory DropOnAnyFile = 2, ///< allow drops on any file DropOnLocalExecutable = 4 ///< allow drops on local executables, shell scripts and desktop files. Can be used with DropOnDirectory. }; Q_DECLARE_FLAGS(DropsAllowed, DropsAllowedFlag) /// Set whether dropping onto items should be allowed, and for which kind of item /// Drops are disabled by default. void setDropsAllowed(DropsAllowed dropsAllowed); /// Reimplemented from QAbstractItemModel. Returns true for empty directories. bool canFetchMore(const QModelIndex &parent) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns ColumnCount. int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Not implemented yet. bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Lists the subdirectory. void fetchMore(const QModelIndex &parent) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns true for directories. bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Returns the column titles. QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. O(1) QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QStringList mimeTypes() const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. QModelIndex sibling(int row, int column, const QModelIndex &index) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. /// Call this to set a new icon, e.g. a preview bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; /// Reimplemented from QAbstractItemModel. Not implemented. @see KDirSortFilterProxyModel void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; /** * Remove urls from the list if an ancestor is present on the list. This can * be used to delete only the ancestor url and skip a potential error of a non-existent url. * * For example, for a list of "/home/foo/a", "/home/foo/a/a.txt", "/home/foo/a/a/a.txt", "/home/foo/a/b/b.txt", * "home/foo/b/b.txt", this method will return the list "/home/foo/a", "/home/foo/b/b.txt". * * @return the list @p urls without parented urls inside. * @since 4.2 */ static QList simplifiedUrlList(const QList &urls); /** * This emits the needSequenceIcon signal, requesting another sequence icon * * If there is a KFilePreviewGenerator attached to this model, that generator will care * about creating another preview. * * @param index Index of the item that should get another icon * @param sequenceIndex Index in the sequence. If it is zero, the standard icon will be assigned. * For higher indices, arbitrary different meaningful icons will be generated. * @since 4.3 */ void requestSequenceIcon(const QModelIndex &index, int sequenceIndex); /** * Enable/Disable the displaying of an animated overlay that is shown for any destination * urls (in the view). When enabled, the animations (if any) will be drawn automatically. * * Only the files/folders that are visible and have jobs associated with them * will display the animation. * You would likely not want this enabled if you perform some kind of custom painting * that takes up a whole item, and will just make this(and what you paint) look funky. * * Default is disabled. * * Note: KFileItemDelegate needs to have it's method called with the same * value, when you make the call to this method. * * @since 4.5 */ void setJobTransfersVisible(bool value); /** * Returns whether or not displaying job transfers has been enabled. * @since 4.5 */ bool jobTransfersVisible() const; Q_SIGNALS: /** * Emitted for each subdirectory that is a parent of a url passed to expandToUrl * This allows to asynchronously open a tree view down to a given directory. * Also emitted for the final file, if expandToUrl is called with a file * (for instance so that it can be selected). */ void expand(const QModelIndex &index); /** * Emitted when another icon sequence index is requested * @param index Index of the item that should get another icon * @param sequenceIndex Index in the sequence. If it is zero, the standard icon should be assigned. * For higher indices, arbitrary different meaningful icons should be generated. * This is usually slowly counted up while the user hovers the icon. * If no meaningful alternative icons can be generated, this should be ignored. * @since 4.3 */ void needSequenceIcon(const QModelIndex &index, int sequenceIndex); private: // Make those private, they shouldn't be called by applications bool insertRows(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool insertColumns(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool removeRows(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; bool removeColumns(int, int, const QModelIndex & = QModelIndex()) Q_DECL_OVERRIDE; private: friend class KDirModelPrivate; KDirModelPrivate *const d; - - Q_PRIVATE_SLOT(d, void _k_slotNewItems(const QUrl &, const KFileItemList &)) - Q_PRIVATE_SLOT(d, void _k_slotDeleteItems(const KFileItemList &)) - Q_PRIVATE_SLOT(d, void _k_slotRefreshItems(const QList > &)) - Q_PRIVATE_SLOT(d, void _k_slotClear()) - Q_PRIVATE_SLOT(d, void _k_slotRedirection(const QUrl &, const QUrl &)) - Q_PRIVATE_SLOT(d, void _k_slotJobUrlsChanged(const QStringList &)) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDirModel::DropsAllowed) #endif /* KDIRMODEL_H */