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