38
38
class KDirModelNode;
39
39
class KDirModelDirNode;
41
static KUrl cleanupUrl(const KUrl& url) {
43
u.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
44
u.cleanPath(); // remove double slashes in the path
45
u.setQuery(QString());
41
50
// We create our own tree behind the scenes to have fast lookup from an item to its parent,
42
51
// and also to get the children of an item fast.
43
52
class KDirModelNode
87
96
bool isPopulated() const { return m_populated; }
88
97
void setPopulated( bool populated ) { m_populated = populated; }
99
// For removing all child urls from the global hash.
100
void collectAllChildUrls(KUrl::List &urls) const {
101
Q_FOREACH(KDirModelNode* node, m_childNodes) {
102
const KFileItem& item = node->item();
103
urls.append(cleanupUrl(item.url()));
105
static_cast<KDirModelDirNode*>(node)->collectAllChildUrls(urls);
91
110
int m_childCount:31;
92
111
bool m_populated:1;
113
132
delete m_rootNode;
116
void _k_slotNewItems(const KFileItemList&);
135
void _k_slotNewItems(const KUrl& directoryUrl, const KFileItemList&);
117
136
void _k_slotDeleteItems(const KFileItemList&);
118
137
void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&);
119
138
void _k_slotClear();
122
141
delete m_rootNode;
123
142
m_rootNode = new KDirModelDirNode(0, KFileItem());
125
// Find the row number and node for a given url.
126
// This has to drill down from the root node.
127
// Returns (0,0) if there is no node for this url.
128
// If expandAndReturnLastParent is set, then we emit expand for each parent and then return the
129
// last known parent if there is no node for this url (special case for expandToUrl)
130
KDirModelNode* nodeForUrl(const KUrl& url, bool expandAndReturnLastParent = false) const;
144
// Emit expand for each parent and then return the
145
// last known parent if there is no node for this url
146
KDirModelNode* expandAllParentsUntil(const KUrl& url) const;
148
// Return the node for a given url, using the hash.
149
KDirModelNode* nodeForUrl(const KUrl& url) const;
131
150
KDirModelNode* nodeForIndex(const QModelIndex& index) const;
132
151
QModelIndex indexForNode(KDirModelNode* node, int rowNumber = -1 /*unknown*/) const;
133
152
bool isDir(KDirModelNode* node) const {
146
165
url.setQuery(QString());
147
166
url.setRef(QString()); // kill ref (#171117)
168
if (node == m_rootNode) {
169
// For a URL without a path, like "applications:" or "settings://",
170
// we want to resolve here "no path" to "/ assumed".
171
// We don't do it before (e.g. in KDirLister) because we want to
172
// give the ioslave a chance for a redirect (e.g. kio_ftp redirects "no path"
173
// to the user's home dir)
174
if (url.path().isEmpty())
179
void removeFromNodeHash(KDirModelNode* node, const KUrl& url);
153
185
KDirLister* m_dirLister;
156
188
// key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
157
189
// value = final url[s] being fetched
158
190
QMap<KDirModelNode*, KUrl::List> m_urlsBeingFetched;
191
QHash<KUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
161
// If we want to support arbitrary trees like "home:/ as a child of system:/" then,
162
// we need to get the parent KFileItem in _k_slotNewItems, and then we can use a QHash<KFileItem,KDirModelNode*> cache.
163
// (well there isn't a parent kfileitem, rather a parent url... hmm, back to square one with hashes-of-urls..)
164
// For now we'll assume "child url = parent url + filename"
165
KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url, bool expandAndReturnLastParent) const // O(depth)
168
url.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
169
url.cleanPath(); // remove double slashes in the path
170
url.setQuery(QString());
171
url.setRef(QString());
194
KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url) const // O(1), well, O(length of url as a string)
196
KUrl url = cleanupUrl(_url);
197
if (url == urlForNode(m_rootNode))
199
return m_nodeHash.value(url);
202
void KDirModelPrivate::removeFromNodeHash(KDirModelNode* node, const KUrl& url)
204
if (node->item().isDir()) {
206
static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(urls);
207
Q_FOREACH(const KUrl& u, urls) {
208
m_nodeHash.remove(u);
211
m_nodeHash.remove(cleanupUrl(url));
215
KDirModelNode* KDirModelPrivate::expandAllParentsUntil(const KUrl& _url) const // O(depth)
217
KUrl url = cleanupUrl(_url);
173
219
//kDebug(7008) << url;
174
220
KUrl nodeUrl = urlForNode(m_rootNode);
175
// For a URL without a path, like "applications:" or "settings://",
176
// we want to resolve here "no path" to "/ assumed".
177
// We don't do it before (e.g. in KDirLister) because we want to
178
// give the ioslave a chance for a redirect (e.g. kio_ftp redirects "no path"
179
// to the user's home dir)
180
if (nodeUrl.path().isEmpty())
181
nodeUrl.setPath("/");
183
221
if (url == nodeUrl)
184
222
return m_rootNode;
210
248
KDirModelNode* node = dirNode->m_childNodesByName.value(fileName);
212
250
//kDebug(7008) << "child equal or starting with" << url << "not found";
213
if (expandAndReturnLastParent)
251
// return last parent found:
219
if (expandAndReturnLastParent)
220
emit q->expand(indexForNode(node));
255
emit q->expand(indexForNode(node));
222
257
nodeUrl = urlForNode(node);
223
258
nodeUrl.adjustPath(KUrl::RemoveTrailingSlash); // #172508
273
void KDirModelPrivate::dump()
275
kDebug() << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
276
QHashIterator<KUrl, KDirModelNode *> it(m_nodeHash);
277
while (it.hasNext()) {
279
kDebug() << it.key() << it.value();
237
284
// node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
238
285
QModelIndex KDirModelPrivate::indexForNode(KDirModelNode* node, int rowNumber) const
255
// We don't use QHash<KUrl,...> anymore, it's too slow.
256
// Idea from George, to make QHash<KUrl,...> fast: - cache hash value into QUrl or KUrl
257
// This also helps making operator== fast [which means operator== has to call qHash if cached value isn't there]
258
// But it means invalidating the cached hash value when the url is modified,
259
// so it can't be done in the current KUrl due to the public inheritance from QUrl.
263
303
* This model wraps the data held by KDirLister.
306
346
d->m_dirLister = dirLister;
307
347
d->m_dirLister->setParent(this);
308
connect( d->m_dirLister, SIGNAL(newItems(KFileItemList)),
309
this, SLOT(_k_slotNewItems(KFileItemList)) );
348
connect( d->m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)),
349
this, SLOT(_k_slotNewItems(KUrl,KFileItemList)) );
310
350
connect( d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)),
311
351
this, SLOT(_k_slotDeleteItems(KFileItemList)) );
312
352
connect( d->m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem, KFileItem> >)),
320
360
return d->m_dirLister;
323
void KDirModelPrivate::_k_slotNewItems(const KFileItemList& items)
363
void KDirModelPrivate::_k_slotNewItems(const KUrl& directoryUrl, const KFileItemList& items)
325
// Find parent item - it's the same for all the items
326
// TODO: add parent url to the newItems signal
328
// This way we can finally support properly trees where the urls are using different protocols.
329
// Well, it's not that simple - nodeForUrl still needs to know where to drill down...
331
KUrl firstItemUrl = items.first().url();
332
firstItemUrl.setQuery(QString());
333
firstItemUrl.setRef(QString());
334
KUrl dir(firstItemUrl);
335
dir.setPath(dir.directory());
337
//kDebug(7008) << "dir=" << dir;
339
KDirModelNode* result = nodeForUrl(dir); // O(depth)
365
//kDebug(7008) << "directoryUrl=" << directoryUrl;
367
KDirModelNode* result = nodeForUrl(directoryUrl); // O(depth)
340
368
// If the directory containing the items wasn't found, then we have a big problem.
341
369
// Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead.
343
kError(7008) << "First item has URL" << firstItemUrl
344
<< "-> parent directory would be" << dir
371
kError(7008) << "Items emitted in directory" << directoryUrl
345
372
<< "but that directory isn't in KDirModel!"
346
373
<< "Root directory:" << urlForNode(m_rootNode);
347
377
Q_ASSERT(result);
349
379
Q_ASSERT(isDir(result));
361
391
q->beginInsertRows( index, newRowCount - newItemsCount, newRowCount - 1 ); // parent, first, last
363
393
const KUrl::List urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
364
//kDebug(7008) << "urlsBeingFetched for dir" << dirNode << dir << ":" << urlsBeingFetched;
394
//kDebug(7008) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
366
396
QList<QModelIndex> emitExpandFor;
375
405
dirNode->m_childNodes.append(node);
376
406
const KUrl url = it->url();
377
407
dirNode->m_childNodesByName.insert(url.fileName(), node);
408
m_nodeHash.insert(cleanupUrl(url), node);
378
409
//kDebug(7008) << url;
380
411
if (!urlsBeingFetched.isEmpty()) {
428
461
if (items.count() == 1) {
429
462
const int r = node->rowNumber();
430
463
q->beginRemoveRows(parentIndex, r, r);
464
removeFromNodeHash(node, url);
431
465
delete dirNode->m_childNodes.takeAt(r);
432
466
q->endRemoveRows();
433
467
Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
443
477
if (!node) { // don't lookup the first item twice
444
478
url = item.url();
445
479
node = nodeForUrl(url);
481
kWarning(7008) << "No node found for item that was just removed:" << url;
448
485
rowNumbers.setBit(node->rowNumber(), 1); // O(n)
449
486
Q_ASSERT(dirNode->m_childNodesByName.contains(url.fileName()));
450
487
dirNode->m_childNodesByName.remove(url.fileName());
488
removeFromNodeHash(node, url);
491
529
if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
492
530
node->setItem(fit->second);
494
if (oldUrl.fileName() != newUrl.fileName()) {
495
KDirModelDirNode* parentNode = node->parent();
496
Q_ASSERT(parentNode);
497
parentNode->m_childNodesByName.remove(oldUrl.fileName());
498
parentNode->m_childNodesByName.insert(newUrl.fileName(), node);
532
if (oldUrl != newUrl) {
533
if (oldUrl.fileName() != newUrl.fileName()) {
534
KDirModelDirNode* parentNode = node->parent();
535
Q_ASSERT(parentNode);
536
parentNode->m_childNodesByName.remove(oldUrl.fileName());
537
parentNode->m_childNodesByName.insert(newUrl.fileName(), node);
539
m_nodeHash.remove(cleanupUrl(oldUrl));
540
m_nodeHash.insert(cleanupUrl(newUrl), node);
500
542
if (!topLeft.isValid() || index.row() < topLeft.row()) {
682
725
int KDirModel::rowCount( const QModelIndex & parent ) const
684
KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(d->nodeForIndex(parent));
727
KDirModelNode* node = d->nodeForIndex(parent);
728
if (!node || !d->isDir(node)) // #176555
731
KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(node);
685
732
Q_ASSERT(parentNode);
686
733
const int count = parentNode->m_childNodes.count();
706
753
return d->indexForNode(parentNode); // O(n)
756
static bool lessThan(const KUrl &left, const KUrl &right)
758
return left.url().compare(right.url()) < 0;
761
KUrl::List KDirModel::simplifiedUrlList(const KUrl::List &urls)
767
KUrl::List ret(urls);
768
qSort(ret.begin(), ret.end(), lessThan);
770
KUrl::List::iterator it = ret.begin();
773
while (it != ret.end()) {
774
if (url.isParentOf(*it)) {
709
785
QStringList KDirModel::mimeTypes( ) const
711
return QStringList() << QLatin1String("text/uri-list")
712
<< QLatin1String( "application/x-kde-cutselection" ) // TODO
713
<< QLatin1String( "text/plain" )
714
<< QLatin1String( "application/x-kde-urilist" );
787
return KUrl::List::mimeDataTypes();
717
790
QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
720
foreach ( const QModelIndex &index, indexes ) {
721
urls << d->nodeForIndex( index )->item().url();
792
KUrl::List urls, mostLocalUrls;
793
foreach (const QModelIndex &index, indexes) {
794
const KFileItem& item = d->nodeForIndex(index)->item();
797
mostLocalUrls << item.mostLocalUrl(dummy);
723
799
QMimeData *data = new QMimeData();
724
urls.populateMimeData( data );
800
const bool different = mostLocalUrls != urls;
801
urls = simplifiedUrlList(urls);
803
mostLocalUrls = simplifiedUrlList(mostLocalUrls);
804
urls.populateMimeData(mostLocalUrls, data);
806
urls.populateMimeData(data);
809
// for compatibility reasons (when dropping or pasting into kde3 applications)
810
QString application_x_qiconlist;
811
const int items = urls.count();
812
for (int i = 0; i < items; i++) {
813
const int offset = i*16;
814
QString tmp("%1$@@$%2$@@$32$@@$32$@@$%3$@@$%4$@@$32$@@$16$@@$no data$@@$");
815
application_x_qiconlist += tmp.arg(offset).arg(offset).arg(offset).arg(offset+40);
817
data->setData("application/x-qiconlist", application_x_qiconlist.toLatin1());
904
998
void KDirModel::expandToUrl(const KUrl& url)
906
KDirModelNode* result = d->nodeForUrl(url, true /*emit expand for each parent and return last parent*/); // O(depth)
907
kDebug(7008) << url << result;
1000
// emit expand for each parent and return last parent
1001
KDirModelNode* result = d->expandAllParentsUntil(url); // O(depth)
1002
//kDebug(7008) << url << result;
909
1004
if (!result) // doesn't seem related to our base url?