• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KIO

kdirmodel.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kdirmodel.h"
00021 #include "kdirlister.h"
00022 #include "kfileitem.h"
00023 #include <kdatetime.h>
00024 #include <kicon.h>
00025 #include <klocale.h>
00026 #include <kglobal.h>
00027 #include <kio/copyjob.h>
00028 #include <kio/jobuidelegate.h>
00029 #include <kurl.h>
00030 #include <kdebug.h>
00031 #include <QMimeData>
00032 #include <QFile>
00033 #include <QFileInfo>
00034 #include <QDir>
00035 #include <sys/types.h>
00036 #include <dirent.h>
00037 
00038 class KDirModelNode;
00039 class KDirModelDirNode;
00040 
00041 // We create our own tree behind the scenes to have fast lookup from an item to its parent,
00042 // and also to get the children of an item fast.
00043 class KDirModelNode
00044 {
00045 public:
00046     KDirModelNode( KDirModelDirNode* parent, const KFileItem& item ) :
00047         m_item(item),
00048         m_parent(parent),
00049         m_preview()
00050     {
00051     }
00052     // m_item is KFileItem() for the root item
00053     const KFileItem& item() const { return m_item; }
00054     void setItem(const KFileItem& item) { m_item = item; }
00055     KDirModelDirNode* parent() const { return m_parent; }
00056     // linear search
00057     int rowNumber() const; // O(n)
00058     QIcon preview() const { return m_preview; }
00059     void addPreview( const QPixmap& pix ) { m_preview.addPixmap(pix); }
00060     void setPreview( const QIcon& icn ) { m_preview = icn; }
00061 
00062 private:
00063     KFileItem m_item;
00064     KDirModelDirNode* const m_parent;
00065     QIcon m_preview;
00066 };
00067 
00068 // Specialization for directory nodes
00069 class KDirModelDirNode : public KDirModelNode
00070 {
00071 public:
00072     KDirModelDirNode( KDirModelDirNode* parent, const KFileItem& item)
00073         : KDirModelNode( parent, item),
00074           m_childNodes(),
00075           m_childCount(KDirModel::ChildCountUnknown),
00076           m_populated(false)
00077     {}
00078     ~KDirModelDirNode() {
00079         qDeleteAll(m_childNodes);
00080     }
00081     QList<KDirModelNode *> m_childNodes; // owns the nodes
00082     QHash<QString, KDirModelNode *> m_childNodesByName; // key = filename
00083 
00084     // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
00085     int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); }
00086     void setChildCount(int count) { m_childCount = count; }
00087     bool isPopulated() const { return m_populated; }
00088     void setPopulated( bool populated ) { m_populated = populated; }
00089 
00090 private:
00091     int m_childCount:31;
00092     bool m_populated:1;
00093 };
00094 
00095 int KDirModelNode::rowNumber() const
00096 {
00097     if (!m_parent) return 0;
00098     return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode*>(this));
00099 }
00100 
00102 
00103 class KDirModelPrivate
00104 {
00105 public:
00106     KDirModelPrivate( KDirModel* model )
00107         : q(model), m_dirLister(0),
00108           m_rootNode(new KDirModelDirNode(0, KFileItem())),
00109           m_dropsAllowed(KDirModel::NoDrops)
00110     {
00111     }
00112     ~KDirModelPrivate() {
00113         delete m_rootNode;
00114     }
00115 
00116     void _k_slotNewItems(const KFileItemList&);
00117     void _k_slotDeleteItems(const KFileItemList&);
00118     void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&);
00119     void _k_slotClear();
00120 
00121     void clear() {
00122         delete m_rootNode;
00123         m_rootNode = new KDirModelDirNode(0, KFileItem());
00124     }
00125     // Find the row number and node for a given url.
00126     // This has to drill down from the root node.
00127     // Returns (0,0) if there is no node for this url.
00128     // If returnLastParent is set, then return the last known parent if there is no node for this url
00129     // (special case for expandToUrl)
00130     KDirModelNode* nodeForUrl(const KUrl& url, bool returnLastParent = false) const;
00131     KDirModelNode* nodeForIndex(const QModelIndex& index) const;
00132     QModelIndex indexForNode(KDirModelNode* node, int rowNumber = -1 /*unknown*/) const;
00133     bool isDir(KDirModelNode* node) const {
00134         return (node == m_rootNode) || node->item().isDir();
00135     }
00136     KUrl urlForNode(KDirModelNode* node) const {
00144         KUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url());
00145         if (url.hasQuery() || url.hasRef()) { // avoid detach if not necessary.
00146             url.setQuery(QString());
00147             url.setRef(QString()); // kill ref (#171117)
00148         }
00149         return url;
00150     }
00151 
00152     KDirModel* q;
00153     KDirLister* m_dirLister;
00154     KDirModelDirNode* m_rootNode;
00155     KDirModel::DropsAllowed m_dropsAllowed;
00156     // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
00157     // value = final url[s] being fetched
00158     QMap<KDirModelNode*, KUrl::List> m_urlsBeingFetched;
00159 };
00160 
00161 // If we want to support arbitrary trees like "home:/ as a child of system:/" then,
00162 // we need to get the parent KFileItem in _k_slotNewItems, and then we can use a QHash<KFileItem,KDirModelNode*> cache.
00163 // (well there isn't a parent kfileitem, rather a parent url... hmm, back to square one with hashes-of-urls..)
00164 // For now we'll assume "child url = parent url + filename"
00165 KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url, bool returnLastParent) const // O(depth)
00166 {
00167     KUrl url(_url);
00168     url.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
00169     url.setQuery(QString());
00170     url.setRef(QString());
00171 
00172     //kDebug(7008) << url;
00173     KUrl nodeUrl = urlForNode(m_rootNode);
00174     // For a URL without a path, like "applications:" or "settings://",
00175     // we want to resolve here "no path" to "/ assumed".
00176     // We don't do it before (e.g. in KDirLister) because we want to
00177     // give the ioslave a chance for a redirect (e.g. kio_ftp redirects "no path"
00178     // to the user's home dir)
00179     if (nodeUrl.path().isEmpty())
00180         nodeUrl.setPath("/");
00181 
00182     if (url == nodeUrl)
00183         return m_rootNode;
00184 
00185     // Protocol mismatch? Don't even start comparing paths then. #171721
00186     if (url.protocol() != nodeUrl.protocol())
00187         return 0;
00188 
00189     const QString pathStr = url.path(); // no trailing slash
00190     KDirModelDirNode* dirNode = m_rootNode;
00191 
00192     if (!pathStr.startsWith(nodeUrl.path())) {
00193         return 0;
00194     }
00195 
00196     for (;;) {
00197         const QString nodePath = nodeUrl.path(KUrl::AddTrailingSlash);
00198         if(!pathStr.startsWith(nodePath)) {
00199             kError(7008) << "The kioslave for" << url.protocol() << "violates the hierarchy structure:"
00200                          << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
00201             return 0;
00202         }
00203 
00204         // E.g. pathStr is /a/b/c and nodePath is /a. We want to find the child "b" in dirNode.
00205         const QString relativePath = pathStr.mid(nodePath.length());
00206         Q_ASSERT(!relativePath.startsWith('/')); // huh? we need double-slash simplification?
00207         const int nextSlash = relativePath.indexOf('/');
00208         const QString fileName = relativePath.left(nextSlash); // works even if nextSlash==-1
00209         KDirModelNode* node = dirNode->m_childNodesByName.value(fileName);
00210         if (!node) {
00211             //kDebug(7008) << "child equal or starting with" << url << "not found";
00212             if (returnLastParent)
00213                 return dirNode;
00214             else
00215                 return 0;
00216         }
00217         nodeUrl = urlForNode(node);
00218         nodeUrl.adjustPath(KUrl::RemoveTrailingSlash); // #172508
00219         //kDebug(7008) << " nodeUrl=" << nodeUrl;
00220         if (nodeUrl == url) {
00221             //kDebug(7008) << "Found node" << node << "for" << url;
00222             return node;
00223         }
00224         //kDebug(7008) << "going into" << node->item().url();
00225         Q_ASSERT(isDir(node));
00226         dirNode = static_cast<KDirModelDirNode *>(node);
00227     }
00228     // NOTREACHED
00229     //return 0;
00230 }
00231 
00232 // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
00233 QModelIndex KDirModelPrivate::indexForNode(KDirModelNode* node, int rowNumber) const
00234 {
00235     if (node == m_rootNode)
00236         return QModelIndex();
00237 
00238     Q_ASSERT(node->parent());
00239     return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
00240 }
00241 
00242 // index -> node. O(1)
00243 KDirModelNode* KDirModelPrivate::nodeForIndex(const QModelIndex& index) const
00244 {
00245     return index.isValid()
00246         ? static_cast<KDirModelNode*>(index.internalPointer())
00247         : m_rootNode;
00248 }
00249 
00250 // We don't use QHash<KUrl,...> anymore, it's too slow.
00251 // Idea from George, to make QHash<KUrl,...> fast: - cache hash value into QUrl or KUrl
00252 // This also helps making operator== fast [which means operator== has to call qHash if cached value isn't there]
00253 // But it means invalidating the cached hash value when the url is modified,
00254 // so it can't be done in the current KUrl due to the public inheritance from QUrl.
00255 
00256 
00257 /*
00258  * This model wraps the data held by KDirLister.
00259  *
00260  * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
00261  * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
00262  *
00263  * Invalid parent index means root of the tree, m_rootNode
00264  */
00265 
00266 #ifndef NDEBUG
00267 static QString debugIndex(const QModelIndex& index)
00268 {
00269     QString str;
00270     if (!index.isValid())
00271         str = "[invalid index, i.e. root]";
00272     else {
00273         KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00274         str = "[index for " + node->item().url().pathOrUrl();
00275         if (index.column() > 0)
00276             str += ", column " + QString::number(index.column());
00277         str += ']';
00278     }
00279     return str;
00280 }
00281 #endif
00282 
00283 KDirModel::KDirModel(QObject* parent)
00284     : QAbstractItemModel(parent),
00285       d(new KDirModelPrivate(this))
00286 {
00287     setDirLister(new KDirLister(this));
00288 }
00289 
00290 KDirModel::~KDirModel()
00291 {
00292     delete d;
00293 }
00294 
00295 void KDirModel::setDirLister(KDirLister* dirLister)
00296 {
00297     if (d->m_dirLister) {
00298         d->clear();
00299         delete d->m_dirLister;
00300     }
00301     d->m_dirLister = dirLister;
00302     d->m_dirLister->setParent(this);
00303     connect( d->m_dirLister, SIGNAL(newItems(KFileItemList)),
00304              this, SLOT(_k_slotNewItems(KFileItemList)) );
00305     connect( d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)),
00306              this, SLOT(_k_slotDeleteItems(KFileItemList)) );
00307     connect( d->m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem, KFileItem> >)),
00308              this, SLOT(_k_slotRefreshItems(QList<QPair<KFileItem, KFileItem> >)) );
00309     connect( d->m_dirLister, SIGNAL(clear()),
00310              this, SLOT(_k_slotClear()) );
00311 }
00312 
00313 KDirLister* KDirModel::dirLister() const
00314 {
00315     return d->m_dirLister;
00316 }
00317 
00318 void KDirModelPrivate::_k_slotNewItems(const KFileItemList& items)
00319 {
00320     // Find parent item - it's the same for all the items
00321     // TODO: add parent url to the newItems signal
00322     //
00323     // This way we can finally support properly trees where the urls are using different protocols.
00324     // Well, it's not that simple - nodeForUrl still needs to know where to drill down...
00325 
00326     KUrl firstItemUrl = items.first().url();
00327     firstItemUrl.setQuery(QString());
00328     firstItemUrl.setRef(QString());
00329     KUrl dir(firstItemUrl);
00330     dir.setPath(dir.directory());
00331 
00332     //kDebug(7008) << "dir=" << dir;
00333 
00334     KDirModelNode* result = nodeForUrl(dir); // O(depth)
00335     // If the directory containing the items wasn't found, then we have a big problem.
00336     // Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead.
00337     if (!result) {
00338         kError(7008) << "First item has URL" << firstItemUrl
00339                      << "-> parent directory would be" << dir
00340                      << "but that directory isn't in KDirModel!"
00341                      << "Root directory:" << urlForNode(m_rootNode);
00342         Q_ASSERT(result);
00343     }
00344     Q_ASSERT(isDir(result));
00345     KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(result);
00346 
00347     const QModelIndex index = indexForNode(dirNode); // O(n)
00348     const int newItemsCount = items.count();
00349     const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
00350 #if 0
00351 #ifndef NDEBUG // debugIndex only defined in debug mode
00352     kDebug(7008) << items.count() << "in" << dir
00353              << "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
00354 #endif
00355 #endif
00356     q->beginInsertRows( index, newRowCount - newItemsCount, newRowCount - 1 ); // parent, first, last
00357 
00358     const KUrl::List urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
00359     //kDebug(7008) << "urlsBeingFetched for dir" << dirNode << dir << ":" << urlsBeingFetched;
00360 
00361     QList<QModelIndex> emitExpandFor;
00362 
00363     KFileItemList::const_iterator it = items.begin();
00364     KFileItemList::const_iterator end = items.end();
00365     for ( ; it != end ; ++it ) {
00366         const bool isDir = it->isDir();
00367         KDirModelNode* node = isDir
00368                               ? new KDirModelDirNode( dirNode, *it )
00369                               : new KDirModelNode( dirNode, *it );
00370         dirNode->m_childNodes.append(node);
00371         const KUrl url = it->url();
00372         dirNode->m_childNodesByName.insert(url.fileName(), node);
00373         //kDebug(7008) << url;
00374 
00375         if (isDir && !urlsBeingFetched.isEmpty()) {
00376             const KUrl dirUrl = url;
00377             foreach(const KUrl& urlFetched, urlsBeingFetched) {
00378                 if (dirUrl.isParentOf(urlFetched)) {
00379                     //kDebug(7008) << "Listing found" << dirUrl << "which is a parent of fetched url" << urlFetched;
00380                     const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count()-1);
00381                     Q_ASSERT(parentIndex.isValid());
00382                     emitExpandFor.append(parentIndex);
00383                     if (dirUrl != urlFetched) {
00384                         q->fetchMore(parentIndex);
00385                         m_urlsBeingFetched[node].append(urlFetched);
00386                     }
00387                 }
00388             }
00389         }
00390     }
00391 
00392     m_urlsBeingFetched.remove(dirNode);
00393 
00394     q->endInsertRows();
00395 
00396     // Emit expand signal after rowsInserted signal has been emitted,
00397     // so that any proxy model will have updated its mapping already
00398     Q_FOREACH(const QModelIndex& idx, emitExpandFor) {
00399         emit q->expand(idx);
00400     }
00401 }
00402 
00403 void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList& items)
00404 {
00405     //kDebug(7008) << items.count();
00406 
00407     // I assume all items are from the same directory.
00408     // From KDirLister's code, this should be the case, except maybe emitChanges?
00409     const KFileItem item = items.first();
00410     Q_ASSERT(!item.isNull());
00411     KUrl url = item.url();
00412     KDirModelNode* node = nodeForUrl(url); // O(depth)
00413     if (!node)
00414         return;
00415 
00416     KDirModelDirNode* dirNode = node->parent();
00417     if (!dirNode)
00418         return;
00419 
00420     QModelIndex parentIndex = indexForNode(dirNode); // O(n)
00421 
00422     // Short path for deleting a single item
00423     if (items.count() == 1) {
00424         const int r = node->rowNumber();
00425         q->beginRemoveRows(parentIndex, r, r);
00426         delete dirNode->m_childNodes.takeAt(r);
00427         q->endRemoveRows();
00428         Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
00429         dirNode->m_childNodesByName.remove(url.fileName());
00430         return;
00431     }
00432 
00433     // We need to make lists of consecutive row numbers, for the beginRemoveRows call.
00434     // Let's use a bit array where each bit represents a given child node.
00435     const int childCount = dirNode->m_childNodes.count();
00436     QBitArray rowNumbers(childCount, false);
00437     Q_FOREACH(const KFileItem& item, items) {
00438         if (!node) { // don't lookup the first item twice
00439             url = item.url();
00440             node = nodeForUrl(url);
00441             Q_ASSERT(node);
00442         }
00443         rowNumbers.setBit(node->rowNumber(), 1); // O(n)
00444         Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
00445         dirNode->m_childNodesByName.remove(url.fileName());
00446         node = 0;
00447     }
00448 
00449     int start = -1;
00450     int end = -1;
00451     bool lastVal = false;
00452     // Start from the end, otherwise all the row numbers are offset while we go
00453     for (int i = childCount - 1; i >= 0; --i) {
00454         const bool val = rowNumbers.testBit(i);
00455         if (!lastVal && val) {
00456             end = i;
00457             //kDebug(7008) << "end=" << end;
00458         }
00459         if ((lastVal && !val) || (i == 0 && val)) {
00460             start = val ? i : i + 1;
00461             //kDebug(7008) << "beginRemoveRows" << start << end;
00462             q->beginRemoveRows(parentIndex, start, end);
00463             for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
00464                 //kDebug(7008) << "Removing from m_childNodes at" << r;
00465                 delete dirNode->m_childNodes.takeAt(r);
00466             }
00467             q->endRemoveRows();
00468         }
00469         lastVal = val;
00470     }
00471 }
00472 
00473 void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
00474 {
00475     QModelIndex topLeft, bottomRight;
00476 
00477     // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
00478     // Solution 2: more fine-grained, actually figure out the beginning and end rows.
00479     for ( QList<QPair<KFileItem, KFileItem> >::const_iterator fit = items.begin(), fend = items.end() ; fit != fend ; ++fit ) {
00480         Q_ASSERT(!fit->first.isNull());
00481         Q_ASSERT(!fit->second.isNull());
00482         const KUrl oldUrl = fit->first.url();
00483         const KUrl newUrl = fit->second.url();
00484         const QModelIndex index = q->indexForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
00485         KDirModelNode* node = nodeForIndex(index);
00486         if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
00487             node->setItem(fit->second);
00488 
00489             if (oldUrl.fileName() != newUrl.fileName()) {
00490                 KDirModelDirNode* parentNode = node->parent();
00491                 Q_ASSERT(parentNode);
00492                 parentNode->m_childNodesByName.remove(oldUrl.fileName());
00493                 parentNode->m_childNodesByName.insert(newUrl.fileName(), node);
00494             }
00495             if (!topLeft.isValid() || index.row() < topLeft.row()) {
00496                 topLeft = index;
00497             }
00498             if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
00499                 bottomRight = index;
00500             }
00501         }
00502     }
00503 #ifndef NDEBUG // debugIndex only defined in debug mode
00504     kDebug(7008) << "slotRefreshItems: dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
00505 #endif
00506     bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex())-1);
00507     emit q->dataChanged(topLeft, bottomRight);
00508 }
00509 
00510 void KDirModelPrivate::_k_slotClear()
00511 {
00512     const int numRows = m_rootNode->m_childNodes.count();
00513     q->beginRemoveRows( QModelIndex(), 0, numRows );
00514     q->endRemoveRows();
00515 
00516     //emit layoutAboutToBeChanged();
00517     clear();
00518     //emit layoutChanged();
00519 }
00520 
00521 void KDirModel::itemChanged( const QModelIndex& index )
00522 {
00523     emit dataChanged(index, index);
00524 }
00525 
00526 int KDirModel::columnCount( const QModelIndex & ) const
00527 {
00528     return ColumnCount;
00529 }
00530 
00531 QVariant KDirModel::data( const QModelIndex & index, int role ) const
00532 {
00533     if (index.isValid()) {
00534         KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00535         const KFileItem& item( node->item() );
00536         switch (role) {
00537         case Qt::DisplayRole:
00538             switch (index.column()) {
00539             case Name:
00540                 return item.text();
00541             case Size:
00542                 //
00543                 //return KIO::convertSize(item->size());
00544                 // Default to "file size in bytes" like in kde3's filedialog
00545                 return KGlobal::locale()->formatNumber(item.size(), 0);
00546             case ModifiedTime: {
00547                 KDateTime dt = item.time(KFileItem::ModificationTime);
00548                 return KGlobal::locale()->formatDateTime(dt);
00549             }
00550             case Permissions:
00551                 return item.permissionsString();
00552             case Owner:
00553                 return item.user();
00554             case Group:
00555                 return item.group();
00556             case Type:
00557                 return item.mimeComment();
00558             }
00559             break;
00560         case Qt::EditRole:
00561             switch (index.column()) {
00562             case Name:
00563                 return item.text();
00564             }
00565             break;
00566         case Qt::DecorationRole:
00567             if (index.column() == Name) {
00568                 if (!node->preview().isNull()) {
00569                     //kDebug(7008) << item->url() << " preview found";
00570                     return node->preview();
00571                 }
00572                 Q_ASSERT(!item.isNull());
00573                 //kDebug(7008) << item->url() << " overlays=" << item->overlays();
00574                 return KIcon(item.iconName(), 0, item.overlays());
00575             }
00576             break;
00577         case Qt::TextAlignmentRole:
00578             if (index.column() == Size) {
00579                 // use a right alignment for L2R and R2L languages
00580                 const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter;
00581                 return int(alignment);
00582             }
00583             break;
00584         case FileItemRole:
00585             return QVariant::fromValue(item);
00586         case ChildCountRole:
00587             if (!item.isDir())
00588                 return ChildCountUnknown;
00589             else {
00590                 KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(node);
00591                 int count = dirNode->childCount();
00592                 if (count == ChildCountUnknown && item.isReadable()) {
00593                     const QString path = item.localPath();
00594                     if (!path.isEmpty()) {
00595 #if 0 // slow
00596                         QDir dir(path);
00597                         count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
00598 #else
00599                         DIR* dir = ::opendir(QFile::encodeName(path));
00600                         if (dir) {
00601                             count = 0;
00602                             struct dirent *dirEntry = 0;
00603                             while ((dirEntry = ::readdir(dir))) {
00604                                 if (dirEntry->d_name[0] == '.') {
00605                                     if (dirEntry->d_name[1] == '\0') // skip "."
00606                                         continue;
00607                                     if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') // skip ".."
00608                                         continue;
00609                                 }
00610                                 ++count;
00611                             }
00612                             ::closedir(dir);
00613                         }
00614 #endif
00615                         //kDebug(7008) << "child count for " << path << ":" << count;
00616                         dirNode->setChildCount(count);
00617                     }
00618                 }
00619                 return count;
00620             }
00621         }
00622     }
00623     return QVariant();
00624 }
00625 
00626 void KDirModel::sort( int column, Qt::SortOrder order )
00627 {
00628     // Not implemented - we should probably use QSortFilterProxyModel instead.
00629     return QAbstractItemModel::sort(column, order);
00630 }
00631 
00632 bool KDirModel::setData( const QModelIndex & index, const QVariant & value, int role )
00633 {
00634     switch (role) {
00635     case Qt::EditRole:
00636         if (index.column() == Name && value.type() == QVariant::String) {
00637             Q_ASSERT(index.isValid());
00638             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00639             const KFileItem& item = node->item();
00640             const QString newName = value.toString();
00641             if (newName.isEmpty() || newName == item.text())
00642                 return true;
00643             KUrl newurl(item.url());
00644             newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + newName);
00645             KIO::Job * job = KIO::moveAs(item.url(), newurl, newurl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
00646             job->ui()->setAutoErrorHandlingEnabled(true);
00647             // TODO undo handling
00648             return true;
00649         }
00650         break;
00651     case Qt::DecorationRole:
00652         if (index.column() == Name) {
00653             Q_ASSERT(index.isValid());
00654             // Set new pixmap - e.g. preview
00655             KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
00656             //kDebug(7008) << "setting icon for " << node->item()->url();
00657             Q_ASSERT(node);
00658             if (value.type() == QVariant::Icon) {
00659                 const QIcon icon(qvariant_cast<QIcon>(value));
00660                 Q_ASSERT(!icon.isNull());
00661                 node->setPreview(icon);
00662             } else if (value.type() == QVariant::Pixmap) {
00663                 node->addPreview(qvariant_cast<QPixmap>(value));
00664             }
00665             emit dataChanged(index, index);
00666             return true;
00667         }
00668         break;
00669     default:
00670         break;
00671     }
00672     return false;
00673 }
00674 
00675 int KDirModel::rowCount( const QModelIndex & parent ) const
00676 {
00677     KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(d->nodeForIndex(parent));
00678     Q_ASSERT(parentNode);
00679     const int count = parentNode->m_childNodes.count();
00680 #if 0
00681     QStringList filenames;
00682     for (int i = 0; i < count; ++i) {
00683         filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
00684     }
00685     kDebug(7008) << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
00686 #endif
00687     return count;
00688 }
00689 
00690 // sibling() calls parent() and isn't virtual! So parent() should be fast...
00691 QModelIndex KDirModel::parent( const QModelIndex & index ) const
00692 {
00693     if (!index.isValid())
00694         return QModelIndex();
00695     KDirModelNode* childNode = static_cast<KDirModelNode*>(index.internalPointer());
00696     Q_ASSERT(childNode);
00697     KDirModelNode* parentNode = childNode->parent();
00698     Q_ASSERT(parentNode);
00699     return d->indexForNode(parentNode); // O(n)
00700 }
00701 
00702 QStringList KDirModel::mimeTypes( ) const
00703 {
00704     return QStringList() << QLatin1String("text/uri-list")
00705                          << QLatin1String( "application/x-kde-cutselection" ) // TODO
00706                          << QLatin1String( "text/plain" )
00707                          << QLatin1String( "application/x-kde-urilist" );
00708 }
00709 
00710 QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
00711 {
00712     KUrl::List urls;
00713     foreach ( const QModelIndex &index, indexes ) {
00714         urls << d->nodeForIndex( index )->item().url();
00715     }
00716     QMimeData *data = new QMimeData();
00717     urls.populateMimeData( data );
00718     return data;
00719 }
00720 
00721 // Public API; not much point in calling it internally
00722 KFileItem KDirModel::itemForIndex( const QModelIndex& index ) const
00723 {
00724     if (!index.isValid()) {
00725         return d->m_dirLister->rootItem();
00726     } else {
00727         return static_cast<KDirModelNode*>(index.internalPointer())->item();
00728     }
00729 }
00730 
00731 QModelIndex KDirModel::indexForItem( const KFileItem* item ) const
00732 {
00733     // Note that we can only use the URL here, not the pointer.
00734     // KFileItems can be copied.
00735     return indexForUrl(item->url()); // O(n)
00736 }
00737 
00738 QModelIndex KDirModel::indexForItem( const KFileItem& item ) const
00739 {
00740     // Note that we can only use the URL here, not the pointer.
00741     // KFileItems can be copied.
00742     return indexForUrl(item.url()); // O(n)
00743 }
00744 
00745 // url -> index. O(n)
00746 QModelIndex KDirModel::indexForUrl(const KUrl& url) const
00747 {
00748     KDirModelNode* node = d->nodeForUrl(url); // O(depth)
00749     if (!node) {
00750         kDebug(7007) << url << "not found";
00751         return QModelIndex();
00752     }
00753     return d->indexForNode(node); // O(n)
00754 }
00755 
00756 QModelIndex KDirModel::index( int row, int column, const QModelIndex & parent ) const
00757 {
00758     KDirModelNode* parentNode = d->nodeForIndex(parent); // O(1)
00759     Q_ASSERT(parentNode);
00760     Q_ASSERT(d->isDir(parentNode));
00761     KDirModelNode* childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
00762     if (childNode)
00763         return createIndex(row, column, childNode);
00764     else
00765         return QModelIndex();
00766 }
00767 
00768 QVariant KDirModel::headerData( int section, Qt::Orientation orientation, int role ) const
00769 {
00770     Q_UNUSED(orientation);
00771     switch (role) {
00772     case Qt::DisplayRole:
00773         switch (section) {
00774         case Name:
00775             return i18nc("@title:column","Name");
00776         case Size:
00777             return i18nc("@title:column","Size");
00778         case ModifiedTime:
00779             return i18nc("@title:column","Date");
00780         case Permissions:
00781             return i18nc("@title:column","Permissions");
00782         case Owner:
00783             return i18nc("@title:column","Owner");
00784         case Group:
00785             return i18nc("@title:column","Group");
00786         case Type:
00787             return i18nc("@title:column","Type");
00788         }
00789     }
00790     return QVariant();
00791 }
00792 
00793 bool KDirModel::hasChildren( const QModelIndex & parent ) const
00794 {
00795     if (!parent.isValid())
00796         return true;
00797 
00798     const KFileItem& parentItem = static_cast<KDirModelNode*>(parent.internalPointer())->item();
00799     Q_ASSERT(!parentItem.isNull());
00800     return parentItem.isDir();
00801 }
00802 
00803 Qt::ItemFlags KDirModel::flags( const QModelIndex & index ) const
00804 {
00805     Qt::ItemFlags f = Qt::ItemIsEnabled;
00806     if (index.column() == Name) {
00807         f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
00808     }
00809 
00810     // Allow dropping onto this item?
00811     if (d->m_dropsAllowed != NoDrops) {
00812         if(!index.isValid()) {
00813             if (d->m_dropsAllowed & DropOnDirectory) {
00814                 f |= Qt::ItemIsDropEnabled;
00815             }
00816         } else {
00817             KFileItem item = itemForIndex(index);
00818             if (item.isNull()) {
00819                 kWarning(7007) << "Invalid item returned for index";
00820             } else if (item.isDir()) {
00821                 if (d->m_dropsAllowed & DropOnDirectory) {
00822                     f |= Qt::ItemIsDropEnabled;
00823                 }
00824             } else { // regular file item
00825                 if (d->m_dropsAllowed & DropOnAnyFile)
00826                     f |= Qt::ItemIsDropEnabled;
00827                 else if (d->m_dropsAllowed & DropOnLocalExecutable) {
00828                     if (item.isLocalFile()) {
00829                         // Desktop file?
00830                         if (item.mimeTypePtr()->is("application/x-desktop"))
00831                             f |= Qt::ItemIsDropEnabled;
00832                         // Executable, shell script ... ?
00833                         else if ( QFileInfo( item.localPath() ).isExecutable() )
00834                             f |= Qt::ItemIsDropEnabled;
00835                     }
00836                 }
00837             }
00838         }
00839     }
00840 
00841     return f;
00842 }
00843 
00844 bool KDirModel::canFetchMore( const QModelIndex & parent ) const
00845 {
00846     if (!parent.isValid())
00847         return false;
00848 
00849     // We now have a bool KDirModelNode::m_populated,
00850     // to avoid calling fetchMore more than once on empty dirs.
00851     // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
00852     // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
00853 
00854     KDirModelNode* node = static_cast<KDirModelNode*>(parent.internalPointer());
00855     const KFileItem& item = node->item();
00856     return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated()
00857         && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
00858 }
00859 
00860 void KDirModel::fetchMore( const QModelIndex & parent )
00861 {
00862     if (!parent.isValid())
00863         return;
00864 
00865     KDirModelNode* parentNode = static_cast<KDirModelNode*>(parent.internalPointer());
00866 
00867     KFileItem parentItem = parentNode->item();
00868     Q_ASSERT(!parentItem.isNull());
00869     Q_ASSERT(parentItem.isDir());
00870     KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(parentNode);
00871     if( dirNode->isPopulated() )
00872         return;
00873     dirNode->setPopulated( true );
00874 
00875     const KUrl parentUrl = parentItem.url();
00876     d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
00877 }
00878 
00879 bool KDirModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
00880 {
00881     // Not sure we want to implement any drop handling at this level,
00882     // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
00883     Q_UNUSED(data);
00884     Q_UNUSED(action);
00885     Q_UNUSED(row);
00886     Q_UNUSED(column);
00887     Q_UNUSED(parent);
00888     return false;
00889 }
00890 
00891 void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed)
00892 {
00893     d->m_dropsAllowed = dropsAllowed;
00894 }
00895 
00896 void KDirModel::expandToUrl(const KUrl& url)
00897 {
00898     KDirModelNode* result = d->nodeForUrl(url, true /*return last parent*/); // O(depth)
00899 
00900     if (!result) // doesn't seem related to our base url?
00901         return;
00902     if (!(result->item().isNull()) && result->item().url() == url) {
00903         // We have it already, nothing to do
00904         kDebug(7008) << "have it already item=" <<url /*result->item()*/;
00905         return;
00906     }
00907 
00908     d->m_urlsBeingFetched[result].append(url);
00909 
00910     if (result == d->m_rootNode) {
00911         kDebug(7008) << "Remembering to emit expand after listing the root url";
00912         // the root is fetched by default, so it must be currently being fetched
00913         return;
00914     }
00915 
00916     kDebug(7008) << "Remembering to emit expand after listing" << result->item().url();
00917 
00918     // start a new fetch to look for the next level down the URL
00919     const QModelIndex parentIndex = d->indexForNode(result); // O(n)
00920     Q_ASSERT(parentIndex.isValid());
00921     fetchMore(parentIndex);
00922 }
00923 
00924 bool KDirModel::insertRows(int , int, const QModelIndex&)
00925 {
00926     return false;
00927 }
00928 
00929 bool KDirModel::insertColumns(int, int, const QModelIndex&)
00930 {
00931     return false;
00932 }
00933 
00934 bool KDirModel::removeRows(int, int, const QModelIndex&)
00935 {
00936     return false;
00937 }
00938 
00939 bool KDirModel::removeColumns(int, int, const QModelIndex&)
00940 {
00941     return false;
00942 }
00943 
00944 #include "kdirmodel.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal