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"
KDE 4.7 API Reference