• Skip to content
  • Skip to link menu
KDE 4.7 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             if (!node->parent()) {
00493                 // The root node has been deleted, but it was not first in the list 'items'.
00494                 // see https://bugs.kde.org/show_bug.cgi?id=196695
00495                 return;
00496             }
00497         }
00498         rowNumbers.setBit(node->rowNumber(), 1); // O(n)
00499         removeFromNodeHash(node, url);
00500         node = 0;
00501     }
00502 
00503     int start = -1;
00504     int end = -1;
00505     bool lastVal = false;
00506     // Start from the end, otherwise all the row numbers are offset while we go
00507     for (int i = childCount - 1; i >= 0; --i) {
00508         const bool val = rowNumbers.testBit(i);
00509         if (!lastVal && val) {
00510             end = i;
00511             //kDebug(7008) << "end=" << end;
00512         }
00513         if ((lastVal && !val) || (i == 0 && val)) {
00514             start = val ? i : i + 1;
00515             //kDebug(7008) << "beginRemoveRows" << start << end;
00516             q->beginRemoveRows(parentIndex, start, end);
00517             for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
00518                 //kDebug(7008) << "Removing from m_childNodes at" << r;
00519                 delete dirNode->m_childNodes.takeAt(r);
00520             }
00521             q->endRemoveRows();
00522         }
00523         lastVal = val;
00524     }
00525 }
00526 
00527 void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
00528 {
00529     QModelIndex topLeft, bottomRight;
00530 
00531     // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
00532     // Solution 2: more fine-grained, actually figure out the beginning and end rows.
00533     for ( QList<QPair<KFileItem, KFileItem> >::const_iterator fit = items.begin(), fend = items.end() ; fit != fend ; ++fit ) {
00534         Q_ASSERT(!fit->first.isNull());
00535         Q_ASSERT(!fit->second.isNull());
00536         const KUrl oldUrl = fit->first.url();
00537         const KUrl newUrl = fit->second.url();
00538         KDirModelNode* node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
00539         //kDebug(7008) << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
00540         if (!node) // not found [can happen when renaming a dir, redirection was emitted already]
00541             continue;
00542         if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
00543             bool hasNewNode = false;
00544             // A file became directory (well, it was overwritten)
00545             if (fit->first.isDir() != fit->second.isDir()) {
00546                 //kDebug(7008) << "DIR/FILE STATUS CHANGE";
00547                 const int r = node->rowNumber();
00548                 removeFromNodeHash(node, oldUrl);
00549                 KDirModelDirNode* dirNode = node->parent();
00550                 delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
00551                 node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second)
00552                        : new KDirModelNode(dirNode, fit->second);
00553                 dirNode->m_childNodes.insert(r, node); // same position!
00554                 hasNewNode = true;
00555             } else {
00556                 node->setItem(fit->second);
00557             }
00558 
00559             if (oldUrl != newUrl || hasNewNode) {
00560                 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item
00561                 //kDebug(7008) << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
00562                 m_nodeHash.remove(cleanupUrl(oldUrl));
00563                 m_nodeHash.insert(cleanupUrl(newUrl), node);
00564             }
00565             // Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
00566             if (fit->first.mimeTypePtr()->name() != fit->second.mimeTypePtr()->name()) {
00567                 node->setPreview(QIcon());
00568             }
00569 
00570             const QModelIndex index = indexForNode(node);
00571             if (!topLeft.isValid() || index.row() < topLeft.row()) {
00572                 topLeft = index;
00573             }
00574             if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
00575                 bottomRight = index;
00576             }
00577         }
00578     }
00579 #ifndef NDEBUG // debugIndex only defined in debug mode
00580     kDebug(7008) << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
00581 #endif
00582     bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex())-1);
00583     emit q->dataChanged(topLeft, bottomRight);
00584 }
00585 
00586 // Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup)
00587 // and when renaming a directory.
00588 void KDirModelPrivate::_k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
00589 {
00590     KDirModelNode* node = nodeForUrl(oldUrl);
00591     if (!node)
00592         return;
00593     m_nodeHash.remove(cleanupUrl(oldUrl));
00594     m_nodeHash.insert(cleanupUrl(newUrl), node);
00595 
00596     // Ensure the node's URL is updated. In case of a listjob redirection
00597     // we won't get a refreshItem, and in case of renaming a directory
00598     // we'll get it too late (so the hash won't find the old url anymore).
00599     KFileItem item = node->item();
00600     if (!item.isNull()) { // null if root item, #180156
00601         item.setUrl(newUrl);
00602         node->setItem(item);
00603     }
00604 
00605     // The items inside the renamed directory have been handled before,
00606     // KDirLister took care of emitting refreshItem for each of them.
00607 }
00608 
00609 void KDirModelPrivate::_k_slotClear()
00610 {
00611     const int numRows = m_rootNode->m_childNodes.count();
00612     if (numRows > 0) {
00613         q->beginRemoveRows( QModelIndex(), 0, numRows - 1 );
00614         q->endRemoveRows();
00615     }
00616 
00617     m_nodeHash.clear();
00618     //emit layoutAboutToBeChanged();
00619     clear();
00620     //emit layoutChanged();
00621 }
00622 
00623 void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList& urlList)
00624 {
00625     m_allCurrentDestUrls = urlList;
00626 }
00627 
00628 void KDirModel::itemChanged( const QModelIndex& index )
00629 {
00630     // This method is really a itemMimeTypeChanged(), it's mostly called by KMimeTypeResolver.
00631     // When the mimetype is determined, clear the old "preview" (could be
00632     // mimetype dependent like when cutting files, #164185)
00633     KDirModelNode* node = d->nodeForIndex(index);
00634     if (node)
00635         node->setPreview(QIcon());
00636 
00637 #ifndef NDEBUG // debugIndex only defined in debug mode
00638     //kDebug(7008) << "dataChanged(" << debugIndex(index);
00639 #endif
00640     emit dataChanged(index, index);
00641 }
00642 
00643 int KDirModel::columnCount( const QModelIndex & ) const
00644 {
00645     return ColumnCount;
00646 }
00647 
00648 QVariant KDirModel::data( const QModelIndex & index, int role ) const
00649 {
00650     if (index.isValid()) {
00651         KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00652         const KFileItem& item( node->item() );
00653         switch (role) {
00654         case Qt::DisplayRole:
00655             switch (index.column()) {
00656             case Name:
00657                 return item.text();
00658             case Size:
00659                 //
00660                 //return KIO::convertSize(item->size());
00661                 // Default to "file size in bytes" like in kde3's filedialog
00662                 return KGlobal::locale()->formatNumber(item.size(), 0);
00663             case ModifiedTime: {
00664                 KDateTime dt = item.time(KFileItem::ModificationTime);
00665                 return KGlobal::locale()->formatDateTime(dt);
00666             }
00667             case Permissions:
00668                 return item.permissionsString();
00669             case Owner:
00670                 return item.user();
00671             case Group:
00672                 return item.group();
00673             case Type:
00674                 return item.mimeComment();
00675             }
00676             break;
00677         case Qt::EditRole:
00678             switch (index.column()) {
00679             case Name:
00680                 return item.text();
00681             }
00682             break;
00683         case Qt::DecorationRole:
00684             if (index.column() == Name) {
00685                 if (!node->preview().isNull()) {
00686                     //kDebug(7008) << item->url() << " preview found";
00687                     return node->preview();
00688                 }
00689                 Q_ASSERT(!item.isNull());
00690                 //kDebug(7008) << item->url() << " overlays=" << item->overlays();
00691                 return KIcon(item.iconName(), 0, item.overlays());
00692             }
00693             break;
00694         case Qt::TextAlignmentRole:
00695             if (index.column() == Size) {
00696                 // use a right alignment for L2R and R2L languages
00697                 const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter;
00698                 return int(alignment);
00699             }
00700             break;
00701         case Qt::ToolTipRole:
00702             return item.text();
00703         case FileItemRole:
00704             return QVariant::fromValue(item);
00705         case ChildCountRole:
00706             if (!item.isDir())
00707                 return ChildCountUnknown;
00708             else {
00709                 KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(node);
00710                 int count = dirNode->childCount();
00711                 if (count == ChildCountUnknown && item.isReadable()) {
00712                     const QString path = item.localPath();
00713                     if (!path.isEmpty()) {
00714 //                        slow
00715 //                        QDir dir(path);
00716 //                        count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
00717 #ifdef Q_WS_WIN
00718                         QString s = path + QLatin1String( "\\*.*" );
00719                         s.replace('/', '\\');
00720                         count = 0;
00721                         WIN32_FIND_DATA findData;
00722                         HANDLE hFile = FindFirstFile( (LPWSTR)s.utf16(), &findData );
00723                         if( hFile != INVALID_HANDLE_VALUE ) {
00724                             do {
00725                                 if (!( findData.cFileName[0] == '.' &&
00726                                        findData.cFileName[1] == '\0' ) &&
00727                                     !( findData.cFileName[0] == '.' &&
00728                                        findData.cFileName[1] == '.' &&
00729                                        findData.cFileName[2] == '\0' ) )
00730                                     ++count;
00731                             } while( FindNextFile( hFile, &findData ) != 0 );
00732                             FindClose( hFile );
00733                         }
00734 #else
00735                         DIR* dir = ::opendir(QFile::encodeName(path));
00736                         if (dir) {
00737                             count = 0;
00738                             struct dirent *dirEntry = 0;
00739                             while ((dirEntry = ::readdir(dir))) {
00740                                 if (dirEntry->d_name[0] == '.') {
00741                                     if (dirEntry->d_name[1] == '\0') // skip "."
00742                                         continue;
00743                                     if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') // skip ".."
00744                                         continue;
00745                                 }
00746                                 ++count;
00747                             }
00748                             ::closedir(dir);
00749                         }
00750 #endif
00751                         //kDebug(7008) << "child count for " << path << ":" << count;
00752                         dirNode->setChildCount(count);
00753                     }
00754                 }
00755                 return count;
00756             }
00757         case HasJobRole:
00758             if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
00759                 KDirModelNode* node = d->nodeForIndex(index);
00760                 const QString url = node->item().url().url();
00761                 //return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
00762                 return QVariant(d->m_allCurrentDestUrls.contains(url));
00763             }
00764         }
00765     }
00766     return QVariant();
00767 }
00768 
00769 void KDirModel::sort( int column, Qt::SortOrder order )
00770 {
00771     // Not implemented - we should probably use QSortFilterProxyModel instead.
00772     return QAbstractItemModel::sort(column, order);
00773 }
00774 
00775 bool KDirModel::setData( const QModelIndex & index, const QVariant & value, int role )
00776 {
00777     switch (role) {
00778     case Qt::EditRole:
00779         if (index.column() == Name && value.type() == QVariant::String) {
00780             Q_ASSERT(index.isValid());
00781             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00782             const KFileItem& item = node->item();
00783             const QString newName = value.toString();
00784             if (newName.isEmpty() || newName == item.text() || (newName == QLatin1String(".")) || (newName == QLatin1String("..")))
00785                 return true;
00786             KUrl newurl(item.url());
00787             newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + KIO::encodeFileName(newName));
00788             KIO::Job * job = KIO::moveAs(item.url(), newurl, newurl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
00789             job->ui()->setAutoErrorHandlingEnabled(true);
00790             // undo handling
00791             KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Rename, item.url(), newurl, job );
00792             return true;
00793         }
00794         break;
00795     case Qt::DecorationRole:
00796         if (index.column() == Name) {
00797             Q_ASSERT(index.isValid());
00798             // Set new pixmap - e.g. preview
00799             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00800             //kDebug(7008) << "setting icon for " << node->item()->url();
00801             Q_ASSERT(node);
00802             if (value.type() == QVariant::Icon) {
00803                 const QIcon icon(qvariant_cast<QIcon>(value));
00804                 node->setPreview(icon);
00805             } else if (value.type() == QVariant::Pixmap) {
00806                 node->setPreview(qvariant_cast<QPixmap>(value));
00807             }
00808             emit dataChanged(index, index);
00809             return true;
00810         }
00811         break;
00812     default:
00813         break;
00814     }
00815     return false;
00816 }
00817 
00818 int KDirModel::rowCount( const QModelIndex & parent ) const
00819 {
00820     KDirModelNode* node = d->nodeForIndex(parent);
00821     if (!node || !d->isDir(node)) // #176555
00822         return 0;
00823 
00824     KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(node);
00825     Q_ASSERT(parentNode);
00826     const int count = parentNode->m_childNodes.count();
00827 #if 0
00828     QStringList filenames;
00829     for (int i = 0; i < count; ++i) {
00830         filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
00831     }
00832     kDebug(7008) << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
00833 #endif
00834     return count;
00835 }
00836 
00837 // sibling() calls parent() and isn't virtual! So parent() should be fast...
00838 QModelIndex KDirModel::parent( const QModelIndex & index ) const
00839 {
00840     if (!index.isValid())
00841         return QModelIndex();
00842     KDirModelNode* childNode = static_cast<KDirModelNode*>(index.internalPointer());
00843     Q_ASSERT(childNode);
00844     KDirModelNode* parentNode = childNode->parent();
00845     Q_ASSERT(parentNode);
00846     return d->indexForNode(parentNode); // O(n)
00847 }
00848 
00849 static bool lessThan(const KUrl &left, const KUrl &right)
00850 {
00851     return left.url().compare(right.url()) < 0;
00852 }
00853 
00854 void KDirModel::requestSequenceIcon(const QModelIndex& index, int sequenceIndex)
00855 {
00856     emit needSequenceIcon(index, sequenceIndex);
00857 }
00858 
00859 void KDirModel::setJobTransfersVisible(bool value)
00860 {
00861     if(value) {
00862         d->m_jobTransfersVisible = true;
00863         connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection);
00864 
00865         JobUrlCache::instance().requestJobUrlsChanged();
00866     } else {
00867         disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList)));
00868     }
00869 
00870 }
00871 
00872 bool KDirModel::jobTransfersVisible() const
00873 {
00874     return d->m_jobTransfersVisible;
00875 }
00876 
00877 KUrl::List KDirModel::simplifiedUrlList(const KUrl::List &urls)
00878 {
00879     if (!urls.count()) {
00880         return urls;
00881     }
00882 
00883     KUrl::List ret(urls);
00884     qSort(ret.begin(), ret.end(), lessThan);
00885 
00886     KUrl::List::iterator it = ret.begin();
00887     KUrl url = *it;
00888     ++it;
00889     while (it != ret.end()) {
00890         if (url.isParentOf(*it)) {
00891             it = ret.erase(it);
00892         } else {
00893             url = *it;
00894             ++it;
00895         }
00896     }
00897 
00898     return ret;
00899 }
00900 
00901 QStringList KDirModel::mimeTypes( ) const
00902 {
00903     return KUrl::List::mimeDataTypes();
00904 }
00905 
00906 QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
00907 {
00908     KUrl::List urls, mostLocalUrls;
00909     bool canUseMostLocalUrls = true;
00910     foreach (const QModelIndex &index, indexes) {
00911         const KFileItem& item = d->nodeForIndex(index)->item();
00912         urls << item.url();
00913         bool isLocal;
00914         mostLocalUrls << item.mostLocalUrl(isLocal);
00915         if (!isLocal)
00916             canUseMostLocalUrls = false;
00917     }
00918     QMimeData *data = new QMimeData();
00919     const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
00920     urls = simplifiedUrlList(urls);
00921     if (different) {
00922         mostLocalUrls = simplifiedUrlList(mostLocalUrls);
00923         urls.populateMimeData(mostLocalUrls, data);
00924     } else {
00925         urls.populateMimeData(data);
00926     }
00927 
00928     // for compatibility reasons (when dropping or pasting into kde3 applications)
00929     QString application_x_qiconlist;
00930     const int items = urls.count();
00931     for (int i = 0; i < items; i++) {
00932     const int offset = i*16;
00933     QString tmp("%1$@@$%2$@@$32$@@$32$@@$%3$@@$%4$@@$32$@@$16$@@$no data$@@$");
00934     application_x_qiconlist += tmp.arg(offset).arg(offset).arg(offset).arg(offset+40);
00935     }
00936     data->setData("application/x-qiconlist", application_x_qiconlist.toLatin1());
00937 
00938     return data;
00939 }
00940 
00941 // Public API; not much point in calling it internally
00942 KFileItem KDirModel::itemForIndex( const QModelIndex& index ) const
00943 {
00944     if (!index.isValid()) {
00945         return d->m_dirLister->rootItem();
00946     } else {
00947         return static_cast<KDirModelNode*>(index.internalPointer())->item();
00948     }
00949 }
00950 
00951 #ifndef KDE_NO_DEPRECATED
00952 QModelIndex KDirModel::indexForItem( const KFileItem* item ) const
00953 {
00954     // Note that we can only use the URL here, not the pointer.
00955     // KFileItems can be copied.
00956     return indexForUrl(item->url()); // O(n)
00957 }
00958 #endif
00959 
00960 QModelIndex KDirModel::indexForItem( const KFileItem& item ) const
00961 {
00962     // Note that we can only use the URL here, not the pointer.
00963     // KFileItems can be copied.
00964     return indexForUrl(item.url()); // O(n)
00965 }
00966 
00967 // url -> index. O(n)
00968 QModelIndex KDirModel::indexForUrl(const KUrl& url) const
00969 {
00970     KDirModelNode* node = d->nodeForUrl(url); // O(depth)
00971     if (!node) {
00972         kDebug(7007) << url << "not found";
00973         return QModelIndex();
00974     }
00975     return d->indexForNode(node); // O(n)
00976 }
00977 
00978 QModelIndex KDirModel::index( int row, int column, const QModelIndex & parent ) const
00979 {
00980     KDirModelNode* parentNode = d->nodeForIndex(parent); // O(1)
00981     Q_ASSERT(parentNode);
00982     Q_ASSERT(d->isDir(parentNode));
00983     KDirModelNode* childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
00984     if (childNode)
00985         return createIndex(row, column, childNode);
00986     else
00987         return QModelIndex();
00988 }
00989 
00990 QVariant KDirModel::headerData( int section, Qt::Orientation orientation, int role ) const
00991 {
00992     if (orientation == Qt::Horizontal) {
00993         switch (role) {
00994         case Qt::DisplayRole:
00995             switch (section) {
00996             case Name:
00997                 return i18nc("@title:column","Name");
00998             case Size:
00999                 return i18nc("@title:column","Size");
01000             case ModifiedTime:
01001                 return i18nc("@title:column","Date");
01002             case Permissions:
01003                 return i18nc("@title:column","Permissions");
01004             case Owner:
01005                 return i18nc("@title:column","Owner");
01006             case Group:
01007                 return i18nc("@title:column","Group");
01008             case Type:
01009                 return i18nc("@title:column","Type");
01010             }
01011         }
01012     }
01013     return QVariant();
01014 }
01015 
01016 bool KDirModel::hasChildren( const QModelIndex & parent ) const
01017 {
01018     if (!parent.isValid())
01019         return true;
01020 
01021     const KFileItem& parentItem = static_cast<KDirModelNode*>(parent.internalPointer())->item();
01022     Q_ASSERT(!parentItem.isNull());
01023     return parentItem.isDir();
01024 }
01025 
01026 Qt::ItemFlags KDirModel::flags( const QModelIndex & index ) const
01027 {
01028     Qt::ItemFlags f = Qt::ItemIsEnabled;
01029     if (index.column() == Name) {
01030         f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
01031     }
01032 
01033     // Allow dropping onto this item?
01034     if (d->m_dropsAllowed != NoDrops) {
01035         if(!index.isValid()) {
01036             if (d->m_dropsAllowed & DropOnDirectory) {
01037                 f |= Qt::ItemIsDropEnabled;
01038             }
01039         } else {
01040             KFileItem item = itemForIndex(index);
01041             if (item.isNull()) {
01042                 kWarning(7007) << "Invalid item returned for index";
01043             } else if (item.isDir()) {
01044                 if (d->m_dropsAllowed & DropOnDirectory) {
01045                     f |= Qt::ItemIsDropEnabled;
01046                 }
01047             } else { // regular file item
01048                 if (d->m_dropsAllowed & DropOnAnyFile)
01049                     f |= Qt::ItemIsDropEnabled;
01050                 else if (d->m_dropsAllowed & DropOnLocalExecutable) {
01051                     if (!item.localPath().isEmpty()) {
01052                         // Desktop file?
01053                         if (item.mimeTypePtr()->is("application/x-desktop"))
01054                             f |= Qt::ItemIsDropEnabled;
01055                         // Executable, shell script ... ?
01056                         else if ( QFileInfo( item.localPath() ).isExecutable() )
01057                             f |= Qt::ItemIsDropEnabled;
01058                     }
01059                 }
01060             }
01061         }
01062     }
01063 
01064     return f;
01065 }
01066 
01067 bool KDirModel::canFetchMore( const QModelIndex & parent ) const
01068 {
01069     if (!parent.isValid())
01070         return false;
01071 
01072     // We now have a bool KDirModelNode::m_populated,
01073     // to avoid calling fetchMore more than once on empty dirs.
01074     // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
01075     // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
01076 
01077     KDirModelNode* node = static_cast<KDirModelNode*>(parent.internalPointer());
01078     const KFileItem& item = node->item();
01079     return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated()
01080         && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
01081 }
01082 
01083 void KDirModel::fetchMore( const QModelIndex & parent )
01084 {
01085     if (!parent.isValid())
01086         return;
01087 
01088     KDirModelNode* parentNode = static_cast<KDirModelNode*>(parent.internalPointer());
01089 
01090     KFileItem parentItem = parentNode->item();
01091     Q_ASSERT(!parentItem.isNull());
01092     Q_ASSERT(parentItem.isDir());
01093     KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(parentNode);
01094     if( dirNode->isPopulated() )
01095         return;
01096     dirNode->setPopulated( true );
01097 
01098     const KUrl parentUrl = parentItem.url();
01099     d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
01100 }
01101 
01102 bool KDirModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
01103 {
01104     // Not sure we want to implement any drop handling at this level,
01105     // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
01106     Q_UNUSED(data);
01107     Q_UNUSED(action);
01108     Q_UNUSED(row);
01109     Q_UNUSED(column);
01110     Q_UNUSED(parent);
01111     return false;
01112 }
01113 
01114 void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed)
01115 {
01116     d->m_dropsAllowed = dropsAllowed;
01117 }
01118 
01119 void KDirModel::expandToUrl(const KUrl& url)
01120 {
01121     // emit expand for each parent and return last parent
01122     KDirModelNode* result = d->expandAllParentsUntil(url); // O(depth)
01123     //kDebug(7008) << url << result;
01124 
01125     if (!result) // doesn't seem related to our base url?
01126         return;
01127     if (!(result->item().isNull()) && result->item().url() == url) {
01128         // We have it already, nothing to do
01129         kDebug(7008) << "have it already item=" <<url /*result->item()*/;
01130         return;
01131     }
01132 
01133     d->m_urlsBeingFetched[result].append(url);
01134 
01135     if (result == d->m_rootNode) {
01136         kDebug(7008) << "Remembering to emit expand after listing the root url";
01137         // the root is fetched by default, so it must be currently being fetched
01138         return;
01139     }
01140 
01141     kDebug(7008) << "Remembering to emit expand after listing" << result->item().url();
01142 
01143     // start a new fetch to look for the next level down the URL
01144     const QModelIndex parentIndex = d->indexForNode(result); // O(n)
01145     Q_ASSERT(parentIndex.isValid());
01146     fetchMore(parentIndex);
01147 }
01148 
01149 bool KDirModel::insertRows(int , int, const QModelIndex&)
01150 {
01151     return false;
01152 }
01153 
01154 bool KDirModel::insertColumns(int, int, const QModelIndex&)
01155 {
01156     return false;
01157 }
01158 
01159 bool KDirModel::removeRows(int, int, const QModelIndex&)
01160 {
01161     return false;
01162 }
01163 
01164 bool KDirModel::removeColumns(int, int, const QModelIndex&)
01165 {
01166     return false;
01167 }
01168 
01169 #include "kdirmodel.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • 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.5
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