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