• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KIO

kdirmodel.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kdirmodel.h"
00021 #include "kdirlister.h"
00022 #include "kfileitem.h"
00023 #include <kdatetime.h>
00024 #include <kicon.h>
00025 #include <klocale.h>
00026 #include <kglobal.h>
00027 #include <kio/copyjob.h>
00028 #include <kio/jobuidelegate.h>
00029 #include <kurl.h>
00030 #include <kdebug.h>
00031 #include <QMimeData>
00032 #include <QFile>
00033 #include <QFileInfo>
00034 #include <QDir>
00035 #include <sys/types.h>
00036 #include <dirent.h>
00037 
00038 class KDirModelNode;
00039 class KDirModelDirNode;
00040 
00041 static KUrl cleanupUrl(const KUrl& url) {
00042     KUrl u = url;
00043     u.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
00044     u.cleanPath(); // remove double slashes in the path
00045     u.setQuery(QString());
00046     u.setRef(QString());
00047     return u;
00048 }
00049 
00050 // We create our own tree behind the scenes to have fast lookup from an item to its parent,
00051 // and also to get the children of an item fast.
00052 class KDirModelNode
00053 {
00054 public:
00055     KDirModelNode( KDirModelDirNode* parent, const KFileItem& item ) :
00056         m_item(item),
00057         m_parent(parent),
00058         m_preview()
00059     {
00060     }
00061     // m_item is KFileItem() for the root item
00062     const KFileItem& item() const { return m_item; }
00063     void setItem(const KFileItem& item) { m_item = item; }
00064     KDirModelDirNode* parent() const { return m_parent; }
00065     // linear search
00066     int rowNumber() const; // O(n)
00067     QIcon preview() const { return m_preview; }
00068     void addPreview( const QPixmap& pix ) { m_preview.addPixmap(pix); }
00069     void setPreview( const QIcon& icn ) { m_preview = icn; }
00070 
00071 private:
00072     KFileItem m_item;
00073     KDirModelDirNode* const m_parent;
00074     QIcon m_preview;
00075 };
00076 
00077 // Specialization for directory nodes
00078 class KDirModelDirNode : public KDirModelNode
00079 {
00080 public:
00081     KDirModelDirNode( KDirModelDirNode* parent, const KFileItem& item)
00082         : KDirModelNode( parent, item),
00083           m_childNodes(),
00084           m_childCount(KDirModel::ChildCountUnknown),
00085           m_populated(false)
00086     {}
00087     ~KDirModelDirNode() {
00088         qDeleteAll(m_childNodes);
00089     }
00090     QList<KDirModelNode *> m_childNodes; // owns the nodes
00091     QHash<QString, KDirModelNode *> m_childNodesByName; // key = filename
00092 
00093     // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
00094     int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); }
00095     void setChildCount(int count) { m_childCount = count; }
00096     bool isPopulated() const { return m_populated; }
00097     void setPopulated( bool populated ) { m_populated = populated; }
00098 
00099     // For removing all child urls from the global hash.
00100     void collectAllChildUrls(KUrl::List &urls) const {
00101         Q_FOREACH(KDirModelNode* node, m_childNodes) {
00102             const KFileItem& item = node->item();
00103             urls.append(cleanupUrl(item.url()));
00104             if (item.isDir())
00105                 static_cast<KDirModelDirNode*>(node)->collectAllChildUrls(urls);
00106         }
00107     }
00108 
00109 private:
00110     int m_childCount:31;
00111     bool m_populated:1;
00112 };
00113 
00114 int KDirModelNode::rowNumber() const
00115 {
00116     if (!m_parent) return 0;
00117     return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode*>(this));
00118 }
00119 
00121 
00122 class KDirModelPrivate
00123 {
00124 public:
00125     KDirModelPrivate( KDirModel* model )
00126         : q(model), m_dirLister(0),
00127           m_rootNode(new KDirModelDirNode(0, KFileItem())),
00128           m_dropsAllowed(KDirModel::NoDrops)
00129     {
00130     }
00131     ~KDirModelPrivate() {
00132         delete m_rootNode;
00133     }
00134 
00135     void _k_slotNewItems(const KUrl& directoryUrl, const KFileItemList&);
00136     void _k_slotDeleteItems(const KFileItemList&);
00137     void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&);
00138     void _k_slotClear();
00139     void _k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl);
00140 
00141     void clear() {
00142         delete m_rootNode;
00143         m_rootNode = new KDirModelDirNode(0, KFileItem());
00144     }
00145     // Emit expand for each parent and then return the
00146     // last known parent if there is no node for this url
00147     KDirModelNode* expandAllParentsUntil(const KUrl& url) const;
00148 
00149     // Return the node for a given url, using the hash.
00150     KDirModelNode* nodeForUrl(const KUrl& url) const;
00151     KDirModelNode* nodeForIndex(const QModelIndex& index) const;
00152     QModelIndex indexForNode(KDirModelNode* node, int rowNumber = -1 /*unknown*/) const;
00153     bool isDir(KDirModelNode* node) const {
00154         return (node == m_rootNode) || node->item().isDir();
00155     }
00156     KUrl urlForNode(KDirModelNode* node) const {
00164         KUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url());
00165         if (url.hasQuery() || url.hasRef()) { // avoid detach if not necessary.
00166             url.setQuery(QString());
00167             url.setRef(QString()); // kill ref (#171117)
00168         }
00169         return url;
00170     }
00171     void removeFromNodeHash(KDirModelNode* node, const KUrl& url);
00172 #ifndef NDEBUG
00173     void dump();
00174 #endif
00175 
00176     KDirModel* q;
00177     KDirLister* m_dirLister;
00178     KDirModelDirNode* m_rootNode;
00179     KDirModel::DropsAllowed m_dropsAllowed;
00180     // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
00181     // value = final url[s] being fetched
00182     QMap<KDirModelNode*, KUrl::List> m_urlsBeingFetched;
00183     QHash<KUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
00184 };
00185 
00186 KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url) const // O(1), well, O(length of url as a string)
00187 {
00188     KUrl url = cleanupUrl(_url);
00189     if (url == urlForNode(m_rootNode))
00190         return m_rootNode;
00191     return m_nodeHash.value(url);
00192 }
00193 
00194 void KDirModelPrivate::removeFromNodeHash(KDirModelNode* node, const KUrl& url)
00195 {
00196     if (node->item().isDir()) {
00197         KUrl::List urls;
00198         static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(urls);
00199         Q_FOREACH(const KUrl& u, urls) {
00200             m_nodeHash.remove(u);
00201         }
00202     }
00203     m_nodeHash.remove(cleanupUrl(url));
00204 }
00205 
00206 KDirModelNode* KDirModelPrivate::expandAllParentsUntil(const KUrl& _url) const // O(depth)
00207 {
00208     KUrl url = cleanupUrl(_url);
00209 
00210     //kDebug(7008) << url;
00211     KUrl nodeUrl = urlForNode(m_rootNode);
00212     if (url == nodeUrl)
00213         return m_rootNode;
00214 
00215     // Protocol mismatch? Don't even start comparing paths then. #171721
00216     if (url.protocol() != nodeUrl.protocol())
00217         return 0;
00218 
00219     const QString pathStr = url.path(); // no trailing slash
00220     KDirModelDirNode* dirNode = m_rootNode;
00221 
00222     if (!pathStr.startsWith(nodeUrl.path())) {
00223         return 0;
00224     }
00225 
00226     for (;;) {
00227         const QString nodePath = nodeUrl.path(KUrl::AddTrailingSlash);
00228         if(!pathStr.startsWith(nodePath)) {
00229             kError(7008) << "The kioslave for" << url.protocol() << "violates the hierarchy structure:"
00230                          << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
00231             return 0;
00232         }
00233 
00234         // E.g. pathStr is /a/b/c and nodePath is /a. We want to find the child "b" in dirNode.
00235         const QString relativePath = pathStr.mid(nodePath.length());
00236         Q_ASSERT(!relativePath.startsWith('/')); // check if multiple slashes have been removed
00237         const int nextSlash = relativePath.indexOf('/');
00238         const QString fileName = relativePath.left(nextSlash); // works even if nextSlash==-1
00239         KDirModelNode* node = dirNode->m_childNodesByName.value(fileName);
00240         if (!node) {
00241             //kDebug(7008) << "child equal or starting with" << url << "not found";
00242             // return last parent found:
00243             return dirNode;
00244         }
00245 
00246         emit q->expand(indexForNode(node));
00247 
00248         nodeUrl = urlForNode(node);
00249         nodeUrl.adjustPath(KUrl::RemoveTrailingSlash); // #172508
00250         //kDebug(7008) << " nodeUrl=" << nodeUrl;
00251         if (nodeUrl == url) {
00252             //kDebug(7008) << "Found node" << node << "for" << url;
00253             return node;
00254         }
00255         //kDebug(7008) << "going into" << node->item().url();
00256         Q_ASSERT(isDir(node));
00257         dirNode = static_cast<KDirModelDirNode *>(node);
00258     }
00259     // NOTREACHED
00260     //return 0;
00261 }
00262 
00263 #ifndef NDEBUG
00264 void KDirModelPrivate::dump()
00265 {
00266     kDebug() << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
00267     QHashIterator<KUrl, KDirModelNode *> it(m_nodeHash);
00268     while (it.hasNext()) {
00269         it.next();
00270         kDebug() << it.key() << it.value();
00271     }
00272 }
00273 #endif
00274 
00275 // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
00276 QModelIndex KDirModelPrivate::indexForNode(KDirModelNode* node, int rowNumber) const
00277 {
00278     if (node == m_rootNode)
00279         return QModelIndex();
00280 
00281     Q_ASSERT(node->parent());
00282     return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
00283 }
00284 
00285 // index -> node. O(1)
00286 KDirModelNode* KDirModelPrivate::nodeForIndex(const QModelIndex& index) const
00287 {
00288     return index.isValid()
00289         ? static_cast<KDirModelNode*>(index.internalPointer())
00290         : m_rootNode;
00291 }
00292 
00293 /*
00294  * This model wraps the data held by KDirLister.
00295  *
00296  * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
00297  * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
00298  *
00299  * Invalid parent index means root of the tree, m_rootNode
00300  */
00301 
00302 #ifndef NDEBUG
00303 static QString debugIndex(const QModelIndex& index)
00304 {
00305     QString str;
00306     if (!index.isValid())
00307         str = "[invalid index, i.e. root]";
00308     else {
00309         KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00310         str = "[index for " + node->item().url().pathOrUrl();
00311         if (index.column() > 0)
00312             str += ", column " + QString::number(index.column());
00313         str += ']';
00314     }
00315     return str;
00316 }
00317 #endif
00318 
00319 KDirModel::KDirModel(QObject* parent)
00320     : QAbstractItemModel(parent),
00321       d(new KDirModelPrivate(this))
00322 {
00323     setDirLister(new KDirLister(this));
00324 }
00325 
00326 KDirModel::~KDirModel()
00327 {
00328     delete d;
00329 }
00330 
00331 void KDirModel::setDirLister(KDirLister* dirLister)
00332 {
00333     if (d->m_dirLister) {
00334         d->clear();
00335         delete d->m_dirLister;
00336     }
00337     d->m_dirLister = dirLister;
00338     d->m_dirLister->setParent(this);
00339     connect( d->m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)),
00340              this, SLOT(_k_slotNewItems(KUrl,KFileItemList)) );
00341     connect( d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)),
00342              this, SLOT(_k_slotDeleteItems(KFileItemList)) );
00343     connect( d->m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem, KFileItem> >)),
00344              this, SLOT(_k_slotRefreshItems(QList<QPair<KFileItem, KFileItem> >)) );
00345     connect( d->m_dirLister, SIGNAL(clear()),
00346              this, SLOT(_k_slotClear()) );
00347     connect(d->m_dirLister, SIGNAL(redirection(KUrl, KUrl)),
00348             this, SLOT(_k_slotRedirection(KUrl, KUrl)));
00349 }
00350 
00351 KDirLister* KDirModel::dirLister() const
00352 {
00353     return d->m_dirLister;
00354 }
00355 
00356 void KDirModelPrivate::_k_slotNewItems(const KUrl& directoryUrl, const KFileItemList& items)
00357 {
00358     //kDebug(7008) << "directoryUrl=" << directoryUrl;
00359 
00360     KDirModelNode* result = nodeForUrl(directoryUrl); // O(depth)
00361     // If the directory containing the items wasn't found, then we have a big problem.
00362     // Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead.
00363     if (!result) {
00364         kError(7008) << "Items emitted in directory" << directoryUrl
00365                      << "but that directory isn't in KDirModel!"
00366                      << "Root directory:" << urlForNode(m_rootNode);
00367 #ifndef NDEBUG
00368         dump();
00369 #endif
00370         Q_ASSERT(result);
00371     }
00372     Q_ASSERT(isDir(result));
00373     KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(result);
00374 
00375     const QModelIndex index = indexForNode(dirNode); // O(n)
00376     const int newItemsCount = items.count();
00377     const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
00378 #if 0
00379 #ifndef NDEBUG // debugIndex only defined in debug mode
00380     kDebug(7008) << items.count() << "in" << dir
00381              << "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
00382 #endif
00383 #endif
00384     q->beginInsertRows( index, newRowCount - newItemsCount, newRowCount - 1 ); // parent, first, last
00385 
00386     const KUrl::List urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
00387     //kDebug(7008) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
00388 
00389     QList<QModelIndex> emitExpandFor;
00390 
00391     KFileItemList::const_iterator it = items.begin();
00392     KFileItemList::const_iterator end = items.end();
00393     for ( ; it != end ; ++it ) {
00394         const bool isDir = it->isDir();
00395         KDirModelNode* node = isDir
00396                               ? new KDirModelDirNode( dirNode, *it )
00397                               : new KDirModelNode( dirNode, *it );
00398         dirNode->m_childNodes.append(node);
00399         const KUrl url = it->url();
00400         dirNode->m_childNodesByName.insert(url.fileName(), node);
00401         m_nodeHash.insert(cleanupUrl(url), node);
00402         //kDebug(7008) << url;
00403 
00404         if (!urlsBeingFetched.isEmpty()) {
00405             const KUrl dirUrl = url;
00406             foreach(const KUrl& urlFetched, urlsBeingFetched) {
00407                 if (dirUrl.isParentOf(urlFetched)) {
00408                     kDebug(7008) << "Listing found" << dirUrl << "which is a parent of fetched url" << urlFetched;
00409                     const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count()-1);
00410                     Q_ASSERT(parentIndex.isValid());
00411                     emitExpandFor.append(parentIndex);
00412                     if (isDir && dirUrl != urlFetched) {
00413                         q->fetchMore(parentIndex);
00414                         m_urlsBeingFetched[node].append(urlFetched);
00415                     }
00416                 }
00417             }
00418         }
00419     }
00420 
00421     m_urlsBeingFetched.remove(dirNode);
00422 
00423     q->endInsertRows();
00424 
00425     // Emit expand signal after rowsInserted signal has been emitted,
00426     // so that any proxy model will have updated its mapping already
00427     Q_FOREACH(const QModelIndex& idx, emitExpandFor) {
00428         emit q->expand(idx);
00429     }
00430 }
00431 
00432 void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList& items)
00433 {
00434     //kDebug(7008) << items.count();
00435 
00436     // I assume all items are from the same directory.
00437     // From KDirLister's code, this should be the case, except maybe emitChanges?
00438     const KFileItem item = items.first();
00439     Q_ASSERT(!item.isNull());
00440     KUrl url = item.url();
00441     KDirModelNode* node = nodeForUrl(url); // O(depth)
00442     if (!node) {
00443         kWarning(7008) << "No node found for item that was just removed:" << url;
00444         return;
00445     }
00446 
00447     KDirModelDirNode* dirNode = node->parent();
00448     if (!dirNode)
00449         return;
00450 
00451     QModelIndex parentIndex = indexForNode(dirNode); // O(n)
00452 
00453     // Short path for deleting a single item
00454     if (items.count() == 1) {
00455         const int r = node->rowNumber();
00456         q->beginRemoveRows(parentIndex, r, r);
00457         removeFromNodeHash(node, url);
00458         delete dirNode->m_childNodes.takeAt(r);
00459         q->endRemoveRows();
00460         Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
00461         dirNode->m_childNodesByName.remove(url.fileName());
00462         return;
00463     }
00464 
00465     // We need to make lists of consecutive row numbers, for the beginRemoveRows call.
00466     // Let's use a bit array where each bit represents a given child node.
00467     const int childCount = dirNode->m_childNodes.count();
00468     QBitArray rowNumbers(childCount, false);
00469     Q_FOREACH(const KFileItem& item, items) {
00470         if (!node) { // don't lookup the first item twice
00471             url = item.url();
00472             node = nodeForUrl(url);
00473             if (!node) {
00474                 kWarning(7008) << "No node found for item that was just removed:" << url;
00475             }
00476             Q_ASSERT(node);
00477         }
00478         rowNumbers.setBit(node->rowNumber(), 1); // O(n)
00479         Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
00480         dirNode->m_childNodesByName.remove(url.fileName());
00481         removeFromNodeHash(node, url);
00482         node = 0;
00483     }
00484 
00485     int start = -1;
00486     int end = -1;
00487     bool lastVal = false;
00488     // Start from the end, otherwise all the row numbers are offset while we go
00489     for (int i = childCount - 1; i >= 0; --i) {
00490         const bool val = rowNumbers.testBit(i);
00491         if (!lastVal && val) {
00492             end = i;
00493             //kDebug(7008) << "end=" << end;
00494         }
00495         if ((lastVal && !val) || (i == 0 && val)) {
00496             start = val ? i : i + 1;
00497             //kDebug(7008) << "beginRemoveRows" << start << end;
00498             q->beginRemoveRows(parentIndex, start, end);
00499             for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
00500                 //kDebug(7008) << "Removing from m_childNodes at" << r;
00501                 delete dirNode->m_childNodes.takeAt(r);
00502             }
00503             q->endRemoveRows();
00504         }
00505         lastVal = val;
00506     }
00507 }
00508 
00509 void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
00510 {
00511     QModelIndex topLeft, bottomRight;
00512 
00513     // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
00514     // Solution 2: more fine-grained, actually figure out the beginning and end rows.
00515     for ( QList<QPair<KFileItem, KFileItem> >::const_iterator fit = items.begin(), fend = items.end() ; fit != fend ; ++fit ) {
00516         Q_ASSERT(!fit->first.isNull());
00517         Q_ASSERT(!fit->second.isNull());
00518         const KUrl oldUrl = fit->first.url();
00519         const KUrl newUrl = fit->second.url();
00520         KDirModelNode* node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
00521         if (!node) // not found [can happen when renaming a dir, redirection was emitted already]
00522             continue;
00523         if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
00524             node->setItem(fit->second);
00525 
00526             if (oldUrl != newUrl) {
00527                 if (oldUrl.fileName() != newUrl.fileName()) {
00528                     Q_ASSERT(oldUrl.directory() == newUrl.directory()); // file renamed, not moved.
00529                     KDirModelDirNode* parentNode = node->parent();
00530                     Q_ASSERT(parentNode);
00531                     parentNode->m_childNodesByName.remove(oldUrl.fileName());
00532                     parentNode->m_childNodesByName.insert(newUrl.fileName(), node);
00533                 }
00534                 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item
00535                 //kDebug(7008) << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
00536                 m_nodeHash.remove(cleanupUrl(oldUrl));
00537                 m_nodeHash.insert(cleanupUrl(newUrl), node);
00538             }
00539             // Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
00540             if (fit->first.mimeTypePtr() != fit->second.mimeTypePtr())
00541                 node->setPreview(QIcon());
00542             const QModelIndex index = indexForNode(node);
00543             if (!topLeft.isValid() || index.row() < topLeft.row()) {
00544                 topLeft = index;
00545             }
00546             if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
00547                 bottomRight = index;
00548             }
00549         }
00550     }
00551 #ifndef NDEBUG // debugIndex only defined in debug mode
00552     kDebug(7008) << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
00553 #endif
00554     bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex())-1);
00555     emit q->dataChanged(topLeft, bottomRight);
00556 }
00557 
00558 // Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup)
00559 // and when renaming a directory.
00560 void KDirModelPrivate::_k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
00561 {
00562     KDirModelNode* node = nodeForUrl(oldUrl);
00563     if (!node)
00564         return;
00565     m_nodeHash.remove(cleanupUrl(oldUrl));
00566     m_nodeHash.insert(cleanupUrl(newUrl), node);
00567 
00568     // Ensure the node's URL is updated. In case of a listjob redirection
00569     // we won't get a refreshItem, and in case of renaming a directory
00570     // we'll get it too late (so the hash won't find the old url anymore).
00571     KFileItem item = node->item();
00572     if (!item.isNull()) { // null if root item, #180156
00573         item.setUrl(newUrl);
00574         node->setItem(item);
00575     }
00576 
00577     // The items inside the renamed directory have been handled before,
00578     // KDirLister took care of emitting refreshItem for each of them.
00579 }
00580 
00581 void KDirModelPrivate::_k_slotClear()
00582 {
00583     const int numRows = m_rootNode->m_childNodes.count();
00584     if (numRows > 0) {
00585         q->beginRemoveRows( QModelIndex(), 0, numRows - 1 );
00586         q->endRemoveRows();
00587     }
00588 
00589     m_nodeHash.clear();
00590     //emit layoutAboutToBeChanged();
00591     clear();
00592     //emit layoutChanged();
00593 }
00594 
00595 void KDirModel::itemChanged( const QModelIndex& index )
00596 {
00597     // This method is really a itemMimeTypeChanged(), it's mostly called by KMimeTypeResolver.
00598     // When the mimetype is determined, clear the old "preview" (could be
00599     // mimetype dependent like when cutting files, #164185)
00600     KDirModelNode* node = d->nodeForIndex(index);
00601     if (node)
00602         node->setPreview(QIcon());
00603 
00604 #ifndef NDEBUG // debugIndex only defined in debug mode
00605     //kDebug(7008) << "dataChanged(" << debugIndex(index);
00606 #endif
00607     emit dataChanged(index, index);
00608 }
00609 
00610 int KDirModel::columnCount( const QModelIndex & ) const
00611 {
00612     return ColumnCount;
00613 }
00614 
00615 QVariant KDirModel::data( const QModelIndex & index, int role ) const
00616 {
00617     if (index.isValid()) {
00618         KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00619         const KFileItem& item( node->item() );
00620         switch (role) {
00621         case Qt::DisplayRole:
00622             switch (index.column()) {
00623             case Name:
00624                 return item.text();
00625             case Size:
00626                 //
00627                 //return KIO::convertSize(item->size());
00628                 // Default to "file size in bytes" like in kde3's filedialog
00629                 return KGlobal::locale()->formatNumber(item.size(), 0);
00630             case ModifiedTime: {
00631                 KDateTime dt = item.time(KFileItem::ModificationTime);
00632                 return KGlobal::locale()->formatDateTime(dt);
00633             }
00634             case Permissions:
00635                 return item.permissionsString();
00636             case Owner:
00637                 return item.user();
00638             case Group:
00639                 return item.group();
00640             case Type:
00641                 return item.mimeComment();
00642             }
00643             break;
00644         case Qt::EditRole:
00645             switch (index.column()) {
00646             case Name:
00647                 return item.text();
00648             }
00649             break;
00650         case Qt::DecorationRole:
00651             if (index.column() == Name) {
00652                 if (!node->preview().isNull()) {
00653                     //kDebug(7008) << item->url() << " preview found";
00654                     return node->preview();
00655                 }
00656                 Q_ASSERT(!item.isNull());
00657                 //kDebug(7008) << item->url() << " overlays=" << item->overlays();
00658                 return KIcon(item.iconName(), 0, item.overlays());
00659             }
00660             break;
00661         case Qt::TextAlignmentRole:
00662             if (index.column() == Size) {
00663                 // use a right alignment for L2R and R2L languages
00664                 const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter;
00665                 return int(alignment);
00666             }
00667             break;
00668         case Qt::ToolTipRole:
00669             return item.text();
00670         case FileItemRole:
00671             return QVariant::fromValue(item);
00672         case ChildCountRole:
00673             if (!item.isDir())
00674                 return ChildCountUnknown;
00675             else {
00676                 KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(node);
00677                 int count = dirNode->childCount();
00678                 if (count == ChildCountUnknown && item.isReadable()) {
00679                     const QString path = item.localPath();
00680                     if (!path.isEmpty()) {
00681 #if 0 // slow
00682                         QDir dir(path);
00683                         count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
00684 #else
00685                         DIR* dir = ::opendir(QFile::encodeName(path));
00686                         if (dir) {
00687                             count = 0;
00688                             struct dirent *dirEntry = 0;
00689                             while ((dirEntry = ::readdir(dir))) {
00690                                 if (dirEntry->d_name[0] == '.') {
00691                                     if (dirEntry->d_name[1] == '\0') // skip "."
00692                                         continue;
00693                                     if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') // skip ".."
00694                                         continue;
00695                                 }
00696                                 ++count;
00697                             }
00698                             ::closedir(dir);
00699                         }
00700 #endif
00701                         //kDebug(7008) << "child count for " << path << ":" << count;
00702                         dirNode->setChildCount(count);
00703                     }
00704                 }
00705                 return count;
00706             }
00707         }
00708     }
00709     return QVariant();
00710 }
00711 
00712 void KDirModel::sort( int column, Qt::SortOrder order )
00713 {
00714     // Not implemented - we should probably use QSortFilterProxyModel instead.
00715     return QAbstractItemModel::sort(column, order);
00716 }
00717 
00718 bool KDirModel::setData( const QModelIndex & index, const QVariant & value, int role )
00719 {
00720     switch (role) {
00721     case Qt::EditRole:
00722         if (index.column() == Name && value.type() == QVariant::String) {
00723             Q_ASSERT(index.isValid());
00724             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00725             const KFileItem& item = node->item();
00726             const QString newName = value.toString();
00727             if (newName.isEmpty() || newName == item.text())
00728                 return true;
00729             KUrl newurl(item.url());
00730             newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + newName);
00731             KIO::Job * job = KIO::moveAs(item.url(), newurl, newurl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
00732             job->ui()->setAutoErrorHandlingEnabled(true);
00733             // TODO undo handling
00734             return true;
00735         }
00736         break;
00737     case Qt::DecorationRole:
00738         if (index.column() == Name) {
00739             Q_ASSERT(index.isValid());
00740             // Set new pixmap - e.g. preview
00741             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00742             //kDebug(7008) << "setting icon for " << node->item()->url();
00743             Q_ASSERT(node);
00744             if (value.type() == QVariant::Icon) {
00745                 const QIcon icon(qvariant_cast<QIcon>(value));
00746                 node->setPreview(icon);
00747             } else if (value.type() == QVariant::Pixmap) {
00748                 node->addPreview(qvariant_cast<QPixmap>(value));
00749             }
00750             emit dataChanged(index, index);
00751             return true;
00752         }
00753         break;
00754     default:
00755         break;
00756     }
00757     return false;
00758 }
00759 
00760 int KDirModel::rowCount( const QModelIndex & parent ) const
00761 {
00762     KDirModelNode* node = d->nodeForIndex(parent);
00763     if (!node || !d->isDir(node)) // #176555
00764         return 0;
00765 
00766     KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(node);
00767     Q_ASSERT(parentNode);
00768     const int count = parentNode->m_childNodes.count();
00769 #if 0
00770     QStringList filenames;
00771     for (int i = 0; i < count; ++i) {
00772         filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
00773     }
00774     kDebug(7008) << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
00775 #endif
00776     return count;
00777 }
00778 
00779 // sibling() calls parent() and isn't virtual! So parent() should be fast...
00780 QModelIndex KDirModel::parent( const QModelIndex & index ) const
00781 {
00782     if (!index.isValid())
00783         return QModelIndex();
00784     KDirModelNode* childNode = static_cast<KDirModelNode*>(index.internalPointer());
00785     Q_ASSERT(childNode);
00786     KDirModelNode* parentNode = childNode->parent();
00787     Q_ASSERT(parentNode);
00788     return d->indexForNode(parentNode); // O(n)
00789 }
00790 
00791 static bool lessThan(const KUrl &left, const KUrl &right)
00792 {
00793     return left.url().compare(right.url()) < 0;
00794 }
00795 
00796 KUrl::List KDirModel::simplifiedUrlList(const KUrl::List &urls)
00797 {
00798     if (!urls.count()) {
00799         return urls;
00800     }
00801 
00802     KUrl::List ret(urls);
00803     qSort(ret.begin(), ret.end(), lessThan);
00804 
00805     KUrl::List::iterator it = ret.begin();
00806     KUrl url = *it;
00807     ++it;
00808     while (it != ret.end()) {
00809         if (url.isParentOf(*it)) {
00810             it = ret.erase(it);
00811         } else {
00812             url = *it;
00813             ++it;
00814         }
00815     }
00816 
00817     return ret;
00818 }
00819 
00820 QStringList KDirModel::mimeTypes( ) const
00821 {
00822     return KUrl::List::mimeDataTypes();
00823 }
00824 
00825 QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
00826 {
00827     KUrl::List urls, mostLocalUrls;
00828     foreach (const QModelIndex &index, indexes) {
00829         const KFileItem& item = d->nodeForIndex(index)->item();
00830         urls << item.url();
00831         bool dummy;
00832         mostLocalUrls << item.mostLocalUrl(dummy);
00833     }
00834     QMimeData *data = new QMimeData();
00835     const bool different = mostLocalUrls != urls;
00836     urls = simplifiedUrlList(urls);
00837     if (different) {
00838         mostLocalUrls = simplifiedUrlList(mostLocalUrls);
00839         urls.populateMimeData(mostLocalUrls, data);
00840     } else {
00841         urls.populateMimeData(data);
00842     }
00843 
00844     // for compatibility reasons (when dropping or pasting into kde3 applications)
00845     QString application_x_qiconlist;
00846     const int items = urls.count();
00847     for (int i = 0; i < items; i++) {
00848     const int offset = i*16;
00849     QString tmp("%1$@@$%2$@@$32$@@$32$@@$%3$@@$%4$@@$32$@@$16$@@$no data$@@$");
00850     application_x_qiconlist += tmp.arg(offset).arg(offset).arg(offset).arg(offset+40);
00851     }
00852     data->setData("application/x-qiconlist", application_x_qiconlist.toLatin1());
00853 
00854     return data;
00855 }
00856 
00857 // Public API; not much point in calling it internally
00858 KFileItem KDirModel::itemForIndex( const QModelIndex& index ) const
00859 {
00860     if (!index.isValid()) {
00861         return d->m_dirLister->rootItem();
00862     } else {
00863         return static_cast<KDirModelNode*>(index.internalPointer())->item();
00864     }
00865 }
00866 
00867 QModelIndex KDirModel::indexForItem( const KFileItem* item ) const
00868 {
00869     // Note that we can only use the URL here, not the pointer.
00870     // KFileItems can be copied.
00871     return indexForUrl(item->url()); // O(n)
00872 }
00873 
00874 QModelIndex KDirModel::indexForItem( const KFileItem& item ) const
00875 {
00876     // Note that we can only use the URL here, not the pointer.
00877     // KFileItems can be copied.
00878     return indexForUrl(item.url()); // O(n)
00879 }
00880 
00881 // url -> index. O(n)
00882 QModelIndex KDirModel::indexForUrl(const KUrl& url) const
00883 {
00884     KDirModelNode* node = d->nodeForUrl(url); // O(depth)
00885     if (!node) {
00886         kDebug(7007) << url << "not found";
00887         return QModelIndex();
00888     }
00889     return d->indexForNode(node); // O(n)
00890 }
00891 
00892 QModelIndex KDirModel::index( int row, int column, const QModelIndex & parent ) const
00893 {
00894     KDirModelNode* parentNode = d->nodeForIndex(parent); // O(1)
00895     Q_ASSERT(parentNode);
00896     Q_ASSERT(d->isDir(parentNode));
00897     KDirModelNode* childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
00898     if (childNode)
00899         return createIndex(row, column, childNode);
00900     else
00901         return QModelIndex();
00902 }
00903 
00904 QVariant KDirModel::headerData( int section, Qt::Orientation orientation, int role ) const
00905 {
00906     if (orientation == Qt::Horizontal) {
00907         switch (role) {
00908         case Qt::DisplayRole:
00909             switch (section) {
00910             case Name:
00911                 return i18nc("@title:column","Name");
00912             case Size:
00913                 return i18nc("@title:column","Size");
00914             case ModifiedTime:
00915                 return i18nc("@title:column","Date");
00916             case Permissions:
00917                 return i18nc("@title:column","Permissions");
00918             case Owner:
00919                 return i18nc("@title:column","Owner");
00920             case Group:
00921                 return i18nc("@title:column","Group");
00922             case Type:
00923                 return i18nc("@title:column","Type");
00924             }
00925         }
00926     }
00927     return QVariant();
00928 }
00929 
00930 bool KDirModel::hasChildren( const QModelIndex & parent ) const
00931 {
00932     if (!parent.isValid())
00933         return true;
00934 
00935     const KFileItem& parentItem = static_cast<KDirModelNode*>(parent.internalPointer())->item();
00936     Q_ASSERT(!parentItem.isNull());
00937     return parentItem.isDir();
00938 }
00939 
00940 Qt::ItemFlags KDirModel::flags( const QModelIndex & index ) const
00941 {
00942     Qt::ItemFlags f = Qt::ItemIsEnabled;
00943     if (index.column() == Name) {
00944         f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
00945     }
00946 
00947     // Allow dropping onto this item?
00948     if (d->m_dropsAllowed != NoDrops) {
00949         if(!index.isValid()) {
00950             if (d->m_dropsAllowed & DropOnDirectory) {
00951                 f |= Qt::ItemIsDropEnabled;
00952             }
00953         } else {
00954             KFileItem item = itemForIndex(index);
00955             if (item.isNull()) {
00956                 kWarning(7007) << "Invalid item returned for index";
00957             } else if (item.isDir()) {
00958                 if (d->m_dropsAllowed & DropOnDirectory) {
00959                     f |= Qt::ItemIsDropEnabled;
00960                 }
00961             } else { // regular file item
00962                 if (d->m_dropsAllowed & DropOnAnyFile)
00963                     f |= Qt::ItemIsDropEnabled;
00964                 else if (d->m_dropsAllowed & DropOnLocalExecutable) {
00965                     if (!item.localPath().isEmpty()) {
00966                         // Desktop file?
00967                         if (item.mimeTypePtr()->is("application/x-desktop"))
00968                             f |= Qt::ItemIsDropEnabled;
00969                         // Executable, shell script ... ?
00970                         else if ( QFileInfo( item.localPath() ).isExecutable() )
00971                             f |= Qt::ItemIsDropEnabled;
00972                     }
00973                 }
00974             }
00975         }
00976     }
00977 
00978     return f;
00979 }
00980 
00981 bool KDirModel::canFetchMore( const QModelIndex & parent ) const
00982 {
00983     if (!parent.isValid())
00984         return false;
00985 
00986     // We now have a bool KDirModelNode::m_populated,
00987     // to avoid calling fetchMore more than once on empty dirs.
00988     // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
00989     // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
00990 
00991     KDirModelNode* node = static_cast<KDirModelNode*>(parent.internalPointer());
00992     const KFileItem& item = node->item();
00993     return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated()
00994         && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
00995 }
00996 
00997 void KDirModel::fetchMore( const QModelIndex & parent )
00998 {
00999     if (!parent.isValid())
01000         return;
01001 
01002     KDirModelNode* parentNode = static_cast<KDirModelNode*>(parent.internalPointer());
01003 
01004     KFileItem parentItem = parentNode->item();
01005     Q_ASSERT(!parentItem.isNull());
01006     Q_ASSERT(parentItem.isDir());
01007     KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(parentNode);
01008     if( dirNode->isPopulated() )
01009         return;
01010     dirNode->setPopulated( true );
01011 
01012     const KUrl parentUrl = parentItem.url();
01013     d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
01014 }
01015 
01016 bool KDirModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
01017 {
01018     // Not sure we want to implement any drop handling at this level,
01019     // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
01020     Q_UNUSED(data);
01021     Q_UNUSED(action);
01022     Q_UNUSED(row);
01023     Q_UNUSED(column);
01024     Q_UNUSED(parent);
01025     return false;
01026 }
01027 
01028 void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed)
01029 {
01030     d->m_dropsAllowed = dropsAllowed;
01031 }
01032 
01033 void KDirModel::expandToUrl(const KUrl& url)
01034 {
01035     // emit expand for each parent and return last parent
01036     KDirModelNode* result = d->expandAllParentsUntil(url); // O(depth)
01037     //kDebug(7008) << url << result;
01038 
01039     if (!result) // doesn't seem related to our base url?
01040         return;
01041     if (!(result->item().isNull()) && result->item().url() == url) {
01042         // We have it already, nothing to do
01043         kDebug(7008) << "have it already item=" <<url /*result->item()*/;
01044         return;
01045     }
01046 
01047     d->m_urlsBeingFetched[result].append(url);
01048 
01049     if (result == d->m_rootNode) {
01050         kDebug(7008) << "Remembering to emit expand after listing the root url";
01051         // the root is fetched by default, so it must be currently being fetched
01052         return;
01053     }
01054 
01055     kDebug(7008) << "Remembering to emit expand after listing" << result->item().url();
01056 
01057     // start a new fetch to look for the next level down the URL
01058     const QModelIndex parentIndex = d->indexForNode(result); // O(n)
01059     Q_ASSERT(parentIndex.isValid());
01060     fetchMore(parentIndex);
01061 }
01062 
01063 bool KDirModel::insertRows(int , int, const QModelIndex&)
01064 {
01065     return false;
01066 }
01067 
01068 bool KDirModel::insertColumns(int, int, const QModelIndex&)
01069 {
01070     return false;
01071 }
01072 
01073 bool KDirModel::removeRows(int, int, const QModelIndex&)
01074 {
01075     return false;
01076 }
01077 
01078 bool KDirModel::removeColumns(int, int, const QModelIndex&)
01079 {
01080     return false;
01081 }
01082 
01083 #include "kdirmodel.moc"

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal