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