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