• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.3 API Reference
  • KDE Home
  • Contact Us
 

KIO

kdirmodel.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kdirmodel.h"
00021 #include "kdirlister.h"
00022 #include "kfileitem.h"
00023 #include <kdatetime.h>
00024 #include <kicon.h>
00025 #include <klocale.h>
00026 #include <kglobal.h>
00027 #include <kio/copyjob.h>
00028 #include <kio/fileundomanager.h>
00029 #include <kio/jobuidelegate.h>
00030 #include <kio/joburlcache_p.h>
00031 #include <kurl.h>
00032 #include <kdebug.h>
00033 #include <QMimeData>
00034 #include <QFile>
00035 #include <QFileInfo>
00036 #include <QDir>
00037 #include <sys/types.h>
00038 #include <dirent.h>
00039 
00040 #ifdef Q_WS_WIN
00041 #include <windows.h>
00042 #endif
00043 
00044 class KDirModelNode;
00045 class KDirModelDirNode;
00046 
00047 static KUrl cleanupUrl(const KUrl& url) {
00048     KUrl u = url;
00049     u.cleanPath(); // remove double slashes in the path, simplify "foo/." to "foo/", etc.
00050     u.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
00051     u.setQuery(QString());
00052     u.setRef(QString());
00053     return u;
00054 }
00055 
00056 // We create our own tree behind the scenes to have fast lookup from an item to its parent,
00057 // and also to get the children of an item fast.
00058 class KDirModelNode
00059 {
00060 public:
00061     KDirModelNode( KDirModelDirNode* parent, const KFileItem& item ) :
00062         m_item(item),
00063         m_parent(parent),
00064         m_preview()
00065     {
00066     }
00067     // m_item is KFileItem() for the root item
00068     const KFileItem& item() const { return m_item; }
00069     void setItem(const KFileItem& item) { m_item = item; }
00070     KDirModelDirNode* parent() const { return m_parent; }
00071     // linear search
00072     int rowNumber() const; // O(n)
00073     QIcon preview() const { return m_preview; }
00074     void setPreview( const QPixmap& pix ) {  m_preview = QIcon(); m_preview.addPixmap(pix); }
00075     void setPreview( const QIcon& icn ) { m_preview = icn; }
00076 
00077 private:
00078     KFileItem m_item;
00079     KDirModelDirNode* const m_parent;
00080     QIcon m_preview;
00081 };
00082 
00083 // Specialization for directory nodes
00084 class KDirModelDirNode : public KDirModelNode
00085 {
00086 public:
00087     KDirModelDirNode( KDirModelDirNode* parent, const KFileItem& item)
00088         : KDirModelNode( parent, item),
00089           m_childNodes(),
00090           m_childCount(KDirModel::ChildCountUnknown),
00091           m_populated(false)
00092     {}
00093     ~KDirModelDirNode() {
00094         qDeleteAll(m_childNodes);
00095     }
00096     QList<KDirModelNode *> m_childNodes; // owns the nodes
00097 
00098     // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
00099     int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); }
00100     void setChildCount(int count) { m_childCount = count; }
00101     bool isPopulated() const { return m_populated; }
00102     void setPopulated( bool populated ) { m_populated = populated; }
00103     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 Thu May 10 2012 20:55:20 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

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

kdelibs-4.8.3 API Reference

Skip menu "kdelibs-4.8.3 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal