1 | /* This file is part of the KDE project |
2 | Copyright (C) 2007 Kevin Ottens <ervin@kde.org> |
3 | Copyright (C) 2007 David Faure <faure@kde.org> |
4 | |
5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Library General Public |
7 | License version 2 as published by the Free Software Foundation. |
8 | |
9 | This library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Library General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to |
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | Boston, MA 02110-1301, USA. |
18 | |
19 | */ |
20 | #include "kfileplacesmodel.h" |
21 | #include "kfileplacesitem_p.h" |
22 | #include "kfileplacessharedbookmarks_p.h" |
23 | |
24 | #ifdef _WIN32_WCE |
25 | #include "Windows.h" |
26 | #include "WinBase.h" |
27 | #include <QtCore/QDir> |
28 | #endif |
29 | |
30 | #include <QtCore/QMimeData> |
31 | #include <QtCore/QTimer> |
32 | #include <QtCore/QFile> |
33 | #include <QtGui/QColor> |
34 | #include <QtGui/QAction> |
35 | |
36 | #include <kfileitem.h> |
37 | #include <kglobal.h> |
38 | #include <klocale.h> |
39 | #include <kuser.h> |
40 | #include <kstandarddirs.h> |
41 | #include <kcomponentdata.h> |
42 | #include <kicon.h> |
43 | #include <kmimetype.h> |
44 | #include <kdebug.h> |
45 | |
46 | #include <kbookmarkmanager.h> |
47 | #include <kbookmark.h> |
48 | |
49 | #include <kio/netaccess.h> |
50 | #include <kprotocolinfo.h> |
51 | |
52 | #include <solid/devicenotifier.h> |
53 | #include <solid/storageaccess.h> |
54 | #include <solid/storagedrive.h> |
55 | #include <solid/storagevolume.h> |
56 | #include <solid/opticaldrive.h> |
57 | #include <solid/opticaldisc.h> |
58 | #include <solid/portablemediaplayer.h> |
59 | #include <solid/predicate.h> |
60 | |
61 | class KFilePlacesModel::Private |
62 | { |
63 | public: |
64 | Private(KFilePlacesModel *self) : q(self), bookmarkManager(0), sharedBookmarks(0) {} |
65 | ~Private() |
66 | { |
67 | delete sharedBookmarks; |
68 | qDeleteAll(items); |
69 | } |
70 | |
71 | KFilePlacesModel *q; |
72 | |
73 | QList<KFilePlacesItem*> items; |
74 | QSet<QString> availableDevices; |
75 | QMap<QObject*, QPersistentModelIndex> setupInProgress; |
76 | |
77 | Solid::Predicate predicate; |
78 | KBookmarkManager *bookmarkManager; |
79 | KFilePlacesSharedBookmarks * sharedBookmarks; |
80 | |
81 | void reloadAndSignal(); |
82 | QList<KFilePlacesItem *> loadBookmarkList(); |
83 | |
84 | void _k_initDeviceList(); |
85 | void _k_deviceAdded(const QString &udi); |
86 | void _k_deviceRemoved(const QString &udi); |
87 | void _k_itemChanged(const QString &udi); |
88 | void _k_reloadBookmarks(); |
89 | void _k_storageSetupDone(Solid::ErrorType error, QVariant errorData); |
90 | void _k_storageTeardownDone(Solid::ErrorType error, QVariant errorData); |
91 | }; |
92 | |
93 | KFilePlacesModel::KFilePlacesModel(QObject *parent) |
94 | : QAbstractItemModel(parent), d(new Private(this)) |
95 | { |
96 | const QString file = KStandardDirs::locateLocal("data" , "kfileplaces/bookmarks.xml" ); |
97 | d->bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces" ); |
98 | |
99 | // Let's put some places in there if it's empty. We have a corner case here: |
100 | // Given you have bookmarked some folders (which have been saved on |
101 | // ~/.local/share/user-places.xbel (according to freedesktop bookmarks spec), and |
102 | // deleted the home directory ~/.kde, the call managerForFile() will return the |
103 | // bookmark manager for the fallback "kfilePlaces", making root.first().isNull() being |
104 | // false (you have your own items bookmarked), resulting on only being added your own |
105 | // bookmarks, and not the default ones too. So, we also check if kfileplaces/bookmarks.xml |
106 | // file exists, and if it doesn't, we also add the default places. (ereslibre) |
107 | KBookmarkGroup root = d->bookmarkManager->root(); |
108 | if (root.first().isNull() || !QFile::exists(file)) { |
109 | |
110 | // NOTE: The context for these I18N_NOOP2 calls has to be "KFile System Bookmarks". |
111 | // The real i18nc call is made later, with this context, so the two must match. |
112 | // |
113 | // createSystemBookmark actually does nothing with its third argument, |
114 | // but we have to give it something so the I18N_NOOP2 calls stay here for now. |
115 | // |
116 | // (coles, 13th May 2009) |
117 | |
118 | KFilePlacesItem::createSystemBookmark(d->bookmarkManager, |
119 | "Home" , I18N_NOOP2("KFile System Bookmarks" , "Home" ), |
120 | KUrl(KUser().homeDir()), "user-home" ); |
121 | KFilePlacesItem::createSystemBookmark(d->bookmarkManager, |
122 | "Network" , I18N_NOOP2("KFile System Bookmarks" , "Network" ), |
123 | KUrl("remote:/" ), "network-workgroup" ); |
124 | #if defined(_WIN32_WCE) |
125 | // adding drives |
126 | foreach ( const QFileInfo& info, QDir::drives() ) { |
127 | QString driveIcon = "drive-harddisk" ; |
128 | KFilePlacesItem::createSystemBookmark(d->bookmarkManager, |
129 | info.absoluteFilePath(), info.absoluteFilePath(), |
130 | KUrl(info.absoluteFilePath()), driveIcon); |
131 | } |
132 | #elif !defined(Q_OS_WIN) |
133 | KFilePlacesItem::createSystemBookmark(d->bookmarkManager, |
134 | "Root" , I18N_NOOP2("KFile System Bookmarks" , "Root" ), |
135 | KUrl("/" ), "folder-red" ); |
136 | #endif |
137 | KFilePlacesItem::createSystemBookmark(d->bookmarkManager, |
138 | "Trash" , I18N_NOOP2("KFile System Bookmarks" , "Trash" ), |
139 | KUrl("trash:/" ), "user-trash" ); |
140 | |
141 | // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists |
142 | // will always return false, which opening/closing all the time the open/save dialog would case the |
143 | // bookmarks to be added once each time, having lots of times each bookmark. This forces the defaults |
144 | // to be saved on the bookmarks.xml file. Of course, the complete list of bookmarks (those that come from |
145 | // user-places.xbel will be filled later). (ereslibre) |
146 | d->bookmarkManager->saveAs(file); |
147 | } |
148 | |
149 | // create after, so if we have own places, they are added afterwards, in case of equal priorities |
150 | d->sharedBookmarks = new KFilePlacesSharedBookmarks(d->bookmarkManager); |
151 | |
152 | QString predicate("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" |
153 | " OR " |
154 | "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" |
155 | " OR " |
156 | "OpticalDisc.availableContent & 'Audio' ]" |
157 | " OR " |
158 | "StorageAccess.ignored == false ]" ); |
159 | |
160 | if (KProtocolInfo::isKnownProtocol("mtp" )) { |
161 | predicate.prepend("[" ); |
162 | predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']" ); |
163 | } |
164 | |
165 | d->predicate = Solid::Predicate::fromString(predicate); |
166 | |
167 | Q_ASSERT(d->predicate.isValid()); |
168 | |
169 | connect(d->bookmarkManager, SIGNAL(changed(QString,QString)), |
170 | this, SLOT(_k_reloadBookmarks())); |
171 | connect(d->bookmarkManager, SIGNAL(bookmarksChanged(QString)), |
172 | this, SLOT(_k_reloadBookmarks())); |
173 | |
174 | d->_k_reloadBookmarks(); |
175 | QTimer::singleShot(0, this, SLOT(_k_initDeviceList())); |
176 | } |
177 | |
178 | KFilePlacesModel::~KFilePlacesModel() |
179 | { |
180 | delete d; |
181 | } |
182 | |
183 | KUrl KFilePlacesModel::url(const QModelIndex &index) const |
184 | { |
185 | return KUrl(data(index, UrlRole).toUrl()); |
186 | } |
187 | |
188 | bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const |
189 | { |
190 | return data(index, SetupNeededRole).toBool(); |
191 | } |
192 | |
193 | KIcon KFilePlacesModel::icon(const QModelIndex &index) const |
194 | { |
195 | return KIcon(data(index, Qt::DecorationRole).value<QIcon>()); |
196 | } |
197 | |
198 | QString KFilePlacesModel::text(const QModelIndex &index) const |
199 | { |
200 | return data(index, Qt::DisplayRole).toString(); |
201 | } |
202 | |
203 | bool KFilePlacesModel::isHidden(const QModelIndex &index) const |
204 | { |
205 | return data(index, HiddenRole).toBool(); |
206 | } |
207 | |
208 | bool KFilePlacesModel::isDevice(const QModelIndex &index) const |
209 | { |
210 | if (!index.isValid()) |
211 | return false; |
212 | |
213 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
214 | |
215 | return item->isDevice(); |
216 | } |
217 | |
218 | Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const |
219 | { |
220 | if (!index.isValid()) |
221 | return Solid::Device(); |
222 | |
223 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
224 | |
225 | if (item->isDevice()) { |
226 | return item->device(); |
227 | } else { |
228 | return Solid::Device(); |
229 | } |
230 | } |
231 | |
232 | KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const |
233 | { |
234 | if (!index.isValid()) |
235 | return KBookmark(); |
236 | |
237 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
238 | |
239 | if (!item->isDevice()) { |
240 | return item->bookmark(); |
241 | } else { |
242 | return KBookmark(); |
243 | } |
244 | } |
245 | |
246 | QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const |
247 | { |
248 | if (!index.isValid()) |
249 | return QVariant(); |
250 | |
251 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
252 | return item->data(role); |
253 | } |
254 | |
255 | QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const |
256 | { |
257 | if (row<0 || column!=0 || row>=d->items.size()) |
258 | return QModelIndex(); |
259 | |
260 | if (parent.isValid()) |
261 | return QModelIndex(); |
262 | |
263 | return createIndex(row, column, d->items.at(row)); |
264 | } |
265 | |
266 | QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const |
267 | { |
268 | Q_UNUSED(child); |
269 | return QModelIndex(); |
270 | } |
271 | |
272 | int KFilePlacesModel::rowCount(const QModelIndex &parent) const |
273 | { |
274 | if (parent.isValid()) |
275 | return 0; |
276 | else |
277 | return d->items.size(); |
278 | } |
279 | |
280 | int KFilePlacesModel::columnCount(const QModelIndex &parent) const |
281 | { |
282 | Q_UNUSED(parent) |
283 | // We only know 1 piece of information for a particular entry |
284 | return 1; |
285 | } |
286 | |
287 | QModelIndex KFilePlacesModel::closestItem(const KUrl &url) const |
288 | { |
289 | int foundRow = -1; |
290 | int maxLength = 0; |
291 | |
292 | // Search the item which is equal to the URL or at least is a parent URL. |
293 | // If there are more than one possible item URL candidates, choose the item |
294 | // which covers the bigger range of the URL. |
295 | for (int row = 0; row<d->items.size(); ++row) { |
296 | KFilePlacesItem *item = d->items[row]; |
297 | KUrl itemUrl = KUrl(item->data(UrlRole).toUrl()); |
298 | |
299 | if (itemUrl.isParentOf(url)) { |
300 | const int length = itemUrl.prettyUrl().length(); |
301 | if (length > maxLength) { |
302 | foundRow = row; |
303 | maxLength = length; |
304 | } |
305 | } |
306 | } |
307 | |
308 | if (foundRow==-1) |
309 | return QModelIndex(); |
310 | else |
311 | return createIndex(foundRow, 0, d->items[foundRow]); |
312 | } |
313 | |
314 | void KFilePlacesModel::Private::_k_initDeviceList() |
315 | { |
316 | Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); |
317 | |
318 | connect(notifier, SIGNAL(deviceAdded(QString)), |
319 | q, SLOT(_k_deviceAdded(QString))); |
320 | connect(notifier, SIGNAL(deviceRemoved(QString)), |
321 | q, SLOT(_k_deviceRemoved(QString))); |
322 | |
323 | const QList<Solid::Device> &deviceList = Solid::Device::listFromQuery(predicate); |
324 | |
325 | foreach(const Solid::Device &device, deviceList) { |
326 | availableDevices << device.udi(); |
327 | } |
328 | |
329 | _k_reloadBookmarks(); |
330 | } |
331 | |
332 | void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi) |
333 | { |
334 | Solid::Device d(udi); |
335 | |
336 | if (predicate.matches(d)) { |
337 | availableDevices << udi; |
338 | _k_reloadBookmarks(); |
339 | } |
340 | } |
341 | |
342 | void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi) |
343 | { |
344 | if (availableDevices.contains(udi)) { |
345 | availableDevices.remove(udi); |
346 | _k_reloadBookmarks(); |
347 | } |
348 | } |
349 | |
350 | void KFilePlacesModel::Private::_k_itemChanged(const QString &id) |
351 | { |
352 | for (int row = 0; row<items.size(); ++row) { |
353 | if (items.at(row)->id()==id) { |
354 | QModelIndex index = q->index(row, 0); |
355 | emit q->dataChanged(index, index); |
356 | } |
357 | } |
358 | } |
359 | |
360 | void KFilePlacesModel::Private::_k_reloadBookmarks() |
361 | { |
362 | QList<KFilePlacesItem*> currentItems = loadBookmarkList(); |
363 | |
364 | QList<KFilePlacesItem*>::Iterator it_i = items.begin(); |
365 | QList<KFilePlacesItem*>::Iterator it_c = currentItems.begin(); |
366 | |
367 | QList<KFilePlacesItem*>::Iterator end_i = items.end(); |
368 | QList<KFilePlacesItem*>::Iterator end_c = currentItems.end(); |
369 | |
370 | while (it_i!=end_i || it_c!=end_c) { |
371 | if (it_i==end_i && it_c!=end_c) { |
372 | int row = items.count(); |
373 | |
374 | q->beginInsertRows(QModelIndex(), row, row); |
375 | it_i = items.insert(it_i, *it_c); |
376 | ++it_i; |
377 | it_c = currentItems.erase(it_c); |
378 | |
379 | end_i = items.end(); |
380 | end_c = currentItems.end(); |
381 | q->endInsertRows(); |
382 | |
383 | } else if (it_i!=end_i && it_c==end_c) { |
384 | int row = items.indexOf(*it_i); |
385 | |
386 | q->beginRemoveRows(QModelIndex(), row, row); |
387 | delete *it_i; |
388 | it_i = items.erase(it_i); |
389 | |
390 | end_i = items.end(); |
391 | end_c = currentItems.end(); |
392 | q->endRemoveRows(); |
393 | |
394 | } else if ((*it_i)->id()==(*it_c)->id()) { |
395 | bool shouldEmit = !((*it_i)->bookmark()==(*it_c)->bookmark()); |
396 | (*it_i)->setBookmark((*it_c)->bookmark()); |
397 | if (shouldEmit) { |
398 | int row = items.indexOf(*it_i); |
399 | QModelIndex idx = q->index(row, 0); |
400 | emit q->dataChanged(idx, idx); |
401 | } |
402 | ++it_i; |
403 | ++it_c; |
404 | } else if ((*it_i)->id()!=(*it_c)->id()) { |
405 | int row = items.indexOf(*it_i); |
406 | |
407 | if (it_i+1!=end_i && (*(it_i+1))->id()==(*it_c)->id()) { // if the next one matches, it's a remove |
408 | q->beginRemoveRows(QModelIndex(), row, row); |
409 | delete *it_i; |
410 | it_i = items.erase(it_i); |
411 | |
412 | end_i = items.end(); |
413 | end_c = currentItems.end(); |
414 | q->endRemoveRows(); |
415 | } else { |
416 | q->beginInsertRows(QModelIndex(), row, row); |
417 | it_i = items.insert(it_i, *it_c); |
418 | ++it_i; |
419 | it_c = currentItems.erase(it_c); |
420 | |
421 | end_i = items.end(); |
422 | end_c = currentItems.end(); |
423 | q->endInsertRows(); |
424 | } |
425 | } |
426 | } |
427 | |
428 | qDeleteAll(currentItems); |
429 | currentItems.clear(); |
430 | } |
431 | |
432 | QList<KFilePlacesItem *> KFilePlacesModel::Private::loadBookmarkList() |
433 | { |
434 | QList<KFilePlacesItem*> items; |
435 | |
436 | KBookmarkGroup root = bookmarkManager->root(); |
437 | KBookmark bookmark = root.first(); |
438 | QSet<QString> devices = availableDevices; |
439 | |
440 | while (!bookmark.isNull()) { |
441 | QString udi = bookmark.metaDataItem("UDI" ); |
442 | QString appName = bookmark.metaDataItem("OnlyInApp" ); |
443 | bool deviceAvailable = devices.remove(udi); |
444 | |
445 | bool allowedHere = appName.isEmpty() || (appName==KGlobal::mainComponent().componentName()); |
446 | |
447 | if ((udi.isEmpty() && allowedHere) || deviceAvailable) { |
448 | KFilePlacesItem *item; |
449 | if (deviceAvailable) { |
450 | item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); |
451 | // TODO: Update bookmark internal element |
452 | } else { |
453 | item = new KFilePlacesItem(bookmarkManager, bookmark.address()); |
454 | } |
455 | connect(item, SIGNAL(itemChanged(QString)), |
456 | q, SLOT(_k_itemChanged(QString))); |
457 | items << item; |
458 | } |
459 | |
460 | bookmark = root.next(bookmark); |
461 | } |
462 | |
463 | // Add bookmarks for the remaining devices, they were previously unknown |
464 | foreach (const QString &udi, devices) { |
465 | bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi); |
466 | if (!bookmark.isNull()) { |
467 | KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, |
468 | bookmark.address(), udi); |
469 | connect(item, SIGNAL(itemChanged(QString)), |
470 | q, SLOT(_k_itemChanged(QString))); |
471 | // TODO: Update bookmark internal element |
472 | items << item; |
473 | } |
474 | } |
475 | |
476 | return items; |
477 | } |
478 | |
479 | void KFilePlacesModel::Private::reloadAndSignal() |
480 | { |
481 | bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway |
482 | } |
483 | |
484 | Qt::DropActions KFilePlacesModel::supportedDropActions() const |
485 | { |
486 | return Qt::ActionMask; |
487 | } |
488 | |
489 | Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const |
490 | { |
491 | Qt::ItemFlags res = Qt::ItemIsSelectable|Qt::ItemIsEnabled; |
492 | |
493 | if (index.isValid()) |
494 | res|= Qt::ItemIsDragEnabled; |
495 | |
496 | if (!index.isValid()) |
497 | res|= Qt::ItemIsDropEnabled; |
498 | |
499 | return res; |
500 | } |
501 | |
502 | static QString _k_internalMimetype(const KFilePlacesModel * const self) |
503 | { |
504 | return QString("application/x-kfileplacesmodel-" )+QString::number((qptrdiff)self); |
505 | } |
506 | |
507 | QStringList KFilePlacesModel::mimeTypes() const |
508 | { |
509 | QStringList types; |
510 | |
511 | types << _k_internalMimetype(this) << "text/uri-list" ; |
512 | |
513 | return types; |
514 | } |
515 | |
516 | QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const |
517 | { |
518 | KUrl::List urls; |
519 | QByteArray itemData; |
520 | |
521 | QDataStream stream(&itemData, QIODevice::WriteOnly); |
522 | |
523 | foreach (const QModelIndex &index, indexes) { |
524 | KUrl itemUrl = url(index); |
525 | if (itemUrl.isValid()) |
526 | urls << itemUrl; |
527 | stream << index.row(); |
528 | } |
529 | |
530 | QMimeData *mimeData = new QMimeData(); |
531 | |
532 | if (!urls.isEmpty()) |
533 | urls.populateMimeData(mimeData); |
534 | |
535 | mimeData->setData(_k_internalMimetype(this), itemData); |
536 | |
537 | return mimeData; |
538 | } |
539 | |
540 | bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, |
541 | int row, int column, const QModelIndex &parent) |
542 | { |
543 | if (action == Qt::IgnoreAction) |
544 | return true; |
545 | |
546 | if (column > 0) |
547 | return false; |
548 | |
549 | if (row==-1 && parent.isValid()) { |
550 | return false; // Don't allow to move an item onto another one, |
551 | // too easy for the user to mess something up |
552 | // If we really really want to allow copying files this way, |
553 | // let's do it in the views to get the good old drop menu |
554 | } |
555 | |
556 | |
557 | KBookmark afterBookmark; |
558 | |
559 | if (row==-1) { |
560 | // The dropped item is moved or added to the last position |
561 | |
562 | KFilePlacesItem *lastItem = d->items.last(); |
563 | afterBookmark = lastItem->bookmark(); |
564 | |
565 | } else { |
566 | // The dropped item is moved or added before position 'row', ie after position 'row-1' |
567 | |
568 | if (row>0) { |
569 | KFilePlacesItem *afterItem = d->items[row-1]; |
570 | afterBookmark = afterItem->bookmark(); |
571 | } |
572 | } |
573 | |
574 | if (data->hasFormat(_k_internalMimetype(this))) { |
575 | // The operation is an internal move |
576 | QByteArray itemData = data->data(_k_internalMimetype(this)); |
577 | QDataStream stream(&itemData, QIODevice::ReadOnly); |
578 | int itemRow; |
579 | |
580 | stream >> itemRow; |
581 | |
582 | KFilePlacesItem *item = d->items[itemRow]; |
583 | KBookmark bookmark = item->bookmark(); |
584 | |
585 | int destRow = row == -1 ? d->items.count() : row; |
586 | // The item is not moved when the drop indicator is on either item edge |
587 | if (itemRow == destRow || itemRow + 1 == destRow) { |
588 | return false; |
589 | } |
590 | |
591 | beginMoveRows(QModelIndex(), itemRow, itemRow, QModelIndex(), destRow); |
592 | d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark); |
593 | // Move item ourselves so that _k_reloadBookmarks() does not consider |
594 | // the move as a remove + insert. |
595 | // |
596 | // 2nd argument of QList::move() expects the final destination index, |
597 | // but 'row' is the value of the destination index before the moved |
598 | // item has been removed from its original position. That is why we |
599 | // adjust if necessary. |
600 | d->items.move(itemRow, itemRow < destRow ? (destRow - 1) : destRow); |
601 | endMoveRows(); |
602 | } else if (data->hasFormat("text/uri-list" )) { |
603 | // The operation is an add |
604 | KUrl::List urls = KUrl::List::fromMimeData(data); |
605 | |
606 | KBookmarkGroup group = d->bookmarkManager->root(); |
607 | |
608 | foreach (const KUrl &url, urls) { |
609 | // TODO: use KIO::stat in order to get the UDS_DISPLAY_NAME too |
610 | KMimeType::Ptr mimetype = KMimeType::mimeType(KIO::NetAccess::mimetype(url, 0)); |
611 | |
612 | if (!mimetype) { |
613 | kWarning() << "URL not added to Places as mimetype could not be determined!" ; |
614 | continue; |
615 | } |
616 | |
617 | if (!mimetype->is("inode/directory" )) { |
618 | // Only directories are allowed |
619 | continue; |
620 | } |
621 | |
622 | KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, |
623 | url.fileName(), url, |
624 | mimetype->iconName(url)); |
625 | group.moveBookmark(bookmark, afterBookmark); |
626 | afterBookmark = bookmark; |
627 | } |
628 | |
629 | } else { |
630 | // Oops, shouldn't happen thanks to mimeTypes() |
631 | kWarning() << ": received wrong mimedata, " << data->formats(); |
632 | return false; |
633 | } |
634 | |
635 | d->reloadAndSignal(); |
636 | |
637 | return true; |
638 | } |
639 | |
640 | void KFilePlacesModel::addPlace(const QString &text, const KUrl &url, |
641 | const QString &iconName, const QString &appName) |
642 | { |
643 | addPlace(text, url, iconName, appName, QModelIndex()); |
644 | } |
645 | |
646 | void KFilePlacesModel::addPlace(const QString &text, const KUrl &url, |
647 | const QString &iconName, const QString &appName, |
648 | const QModelIndex &after) |
649 | { |
650 | KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, |
651 | text, url, iconName); |
652 | |
653 | if (!appName.isEmpty()) { |
654 | bookmark.setMetaDataItem("OnlyInApp" , appName); |
655 | } |
656 | |
657 | if (after.isValid()) { |
658 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(after.internalPointer()); |
659 | d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark()); |
660 | } |
661 | |
662 | d->reloadAndSignal(); |
663 | } |
664 | |
665 | void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const KUrl &url, |
666 | const QString &iconName, const QString &appName) |
667 | { |
668 | if (!index.isValid()) return; |
669 | |
670 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
671 | |
672 | if (item->isDevice()) return; |
673 | |
674 | KBookmark bookmark = item->bookmark(); |
675 | |
676 | if (bookmark.isNull()) return; |
677 | |
678 | bookmark.setFullText(text); |
679 | bookmark.setUrl(url); |
680 | bookmark.setIcon(iconName); |
681 | bookmark.setMetaDataItem("OnlyInApp" , appName); |
682 | |
683 | d->reloadAndSignal(); |
684 | emit dataChanged(index, index); |
685 | } |
686 | |
687 | void KFilePlacesModel::removePlace(const QModelIndex &index) const |
688 | { |
689 | if (!index.isValid()) return; |
690 | |
691 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
692 | |
693 | if (item->isDevice()) return; |
694 | |
695 | KBookmark bookmark = item->bookmark(); |
696 | |
697 | if (bookmark.isNull()) return; |
698 | |
699 | d->bookmarkManager->root().deleteBookmark(bookmark); |
700 | d->reloadAndSignal(); |
701 | } |
702 | |
703 | void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden) |
704 | { |
705 | if (!index.isValid()) return; |
706 | |
707 | KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer()); |
708 | |
709 | KBookmark bookmark = item->bookmark(); |
710 | |
711 | if (bookmark.isNull()) return; |
712 | |
713 | bookmark.setMetaDataItem("IsHidden" , (hidden ? "true" : "false" )); |
714 | |
715 | d->reloadAndSignal(); |
716 | emit dataChanged(index, index); |
717 | } |
718 | |
719 | int KFilePlacesModel::hiddenCount() const |
720 | { |
721 | int rows = rowCount(); |
722 | int hidden = 0; |
723 | |
724 | for (int i=0; i<rows; ++i) { |
725 | if (isHidden(index(i, 0))) { |
726 | hidden++; |
727 | } |
728 | } |
729 | |
730 | return hidden; |
731 | } |
732 | |
733 | QAction *KFilePlacesModel::teardownActionForIndex(const QModelIndex &index) const |
734 | { |
735 | Solid::Device device = deviceForIndex(index); |
736 | |
737 | if (device.is<Solid::StorageAccess>() && device.as<Solid::StorageAccess>()->isAccessible()) { |
738 | |
739 | Solid::StorageDrive *drive = device.as<Solid::StorageDrive>(); |
740 | |
741 | if (drive==0) { |
742 | drive = device.parent().as<Solid::StorageDrive>(); |
743 | } |
744 | |
745 | bool hotpluggable = false; |
746 | bool removable = false; |
747 | |
748 | if (drive!=0) { |
749 | hotpluggable = drive->isHotpluggable(); |
750 | removable = drive->isRemovable(); |
751 | } |
752 | |
753 | QString iconName; |
754 | QString text; |
755 | QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&" ); |
756 | |
757 | if (device.is<Solid::OpticalDisc>()) { |
758 | text = i18n("&Release '%1'" , label); |
759 | } else if (removable || hotpluggable) { |
760 | text = i18n("&Safely Remove '%1'" , label); |
761 | iconName = "media-eject" ; |
762 | } else { |
763 | text = i18n("&Unmount '%1'" , label); |
764 | iconName = "media-eject" ; |
765 | } |
766 | |
767 | if (!iconName.isEmpty()) { |
768 | return new QAction(KIcon(iconName), text, 0); |
769 | } else { |
770 | return new QAction(text, 0); |
771 | } |
772 | } |
773 | |
774 | return 0; |
775 | } |
776 | |
777 | QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const |
778 | { |
779 | Solid::Device device = deviceForIndex(index); |
780 | |
781 | if (device.is<Solid::OpticalDisc>()) { |
782 | |
783 | QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&" ); |
784 | QString text = i18n("&Eject '%1'" , label); |
785 | |
786 | return new QAction(KIcon("media-eject" ), text, 0); |
787 | } |
788 | |
789 | return 0; |
790 | } |
791 | |
792 | void KFilePlacesModel::requestTeardown(const QModelIndex &index) |
793 | { |
794 | Solid::Device device = deviceForIndex(index); |
795 | Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); |
796 | |
797 | if (access!=0) { |
798 | connect(access, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)), |
799 | this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); |
800 | |
801 | access->teardown(); |
802 | } |
803 | } |
804 | |
805 | void KFilePlacesModel::requestEject(const QModelIndex &index) |
806 | { |
807 | Solid::Device device = deviceForIndex(index); |
808 | |
809 | Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>(); |
810 | |
811 | if (drive!=0) { |
812 | connect(drive, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)), |
813 | this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); |
814 | |
815 | drive->eject(); |
816 | } else { |
817 | QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&" ); |
818 | QString message = i18n("The device '%1' is not a disk and cannot be ejected." , label); |
819 | emit errorMessage(message); |
820 | } |
821 | } |
822 | |
823 | void KFilePlacesModel::requestSetup(const QModelIndex &index) |
824 | { |
825 | Solid::Device device = deviceForIndex(index); |
826 | |
827 | if (device.is<Solid::StorageAccess>() |
828 | && !d->setupInProgress.contains(device.as<Solid::StorageAccess>()) |
829 | && !device.as<Solid::StorageAccess>()->isAccessible()) { |
830 | |
831 | Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); |
832 | |
833 | d->setupInProgress[access] = index; |
834 | |
835 | connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), |
836 | this, SLOT(_k_storageSetupDone(Solid::ErrorType,QVariant))); |
837 | |
838 | access->setup(); |
839 | } |
840 | } |
841 | |
842 | void KFilePlacesModel::Private::_k_storageSetupDone(Solid::ErrorType error, QVariant errorData) |
843 | { |
844 | QPersistentModelIndex index = setupInProgress.take(q->sender()); |
845 | |
846 | if (!index.isValid()) { |
847 | return; |
848 | } |
849 | |
850 | if (!error) { |
851 | emit q->setupDone(index, true); |
852 | } else { |
853 | if (errorData.isValid()) { |
854 | emit q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2" , |
855 | q->text(index), |
856 | errorData.toString())); |
857 | } else { |
858 | emit q->errorMessage(i18n("An error occurred while accessing '%1'" , |
859 | q->text(index))); |
860 | } |
861 | emit q->setupDone(index, false); |
862 | } |
863 | |
864 | } |
865 | |
866 | void KFilePlacesModel::Private::_k_storageTeardownDone(Solid::ErrorType error, QVariant errorData) |
867 | { |
868 | if (error && errorData.isValid()) { |
869 | emit q->errorMessage(errorData.toString()); |
870 | } |
871 | } |
872 | |
873 | #include "kfileplacesmodel.moc" |
874 | |