1 /*
2 * Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
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 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20
21 #include "foldermodel.h"
22 #include <iostream>
23 #include <algorithm>
24 #include <QtAlgorithms>
25 #include <QVector>
26 #include <qmimedata.h>
27 #include <QMimeData>
28 #include <QByteArray>
29 #include <QPixmap>
30 #include <QPainter>
31 #include <QTimer>
32 #include <QDateTime>
33 #include <QString>
34 #include <QApplication>
35 #include <QClipboard>
36 #include "utilities.h"
37 #include "fileoperation.h"
38
39 namespace Fm {
40
FolderModel()41 FolderModel::FolderModel():
42 hasPendingThumbnailHandler_{false},
43 showFullNames_{false},
44 isLoaded_{false},
45 hasCutfile_{false} {
46 connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FolderModel::onClipboardDataChange);
47 }
48
~FolderModel()49 FolderModel::~FolderModel() {
50 // if the thumbnail requests list is not empty, cancel them
51 for(auto job: pendingThumbnailJobs_) {
52 job->cancel();
53 }
54 }
55
setFolder(const std::shared_ptr<Fm::Folder> & new_folder)56 void FolderModel::setFolder(const std::shared_ptr<Fm::Folder>& new_folder) {
57 if(folder_) {
58 removeAll(); // remove old items
59 }
60 if(new_folder) {
61 folder_ = new_folder;
62 connect(folder_.get(), &Fm::Folder::startLoading, this, &FolderModel::onStartLoading);
63 connect(folder_.get(), &Fm::Folder::finishLoading, this, &FolderModel::onFinishLoading);
64 connect(folder_.get(), &Fm::Folder::filesAdded, this, &FolderModel::onFilesAdded);
65 connect(folder_.get(), &Fm::Folder::filesChanged, this, &FolderModel::onFilesChanged);
66 connect(folder_.get(), &Fm::Folder::filesRemoved, this, &FolderModel::onFilesRemoved);
67 // handle the case if the folder is already loaded
68 if(folder_->isLoaded()) {
69 isLoaded_ = true;
70 insertFiles(0, folder_->files());
71 onClipboardDataChange(); // files may have been cut
72 }
73 }
74 }
75
onStartLoading()76 void FolderModel::onStartLoading() {
77 isLoaded_ = false;
78 hasCutfile_ = false;
79 // remove all items
80 removeAll();
81 }
82
onFinishLoading()83 void FolderModel::onFinishLoading() {
84 isLoaded_ = true;
85 // files may have been cut before (e.g., by another app)
86 onClipboardDataChange();
87 }
88
onFilesAdded(const Fm::FileInfoList & files)89 void FolderModel::onFilesAdded(const Fm::FileInfoList& files) {
90 int n_files = files.size();
91 beginInsertRows(QModelIndex(), items.count(), items.count() + n_files - 1);
92 for(auto& info : files) {
93 FolderModelItem item(info);
94
95 // cut files may be removed and added again
96 if(isLoaded_ && cutFilesHashSet_.count(info->path().hash()) != 0) {
97 item.isCut = true;
98 hasCutfile_ = true;
99 }
100
101 items.append(item);
102 }
103 endInsertRows();
104
105 if(isLoaded_) {
106 Q_EMIT filesAdded(files);
107 }
108 }
109
onFilesChanged(std::vector<Fm::FileInfoPair> & files)110 void FolderModel::onFilesChanged(std::vector<Fm::FileInfoPair>& files) {
111 for(auto& change : files) {
112 int row;
113 auto& oldInfo = change.first;
114 auto& newInfo = change.second;
115 QList<FolderModelItem>::iterator it = findItemByFileInfo(oldInfo.get(), &row);
116 if(it != items.end()) {
117 FolderModelItem& item = *it;
118 // try to update the item
119 item.info = newInfo;
120 item.thumbnails.clear();
121 QModelIndex index = createIndex(row, 0, &item);
122 Q_EMIT dataChanged(index, index);
123 if(oldInfo->size() != newInfo->size()) {
124 Q_EMIT fileSizeChanged(index);
125 }
126 }
127 }
128 }
129
onFilesRemoved(const Fm::FileInfoList & files)130 void FolderModel::onFilesRemoved(const Fm::FileInfoList& files) {
131 for(auto& info : files) {
132 int row;
133 QList<FolderModelItem>::iterator it = findItemByName(info->name().c_str(), &row);
134 if(it != items.end()) {
135 beginRemoveRows(QModelIndex(), row, row);
136 items.erase(it);
137 endRemoveRows();
138 }
139 }
140 }
141
loadPendingThumbnails()142 void FolderModel::loadPendingThumbnails() {
143 hasPendingThumbnailHandler_ = false;
144 for(auto& item: thumbnailData_) {
145 if(!item.pendingThumbnails_.empty()) {
146 auto job = new Fm::ThumbnailJob(std::move(item.pendingThumbnails_), item.size_);
147 pendingThumbnailJobs_.push_back(job);
148 job->setAutoDelete(true);
149 connect(job, &Fm::ThumbnailJob::thumbnailLoaded, this, &FolderModel::onThumbnailLoaded, Qt::BlockingQueuedConnection);
150 connect(job, &Fm::ThumbnailJob::finished, this, &FolderModel::onThumbnailJobFinished, Qt::BlockingQueuedConnection);
151 Fm::ThumbnailJob::threadPool()->start(job);
152 }
153 }
154 }
155
queueLoadThumbnail(const std::shared_ptr<const Fm::FileInfo> & file,int size)156 void FolderModel::queueLoadThumbnail(const std::shared_ptr<const Fm::FileInfo>& file, int size) {
157 auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;});
158 if(it != thumbnailData_.end()) {
159 it->pendingThumbnails_.push_back(file);
160 if(!hasPendingThumbnailHandler_) {
161 QTimer::singleShot(0, this, &FolderModel::loadPendingThumbnails);
162 hasPendingThumbnailHandler_ = true;
163 }
164 }
165 }
166
insertFiles(int row,const Fm::FileInfoList & files)167 void FolderModel::insertFiles(int row, const Fm::FileInfoList& files) {
168 int n_files = files.size();
169 beginInsertRows(QModelIndex(), row, row + n_files - 1);
170 for(auto& info : files) {
171 FolderModelItem item(info);
172 items.append(item);
173 }
174 endInsertRows();
175 }
176
updateCutFilesSet()177 void FolderModel::updateCutFilesSet() {
178 // NOTE: To process large numbers of files as fast as possible,
179 // we analyze the clipboard data directly, without using "utilities.h",
180 // because otherwise, we should iterate through path lists multiple times.
181
182 if(folder_ == nullptr) {
183 return;
184 }
185 cutFilesHashSet_.clear();
186
187 bool cutPathFound = false;
188 const QClipboard* clipboard = QApplication::clipboard();
189 const QMimeData* data = clipboard->mimeData();
190
191 // Gnome, LXDE, XFCE (see utilities.cpp -> pasteFilesFromClipboard)
192 if(data->hasFormat(QStringLiteral("x-special/gnome-copied-files"))) {
193 QByteArray gnomeData = data->data(QStringLiteral("x-special/gnome-copied-files"));
194 char* pdata = gnomeData.data();
195 char* eol = strchr(pdata, '\n');
196 if(eol) {
197 *eol = '\0';
198 if(strcmp(pdata, "cut") == 0) {
199 char** uris = g_strsplit_set(eol + 1, "\r\n", -1);
200 for(char** uri = uris; *uri; ++uri) {
201 if(**uri != '\0') {
202 cutPathFound = true; // although it may not be in this folder
203 auto path = Fm::FilePath::fromUri(*uri);
204 if(path.parent() == folder_->path()) {
205 cutFilesHashSet_.insert(path.hash());
206 }
207 }
208 }
209 g_strfreev(uris);
210 }
211 }
212 }
213 // KDE
214 if(!cutPathFound && data->hasUrls()) {
215 QByteArray cut = data->data(QStringLiteral("application/x-kde-cutselection"));
216 if(!cut.isEmpty() && QChar::fromLatin1(cut.at(0)) == QLatin1Char('1')) {
217 QList<QUrl> urls = data->urls();
218 for(auto it = urls.cbegin(); it != urls.cend(); ++it) {
219 auto path = Fm::FilePath::fromUri(it->toString().toUtf8().constData());
220 if(path.parent() == folder_->path()) {
221 cutFilesHashSet_.insert(path.hash());
222 }
223 }
224 }
225 }
226 }
227
onClipboardDataChange()228 void FolderModel::onClipboardDataChange() {
229 if(folder_ && isLoaded_) {
230 updateCutFilesSet();
231 if(!cutFilesHashSet_.empty()) {
232 // (some) files are cut here; so the items need to be updated
233 hasCutfile_ = false;
234 QList<FolderModelItem>::iterator it = items.begin();
235 while(it != items.end()) {
236 FolderModelItem& item = *it;
237 if(cutFilesHashSet_.count(it->info->path().hash()) != 0) {
238 item.isCut = true;
239 hasCutfile_ = true;
240 }
241 else {
242 item.isCut = false;
243 }
244 ++it;
245 }
246 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
247 }
248 else if(hasCutfile_) {
249 // this folder contained a cut file before but not anymore;
250 // so its items need to be updated
251 hasCutfile_ = false;
252 QList<FolderModelItem>::iterator it = items.begin();
253 while(it != items.end()) {
254 FolderModelItem& item = *it;
255 item.isCut = false;
256 ++it;
257 }
258 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
259 }
260 }
261 }
262
removeAll()263 void FolderModel::removeAll() {
264 if(items.empty()) {
265 return;
266 }
267 beginRemoveRows(QModelIndex(), 0, items.size() - 1);
268 items.clear();
269 endRemoveRows();
270 }
271
rowCount(const QModelIndex & parent) const272 int FolderModel::rowCount(const QModelIndex& parent) const {
273 if(parent.isValid()) {
274 return 0;
275 }
276 return items.size();
277 }
278
columnCount(const QModelIndex & parent=QModelIndex ()) const279 int FolderModel::columnCount(const QModelIndex& parent = QModelIndex()) const {
280 if(parent.isValid()) {
281 return 0;
282 }
283 return NumOfColumns;
284 }
285
itemFromIndex(const QModelIndex & index) const286 FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const {
287 return reinterpret_cast<FolderModelItem*>(index.internalPointer());
288 }
289
fileInfoFromIndex(const QModelIndex & index) const290 std::shared_ptr<const Fm::FileInfo> FolderModel::fileInfoFromIndex(const QModelIndex& index) const {
291 FolderModelItem* item = itemFromIndex(index);
292 return item ? item->info : nullptr;
293 }
294
makeTooltip(FolderModelItem * item) const295 QString FolderModel::makeTooltip(FolderModelItem* item) const {
296 // name (bold)
297 QString tip = QStringLiteral("<p><b>") + (showFullNames_ ? QString::fromStdString(item->name()) : item->displayName()).toHtmlEscaped() + QStringLiteral("</b></p>");
298
299 // parent dir
300 auto info = item->info;
301 auto parent_path = info->path().parent();
302 auto parent_str = parent_path ? parent_path.displayName() : nullptr;
303 if(parent_str) {
304 tip += QStringLiteral("<p><i>") + tr("Location:") + QStringLiteral("</i> ") + QString::fromUtf8(parent_str.get()).toHtmlEscaped() + QStringLiteral("</p>");
305 }
306
307 // file type
308 tip += QStringLiteral("<i>") + tr("File type:") + QStringLiteral("</i> ") + QString::fromUtf8(info->mimeType()->desc());
309
310 // file size
311 const QString dSize = item->displaySize();
312 if(!dSize.isEmpty()) { // it's empty for directories
313 tip += QStringLiteral("<br><i>") + tr("File size:") + QStringLiteral("</i> ") + dSize;
314 }
315
316 // times
317 tip += QStringLiteral("<br><i>") + tr("Last modified:") + QStringLiteral("</i> ") + item->displayMtime()
318 + QStringLiteral("<br><i>") + tr("Last accessed:") + QStringLiteral("</i> ")
319 + (info->atime() == 0 ? tr("N/A")
320 : QDateTime::fromMSecsSinceEpoch(info->atime() * 1000).toString(Qt::SystemLocaleShortDate))
321 + QStringLiteral("<br><i>") + tr("Created:") + QStringLiteral("</i> ") + item->displayCrtime();
322
323 // owner
324 const QString owner = item->ownerName();
325 if(!owner.isEmpty()) { // can be empty
326 tip += QStringLiteral("<br><i>") + tr("Owner:") + QStringLiteral("</i> ") + owner
327 + QStringLiteral("<br><i>") + tr("Group:") + QStringLiteral("</i> ") + item->ownerGroup();
328 }
329
330 return tip;
331 }
332
data(const QModelIndex & index,int role) const333 QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const {
334 if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) {
335 return QVariant();
336 }
337 FolderModelItem* item = itemFromIndex(index);
338 auto info = item->info;
339 bool isCut = folder_ && item->isCut;
340
341 switch(role) {
342 case Qt::ToolTipRole:
343 return QVariant(makeTooltip(item));
344 case Qt::DisplayRole: {
345 switch(index.column()) {
346 case ColumnFileName:
347 return (showFullNames_ && !item->name().empty() ? QString::fromStdString(item->name())
348 : item->displayName());
349 case ColumnFileType:
350 return QString::fromUtf8(info->mimeType()->desc());
351 case ColumnFileMTime:
352 return item->displayMtime();
353 case ColumnFileCrTime:
354 return item->displayCrtime();
355 case ColumnFileDTime:
356 return item->displayDtime();
357 case ColumnFileSize:
358 return item->displaySize();
359 case ColumnFileOwner:
360 return item->ownerName();
361 case ColumnFileGroup:
362 return item->ownerGroup();
363 }
364 break;
365 }
366 case Qt::DecorationRole: {
367 if(index.column() == 0) {
368 return QVariant(item->icon());
369 }
370 break;
371 }
372 case Qt::EditRole: {
373 if(index.column() == 0) {
374 return QString::fromStdString(info->name());
375 }
376 break;
377 }
378 case FileInfoRole:
379 return QVariant::fromValue(info);
380 case FileIsDirRole:
381 return QVariant(info->isDir());
382 case FileIsCutRole:
383 return isCut;
384 }
385 return QVariant();
386 }
387
headerData(int section,Qt::Orientation orientation,int role) const388 QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const {
389 if(role == Qt::DisplayRole) {
390 if(orientation == Qt::Horizontal) {
391 QString title;
392 switch(section) {
393 case ColumnFileName:
394 title = tr("Name");
395 break;
396 case ColumnFileType:
397 title = tr("Type");
398 break;
399 case ColumnFileSize:
400 title = tr("Size");
401 break;
402 case ColumnFileMTime:
403 title = tr("Modified");
404 break;
405 case ColumnFileCrTime:
406 title = tr("Created");
407 break;
408 case ColumnFileDTime:
409 title = tr("Deleted");
410 break;
411 case ColumnFileOwner:
412 title = tr("Owner");
413 break;
414 case ColumnFileGroup:
415 title = tr("Group");
416 break;
417 }
418 return QVariant(title);
419 }
420 }
421 return QVariant();
422 }
423
index(int row,int column,const QModelIndex &) const424 QModelIndex FolderModel::index(int row, int column, const QModelIndex& /*parent*/) const {
425 if(row < 0 || row >= items.size() || column < 0 || column >= NumOfColumns) {
426 return QModelIndex();
427 }
428 const FolderModelItem& item = items.at(row);
429 return createIndex(row, column, (void*)&item);
430 }
431
parent(const QModelIndex &) const432 QModelIndex FolderModel::parent(const QModelIndex& /*index*/) const {
433 return QModelIndex();
434 }
435
flags(const QModelIndex & index) const436 Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const {
437 // FIXME: should not return same flags unconditionally for all columns
438 Qt::ItemFlags flags;
439 if(index.isValid()) {
440 flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
441 if(index.column() == ColumnFileName) {
442 flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
443 | Qt::ItemIsEditable); // inline renaming);
444 }
445 }
446 else {
447 flags = Qt::ItemIsDropEnabled;
448 }
449 return flags;
450 }
451
fileInfoFromPath(const Fm::FilePath & path) const452 std::shared_ptr<const Fm::FileInfo> FolderModel::fileInfoFromPath(const Fm::FilePath& path) const {
453 QList<FolderModelItem>::const_iterator it = items.begin();
454 while(it != items.end()) {
455 const FolderModelItem& item = *it;
456 if(item.info->path() == path) {
457 return item.info;
458 }
459 ++it;
460 }
461 return nullptr;
462 }
463
464 // FIXME: this is very inefficient and should be replaced with a
465 // more reasonable implementation later.
findItemByName(const char * name,int * row)466 QList<FolderModelItem>::iterator FolderModel::findItemByName(const char* name, int* row) {
467 QList<FolderModelItem>::iterator it = items.begin();
468 int i = 0;
469 while(it != items.end()) {
470 FolderModelItem& item = *it;
471 if(item.info->name() == name) {
472 *row = i;
473 return it;
474 }
475 ++it;
476 ++i;
477 }
478 return items.end();
479 }
480
findItemByFileInfo(const Fm::FileInfo * info,int * row)481 QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::FileInfo* info, int* row) {
482 QList<FolderModelItem>::iterator it = items.begin();
483 int i = 0;
484 while(it != items.end()) {
485 FolderModelItem& item = *it;
486 if(item.info.get() == info) {
487 *row = i;
488 return it;
489 }
490 ++it;
491 ++i;
492 }
493 return items.end();
494 }
495
mimeTypes() const496 QStringList FolderModel::mimeTypes() const {
497 //qDebug("FolderModel::mimeTypes");
498 QStringList types = QAbstractItemModel::mimeTypes();
499 // now types contains "application/x-qabstractitemmodeldatalist"
500
501 // add support for freedesktop Xdnd direct save (XDS) protocol.
502 // https://www.freedesktop.org/wiki/Specifications/XDS/#index4h2
503 // the real implementation is in FolderView::childDropEvent().
504 types << QStringLiteral("XdndDirectSave0");
505 types << QStringLiteral("text/uri-list");
506 types << QStringLiteral("libfm/files"); // see FolderModel::mimeData() below
507 return types;
508 }
509
mimeData(const QModelIndexList & indexes) const510 QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const {
511 QMimeData* data = QAbstractItemModel::mimeData(indexes);
512 //qDebug("FolderModel::mimeData");
513 // build two uri lists, one for internal DND
514 // and the other for DNDing to external apps
515 QByteArray urilist, libfmUrilist;
516 urilist.reserve(4096);
517 libfmUrilist.reserve(4096);
518
519 for(const auto& index : indexes) {
520 FolderModelItem* item = itemFromIndex(index);
521 if(item && item->info) {
522 auto path = item->info->path();
523 if(path.isValid()) {
524 // get the list that will be used by internal DND
525 auto uri = path.uri();
526 libfmUrilist.append(uri.get());
527 libfmUrilist.append('\n');
528
529 // also, get the list that will be used when DNDing to external apps,
530 // using local paths as far as possible (for DNDing from remote folders)
531 if(auto localPath = path.localPath()) {
532 QUrl url = QUrl::fromLocalFile(QString::fromUtf8(localPath.get()));
533 urilist.append(url.toEncoded());
534 }
535 else {
536 urilist.append(uri.get());
537 }
538 urilist.append('\n');
539 }
540 }
541 }
542 data->setData(QStringLiteral("text/uri-list"), urilist);
543 // NOTE: The mimetype "text/uri-list" changes the list in QMimeData::setData() to get URLs
544 // but some protocols (like MTP) may need the original list to query file info.
545 data->setData(QStringLiteral("libfm/files"), libfmUrilist);
546
547 return data;
548 }
549
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)550 bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
551 //qDebug("FolderModel::dropMimeData");
552 if(!folder_ || !data) {
553 return false;
554 }
555 Fm::FilePath destPath;
556 if(parent.isValid()) { // drop on an item
557 std::shared_ptr<const Fm::FileInfo> info;
558 if(row == -1 && column == -1) {
559 info = fileInfoFromIndex(parent);
560 }
561 else {
562 QModelIndex itemIndex = index(row, column, parent);
563 info = fileInfoFromIndex(itemIndex);
564 }
565 if(info) {
566 if (info->isDir()) {
567 destPath = info->path();
568 }
569 else {
570 destPath = path(); // don't drop on file
571 }
572 }
573 else {
574 return false;
575 }
576 }
577 else { // drop on blank area of the folder
578 destPath = path();
579 }
580
581 Fm::FilePathList srcPaths;
582 // try to get paths from the original data
583 if(data->hasFormat(QStringLiteral("libfm/files"))) {
584 QByteArray _data = data->data(QStringLiteral("libfm/files"));
585 srcPaths = pathListFromUriList(_data.data());
586 }
587 if(srcPaths.empty() && data->hasUrls()) {
588 srcPaths = Fm::pathListFromQUrls(data->urls());
589 }
590
591 // FIXME: should we put this in dropEvent handler of FolderView instead?
592 if(!srcPaths.empty()) {
593 //qDebug("drop action: %d", action);
594 switch(action) {
595 case Qt::CopyAction:
596 FileOperation::copyFiles(srcPaths, destPath);
597 break;
598 case Qt::MoveAction:
599 FileOperation::moveFiles(srcPaths, destPath);
600 break;
601 case Qt::LinkAction:
602 FileOperation::symlinkFiles(srcPaths, destPath);
603 /* Falls through. */
604 default:
605 return false;
606 }
607 return true;
608 }
609 else if(data->hasFormat(QStringLiteral("application/x-qabstractitemmodeldatalist"))) {
610 return true;
611 }
612 return QAbstractListModel::dropMimeData(data, action, row, column, parent);
613 }
614
supportedDropActions() const615 Qt::DropActions FolderModel::supportedDropActions() const {
616 //qDebug("FolderModel::supportedDropActions");
617 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
618 }
619
620 // ask the model to load thumbnails of the specified size
cacheThumbnails(const int size)621 void FolderModel::cacheThumbnails(const int size) {
622 auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;});
623 if(it != thumbnailData_.cend()) {
624 ++it->refCount_;
625 }
626 else {
627 thumbnailData_.push_front(ThumbnailData(size));
628 }
629 }
630
631 // ask the model to free cached thumbnails of the specified size
releaseThumbnails(int size)632 void FolderModel::releaseThumbnails(int size) {
633 auto prev = thumbnailData_.before_begin();
634 for(auto it = thumbnailData_.begin(); it != thumbnailData_.end(); ++it) {
635 if(it->size_ == size) {
636 --it->refCount_;
637 if(it->refCount_ == 0) {
638 thumbnailData_.erase_after(prev);
639 }
640
641 // remove all cached thumbnails of the specified size
642 QList<FolderModelItem>::iterator itemIt;
643 for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) {
644 FolderModelItem& item = *itemIt;
645 item.removeThumbnail(size);
646 }
647 break;
648 }
649 prev = it;
650 }
651 }
652
onThumbnailJobFinished()653 void FolderModel::onThumbnailJobFinished() {
654 Fm::ThumbnailJob* job = static_cast<Fm::ThumbnailJob*>(sender());
655 auto it = std::find(pendingThumbnailJobs_.cbegin(), pendingThumbnailJobs_.cend(), job);
656 if(it != pendingThumbnailJobs_.end()) {
657 pendingThumbnailJobs_.erase(it);
658 }
659 }
660
onThumbnailLoaded(const std::shared_ptr<const Fm::FileInfo> & file,int size,const QImage & image)661 void FolderModel::onThumbnailLoaded(const std::shared_ptr<const Fm::FileInfo>& file, int size, const QImage& image) {
662 // find the model item this thumbnail belongs to
663 int row;
664 QList<FolderModelItem>::iterator it = findItemByFileInfo(file.get(), &row);
665 if(it != items.end()) {
666 // the file is found in our model
667 FolderModelItem& item = *it;
668 QModelIndex index = createIndex(row, 0, (void*)&item);
669 // store the image in the folder model item.
670 FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size);
671 thumbnail->image = image;
672 // qDebug("thumbnail loaded for: %s, size: %d", item.displayName.toUtf8().constData(), size);
673 if(image.isNull()) {
674 thumbnail->status = FolderModelItem::ThumbnailFailed;
675 }
676 else {
677 thumbnail->status = FolderModelItem::ThumbnailLoaded;
678 thumbnail->image = image;
679
680 // tell the world that we have the thumbnail loaded
681 Q_EMIT thumbnailLoaded(index, size);
682 }
683 }
684 }
685
686 // get a thumbnail of size at the index
687 // if a thumbnail is not yet loaded, this will initiate loading of the thumbnail.
thumbnailFromIndex(const QModelIndex & index,int size)688 QImage FolderModel::thumbnailFromIndex(const QModelIndex& index, int size) {
689 FolderModelItem* item = itemFromIndex(index);
690 if(item) {
691 FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size);
692 // qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().data());
693 switch(thumbnail->status) {
694 case FolderModelItem::ThumbnailNotChecked: {
695 // load the thumbnail
696 queueLoadThumbnail(item->info, size);
697 thumbnail->status = FolderModelItem::ThumbnailLoading;
698 break;
699 }
700 case FolderModelItem::ThumbnailLoaded:
701 return thumbnail->image;
702 default:
703 ;
704 }
705 }
706 return QImage();
707 }
708
709
710 } // namespace Fm
711