/* * Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "foldermodel.h" #include "icontheme.h" #include #include #include #include #include #include #include #include #include #include #include "utilities.h" #include "fileoperation.h" namespace Fm { FolderModel::FolderModel(): hasPendingThumbnailHandler_{false} { } FolderModel::~FolderModel() { qDebug("delete FolderModel"); // if the thumbnail requests list is not empty, cancel them for(auto job: pendingThumbnailJobs_) { job->cancel(); } } void FolderModel::setFolder(const std::shared_ptr& new_folder) { if(folder_) { removeAll(); // remove old items } if(new_folder) { folder_ = new_folder; connect(folder_.get(), &Fm::Folder::startLoading, this, &FolderModel::onStartLoading); connect(folder_.get(), &Fm::Folder::finishLoading, this, &FolderModel::onFinishLoading); connect(folder_.get(), &Fm::Folder::filesAdded, this, &FolderModel::onFilesAdded); connect(folder_.get(), &Fm::Folder::filesChanged, this, &FolderModel::onFilesChanged); connect(folder_.get(), &Fm::Folder::filesRemoved, this, &FolderModel::onFilesRemoved); // handle the case if the folder is already loaded if(folder_->isLoaded()) { insertFiles(0, folder_->files()); } } } void FolderModel::onStartLoading() { // remove all items removeAll(); } void FolderModel::onFinishLoading() { } void FolderModel::onFilesAdded(const Fm::FileInfoList& files) { int n_files = files.size(); beginInsertRows(QModelIndex(), items.count(), items.count() + n_files - 1); for(auto& info : files) { FolderModelItem item(info); /* if(fm_file_info_is_hidden(info)) { model->hiddenItems.append(item); continue; } */ items.append(item); } endInsertRows(); } void FolderModel::onFilesChanged(std::vector& files) { for(auto& change : files) { int row; auto& oldInfo = change.first; auto& newInfo = change.second; QList::iterator it = findItemByFileInfo(oldInfo.get(), &row); if(it != items.end()) { FolderModelItem& item = *it; // try to update the item item.info = newInfo; item.thumbnails.clear(); QModelIndex index = createIndex(row, 0, &item); Q_EMIT dataChanged(index, index); if(oldInfo->size() != newInfo->size()) { Q_EMIT fileSizeChanged(index); } } } } void FolderModel::onFilesRemoved(const Fm::FileInfoList& files) { for(auto& info : files) { int row; QList::iterator it = findItemByName(info->name().c_str(), &row); if(it != items.end()) { beginRemoveRows(QModelIndex(), row, row); items.erase(it); endRemoveRows(); } } } void FolderModel::loadPendingThumbnails() { hasPendingThumbnailHandler_ = false; for(auto& item: thumbnailData_) { if(!item.pendingThumbnails_.empty()) { auto job = new Fm::ThumbnailJob(std::move(item.pendingThumbnails_), item.size_); pendingThumbnailJobs_.push_back(job); job->setAutoDelete(true); connect(job, &Fm::ThumbnailJob::thumbnailLoaded, this, &FolderModel::onThumbnailLoaded, Qt::BlockingQueuedConnection); connect(job, &Fm::ThumbnailJob::finished, this, &FolderModel::onThumbnailJobFinished, Qt::BlockingQueuedConnection); Fm::ThumbnailJob::threadPool()->start(job); } } } void FolderModel::queueLoadThumbnail(const std::shared_ptr& file, int size) { auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;}); if(it != thumbnailData_.end()) { it->pendingThumbnails_.push_back(file); if(!hasPendingThumbnailHandler_) { QTimer::singleShot(0, this, &FolderModel::loadPendingThumbnails); hasPendingThumbnailHandler_ = true; } } } void FolderModel::insertFiles(int row, const Fm::FileInfoList& files) { int n_files = files.size(); beginInsertRows(QModelIndex(), row, row + n_files - 1); for(auto& info : files) { FolderModelItem item(info); items.append(item); } endInsertRows(); } void FolderModel::setCutFiles(const QItemSelection& selection) { if(folder_) { if(!selection.isEmpty()) { auto cutFilesHashSet = std::make_shared(); folder_->setCutFiles(cutFilesHashSet); for(const auto& index : selection.indexes()) { auto item = itemFromIndex(index); item->bindCutFiles(cutFilesHashSet); cutFilesHashSet->insert(item->info->path().hash()); } } Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); } } void FolderModel::removeAll() { if(items.empty()) { return; } beginRemoveRows(QModelIndex(), 0, items.size() - 1); items.clear(); endRemoveRows(); } int FolderModel::rowCount(const QModelIndex& parent) const { if(parent.isValid()) { return 0; } return items.size(); } int FolderModel::columnCount(const QModelIndex& parent = QModelIndex()) const { if(parent.isValid()) { return 0; } return NumOfColumns; } FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const { return reinterpret_cast(index.internalPointer()); } std::shared_ptr FolderModel::fileInfoFromIndex(const QModelIndex& index) const { FolderModelItem* item = itemFromIndex(index); return item ? item->info : nullptr; } QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const { if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) { return QVariant(); } FolderModelItem* item = itemFromIndex(index); auto info = item->info; bool isCut = false; if(folder_ && Q_UNLIKELY(folder_->hasCutFiles())) { isCut = item->isCut(); } switch(role) { case Qt::ToolTipRole: return QVariant(item->displayName()); case Qt::DisplayRole: { switch(index.column()) { case ColumnFileName: return item->displayName(); case ColumnFileType: return QString(info->mimeType()->desc()); case ColumnFileMTime: return item->displayMtime(); case ColumnFileSize: return item->displaySize(); case ColumnFileOwner: return item->ownerName(); } break; } case Qt::DecorationRole: { if(index.column() == 0) { return QVariant(item->icon(isCut)); } break; } case Qt::EditRole: { if(index.column() == 0) { return QString::fromStdString(info->name()); } break; } case FileInfoRole: return QVariant::fromValue(info); case FileIsDirRole: return QVariant(info->isDir()); case FileIsCutRole: return isCut; } return QVariant(); } QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const { if(role == Qt::DisplayRole) { if(orientation == Qt::Horizontal) { QString title; switch(section) { case ColumnFileName: title = tr("Name"); break; case ColumnFileType: title = tr("Type"); break; case ColumnFileSize: title = tr("Size"); break; case ColumnFileMTime: title = tr("Modified"); break; case ColumnFileOwner: title = tr("Owner"); break; } return QVariant(title); } } return QVariant(); } QModelIndex FolderModel::index(int row, int column, const QModelIndex& /*parent*/) const { if(row < 0 || row >= items.size() || column < 0 || column >= NumOfColumns) { return QModelIndex(); } const FolderModelItem& item = items.at(row); return createIndex(row, column, (void*)&item); } QModelIndex FolderModel::parent(const QModelIndex& /*index*/) const { return QModelIndex(); } Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const { // FIXME: should not return same flags unconditionally for all columns Qt::ItemFlags flags; if(index.isValid()) { flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if(index.column() == ColumnFileName) { flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable); // inline renaming); } } else { flags = Qt::ItemIsDropEnabled; } return flags; } // FIXME: this is very inefficient and should be replaced with a // more reasonable implementation later. QList::iterator FolderModel::findItemByPath(const Fm::FilePath& path, int* row) { QList::iterator it = items.begin(); int i = 0; while(it != items.end()) { FolderModelItem& item = *it; auto item_path = item.info->path(); if(item_path == path) { *row = i; return it; } ++it; ++i; } return items.end(); } // FIXME: this is very inefficient and should be replaced with a // more reasonable implementation later. QList::iterator FolderModel::findItemByName(const char* name, int* row) { QList::iterator it = items.begin(); int i = 0; while(it != items.end()) { FolderModelItem& item = *it; if(item.info->name() == name) { *row = i; return it; } ++it; ++i; } return items.end(); } QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::FileInfo* info, int* row) { QList::iterator it = items.begin(); int i = 0; while(it != items.end()) { FolderModelItem& item = *it; if(item.info.get() == info) { *row = i; return it; } ++it; ++i; } return items.end(); } QStringList FolderModel::mimeTypes() const { qDebug("FolderModel::mimeTypes"); QStringList types = QAbstractItemModel::mimeTypes(); // now types contains "application/x-qabstractitemmodeldatalist" // add support for freedesktop Xdnd direct save (XDS) protocol. // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2 // the real implementation is in FolderView::childDropEvent(). types << "XdndDirectSave0"; types << "text/uri-list"; // types << "x-special/gnome-copied-files"; return types; } QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const { QMimeData* data = QAbstractItemModel::mimeData(indexes); qDebug("FolderModel::mimeData"); // build a uri list QByteArray urilist; urilist.reserve(4096); for(const auto& index : indexes) { FolderModelItem* item = itemFromIndex(index); if(item && item->info) { auto path = item->info->path(); if(path.isValid()) { auto uri = path.uri(); urilist.append(uri.get()); urilist.append('\n'); } } } data->setData("text/uri-list", urilist); return data; } bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { qDebug("FolderModel::dropMimeData"); if(!folder_ || !data) { return false; } Fm::FilePath destPath; if(parent.isValid()) { // drop on an item std::shared_ptr info; if(row == -1 && column == -1) { info = fileInfoFromIndex(parent); } else { QModelIndex itemIndex = parent.child(row, column); info = fileInfoFromIndex(itemIndex); } if(info) { destPath = info->path(); } else { return false; } } else { // drop on blank area of the folder destPath = path(); } // FIXME: should we put this in dropEvent handler of FolderView instead? if(data->hasUrls()) { qDebug("drop action: %d", action); auto srcPaths = pathListFromQUrls(data->urls()); switch(action) { case Qt::CopyAction: FileOperation::copyFiles(srcPaths, destPath); break; case Qt::MoveAction: FileOperation::moveFiles(srcPaths, destPath); break; case Qt::LinkAction: FileOperation::symlinkFiles(srcPaths, destPath); default: return false; } return true; } else if(data->hasFormat("application/x-qabstractitemmodeldatalist")) { return true; } return QAbstractListModel::dropMimeData(data, action, row, column, parent); } Qt::DropActions FolderModel::supportedDropActions() const { qDebug("FolderModel::supportedDropActions"); return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } // ask the model to load thumbnails of the specified size void FolderModel::cacheThumbnails(const int size) { auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;}); if(it != thumbnailData_.cend()) { ++it->refCount_; } else { thumbnailData_.push_front(ThumbnailData(size)); } } // ask the model to free cached thumbnails of the specified size void FolderModel::releaseThumbnails(int size) { auto prev = thumbnailData_.before_begin(); for(auto it = thumbnailData_.begin(); it != thumbnailData_.end(); ++it) { if(it->size_ == size) { --it->refCount_; if(it->refCount_ == 0) { thumbnailData_.erase_after(prev); } // remove all cached thumbnails of the specified size QList::iterator itemIt; for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) { FolderModelItem& item = *itemIt; item.removeThumbnail(size); } break; } prev = it; } } void FolderModel::onThumbnailJobFinished() { Fm::ThumbnailJob* job = static_cast(sender()); auto it = std::find(pendingThumbnailJobs_.cbegin(), pendingThumbnailJobs_.cend(), job); if(it != pendingThumbnailJobs_.end()) { pendingThumbnailJobs_.erase(it); } } void FolderModel::onThumbnailLoaded(const std::shared_ptr& file, int size, const QImage& image) { // find the model item this thumbnail belongs to int row; QList::iterator it = findItemByFileInfo(file.get(), &row); if(it != items.end()) { // the file is found in our model FolderModelItem& item = *it; QModelIndex index = createIndex(row, 0, (void*)&item); // store the image in the folder model item. FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size, false); thumbnail->image = image; thumbnail->transparent = false; // qDebug("thumbnail loaded for: %s, size: %d", item.displayName.toUtf8().constData(), size); if(image.isNull()) { thumbnail->status = FolderModelItem::ThumbnailFailed; } else { thumbnail->status = FolderModelItem::ThumbnailLoaded; thumbnail->image = image; // tell the world that we have the thumbnail loaded Q_EMIT thumbnailLoaded(index, size); } } } // get a thumbnail of size at the index // if a thumbnail is not yet loaded, this will initiate loading of the thumbnail. QImage FolderModel::thumbnailFromIndex(const QModelIndex& index, int size) { FolderModelItem* item = itemFromIndex(index); if(item) { FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size, item->isCut()); // qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().data()); switch(thumbnail->status) { case FolderModelItem::ThumbnailNotChecked: { // load the thumbnail queueLoadThumbnail(item->info, size); thumbnail->status = FolderModelItem::ThumbnailLoading; break; } case FolderModelItem::ThumbnailLoaded: return thumbnail->image; default: ; } } return QImage(); } } // namespace Fm