libfm-qt-packaging/src/dirtreemodelitem.cpp
Alf Gaida b2567a5f34
Adding upstream version 0.12.0.
Signed-off-by: Alf Gaida <agaida@siduction.org>
2017-10-22 00:13:03 +02:00

418 lines
15 KiB
C++

/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* 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 "dirtreemodelitem.h"
#include "dirtreemodel.h"
#include "icontheme.h"
#include <QDebug>
namespace Fm {
DirTreeModelItem::DirTreeModelItem():
fileInfo_(nullptr),
folder_(nullptr),
expanded_(false),
loaded_(false),
parent_(nullptr),
placeHolderChild_(nullptr),
model_(nullptr),
queuedForDeletion_(false) {
}
DirTreeModelItem::DirTreeModelItem(std::shared_ptr<const Fm::FileInfo> info, DirTreeModel* model, DirTreeModelItem* parent):
fileInfo_{std::move(info)},
expanded_(false),
loaded_(false),
parent_(parent),
placeHolderChild_(nullptr),
model_(model),
queuedForDeletion_(false) {
if(fileInfo_) {
displayName_ = fileInfo_->displayName();
icon_ = fileInfo_->icon()->qicon();
addPlaceHolderChild();
}
}
DirTreeModelItem::~DirTreeModelItem() {
freeFolder();
// delete child items if needed
if(!children_.empty()) {
Q_FOREACH(DirTreeModelItem* item, children_) {
delete item;
}
}
if(!hiddenChildren_.empty()) {
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
delete item;
}
}
/*if(queuedForDeletion_)
qDebug() << "queued deletion done";*/
}
void DirTreeModelItem::freeFolder() {
if(folder_) {
QObject::disconnect(onFolderFinishLoadingConn_);
QObject::disconnect(onFolderFilesAddedConn_);
QObject::disconnect(onFolderFilesRemovedConn_);
QObject::disconnect(onFolderFilesChangedConn_);
folder_.reset();
}
}
void DirTreeModelItem::addPlaceHolderChild() {
placeHolderChild_ = new DirTreeModelItem();
placeHolderChild_->parent_ = this;
placeHolderChild_->model_ = model_;
placeHolderChild_->displayName_ = DirTreeModel::tr("Loading...");
children_.push_back(placeHolderChild_);
}
void DirTreeModelItem::loadFolder() {
if(!expanded_) {
/* dynamically load content of the folder. */
folder_ = Fm::Folder::fromPath(fileInfo_->path());
/* g_debug("fm_dir_tree_model_load_row()"); */
/* associate the data with loaded handler */
onFolderFinishLoadingConn_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, model_, [=]() {
onFolderFinishLoading();
});
onFolderFilesAddedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesAdded, model_, [=](Fm::FileInfoList files) {
onFolderFilesAdded(files);
});
onFolderFilesRemovedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesRemoved, model_, [=](Fm::FileInfoList files) {
onFolderFilesRemoved(files);
});
onFolderFilesChangedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesChanged, model_, [=](std::vector<Fm::FileInfoPair>& changes) {
onFolderFilesChanged(changes);
});
/* set 'expanded' flag beforehand as callback may check it */
expanded_ = true;
/* if the folder is already loaded, call "loaded" handler ourselves */
if(folder_->isLoaded()) { // already loaded
insertFiles(folder_->files());
onFolderFinishLoading();
}
}
}
void DirTreeModelItem::unloadFolder() {
if(expanded_) { /* do some cleanup */
/* remove all children, and replace them with a dummy child
* item to keep expander in the tree view around. */
// delete all visible child items
model_->beginRemoveRows(index(), 0, children_.size() - 1);
if(!children_.empty()) {
Q_FOREACH(DirTreeModelItem* item, children_) {
delete item;
}
children_.clear();
}
model_->endRemoveRows();
// remove hidden children
if(!hiddenChildren_.empty()) {
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
delete item;
}
hiddenChildren_.clear();
}
/* now, we have no child since all child items are removed.
* So we add a place holder child item to keep the expander around. */
addPlaceHolderChild();
/* deactivate folder since it will be reactivated on expand */
freeFolder();
expanded_ = false;
loaded_ = false;
}
}
QModelIndex DirTreeModelItem::index() {
Q_ASSERT(model_);
return model_->indexFromItem(this);
}
/* Add file info to parent node to proper position. */
DirTreeModelItem* DirTreeModelItem::insertFile(std::shared_ptr<const Fm::FileInfo> fi) {
// qDebug() << "insertFileInfo: " << fm_file_info_get_disp_name(fi);
DirTreeModelItem* item = new DirTreeModelItem(std::move(fi), model_);
insertItem(item);
return item;
}
/* Add file info to parent node to proper position. */
void DirTreeModelItem::insertFiles(Fm::FileInfoList files) {
if(children_.size() == 1 && placeHolderChild_) {
// the list is empty, add them all at once and do sort
if(!model_->showHidden()) { // need to separate visible and hidden items
// insert hidden files into the "hiddenChildren_" list and remove them from "files" list
// WARNING: "std::remove_if" shouldn't be used to work on the "removed" items because, as
// docs say, the elements between the returned and the end iterators are in an unspecified
// state and, as far as I (@tsujan) have tested, some of them announce themselves as null.
for(auto it = files.begin(); it != files.end();) {
auto file = *it;
if(file->isHidden()) {
hiddenChildren_.push_back(new DirTreeModelItem{std::move(file), model_});
it = files.erase(it);
}
else {
++it;
}
}
}
// sort the remaining visible files by name
std::sort(files.begin(), files.end(), [](const std::shared_ptr<const Fm::FileInfo>& a, const std::shared_ptr<const Fm::FileInfo>& b) {
return QString::localeAwareCompare(a->displayName(), b->displayName()) < 0;
});
// insert the files into the visible children list at once
model_->beginInsertRows(index(), 1, files.size() + 1); // the first item is the placeholder item, so we start from row 1
for(auto& file: files) {
if(file->isDir()) {
DirTreeModelItem* newItem = new DirTreeModelItem(std::move(file), model_);
newItem->parent_ = this;
children_.push_back(newItem);
}
}
model_->endInsertRows();
// remove the place holder if a folder is added
if(children_.size() > 1) {
auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_);
if(it != children_.cend()) {
auto pos = it - children_.cbegin();
model_->beginRemoveRows(index(), pos, pos);
children_.erase(it);
delete placeHolderChild_;
model_->endRemoveRows();
placeHolderChild_ = nullptr;
}
}
}
else {
// the list already contain some items, insert new items one by one so they can be sorted.
for(auto& file: files) {
if(file->isDir()) {
insertFile(std::move(file));
}
}
}
}
// find a good position to insert the new item
// FIXME: insert one item at a time is slow. Insert multiple items at once and then sort is faster.
int DirTreeModelItem::insertItem(DirTreeModelItem* newItem) {
if(!newItem->fileInfo_ || !newItem->fileInfo_->isDir()) {
// don't insert placeholders or non-directory files
return -1;
}
if(model_->showHidden() || !newItem->fileInfo_ || !newItem->fileInfo_->isHidden()) {
auto it = std::lower_bound(children_.cbegin(), children_.cend(), newItem, [=](const DirTreeModelItem* a, const DirTreeModelItem* b) {
if(Q_UNLIKELY(!a->fileInfo_)) {
return true; // this is a placeholder item which will be removed so the order doesn't matter.
}
if(Q_UNLIKELY(!b->fileInfo_)) {
return false;
}
return QString::localeAwareCompare(a->fileInfo_->displayName(), b->fileInfo_->displayName()) < 0;
});
// inform the world that we're about to insert the item
auto position = it - children_.begin();
model_->beginInsertRows(index(), position, position);
newItem->parent_ = this;
children_.insert(it, newItem);
model_->endInsertRows();
return position;
}
else { // hidden folder
hiddenChildren_.push_back(newItem);
}
return -1;
}
// FmFolder signal handlers
void DirTreeModelItem::onFolderFinishLoading() {
DirTreeModel* model = model_;
/* set 'loaded' flag beforehand as callback may check it */
loaded_ = true;
QModelIndex idx = index();
//qDebug() << "folder loaded";
// remove the placeholder child if needed
// (a check for its existence is necessary; see insertItem)
if(placeHolderChild_) {
if(children_.size() == 1) { // we have no other child other than the place holder item, leave it
placeHolderChild_->displayName_ = DirTreeModel::tr("<No sub folders>");
QModelIndex placeHolderIndex = placeHolderChild_->index();
// qDebug() << "placeHolderIndex: "<<placeHolderIndex;
Q_EMIT model->dataChanged(placeHolderIndex, placeHolderIndex);
}
else {
auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_);
if(it != children_.cend()) {
auto pos = it - children_.cbegin();
model->beginRemoveRows(idx, pos, pos);
children_.erase(it);
delete placeHolderChild_;
model->endRemoveRows();
placeHolderChild_ = nullptr;
}
}
}
Q_EMIT model->rowLoaded(idx);
}
void DirTreeModelItem::onFolderFilesAdded(Fm::FileInfoList& files) {
insertFiles(files);
}
void DirTreeModelItem::onFolderFilesRemoved(Fm::FileInfoList& files) {
DirTreeModel* model = model_;
for(auto& fi: files) {
int pos;
DirTreeModelItem* child = childFromName(fi->name().c_str(), &pos);
if(child) {
// The item shouldn't be deleted now but after its row is removed from QTreeView;
// otherwise a freeze will happen when it has a child item (its row is expanded).
child->queuedForDeletion_ = true;
model->beginRemoveRows(index(), pos, pos);
children_.erase(children_.cbegin() + pos);
model->endRemoveRows();
}
}
if(children_.empty()) { // no visible children, add a placeholder item to keep the row expanded
addPlaceHolderChild();
placeHolderChild_->displayName_ = DirTreeModel::tr("<No sub folders>");
}
}
void DirTreeModelItem::onFolderFilesChanged(std::vector<Fm::FileInfoPair> &changes) {
DirTreeModel* model = model_;
for(auto& changePair: changes) {
int pos;
auto& changedFile = changePair.first;
DirTreeModelItem* child = childFromName(changedFile->name().c_str(), &pos);
if(child) {
QModelIndex childIndex = child->index();
Q_EMIT model->dataChanged(childIndex, childIndex);
}
}
}
DirTreeModelItem* DirTreeModelItem::childFromName(const char* utf8_name, int* pos) {
int i = 0;
for(const auto item : children_) {
if(item->fileInfo_ && item->fileInfo_->name() == utf8_name) {
if(pos) {
*pos = i;
}
return item;
}
++i;
}
return nullptr;
}
DirTreeModelItem* DirTreeModelItem::childFromPath(Fm::FilePath path, bool recursive) const {
Q_ASSERT(path != nullptr);
Q_FOREACH(DirTreeModelItem* item, children_) {
// if(item->fileInfo_)
// qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_));
if(item->fileInfo_ && item->fileInfo_->path() == path) {
return item;
}
else if(recursive) {
DirTreeModelItem* child = item->childFromPath(std::move(path), true);
if(child) {
return child;
}
}
}
return nullptr;
}
void DirTreeModelItem::setShowHidden(bool show) {
if(show) {
// move all hidden children to visible list
for(auto item: hiddenChildren_) {
insertItem(item);
}
hiddenChildren_.clear();
// remove the placeholder if needed
if(children_.size() > 1) {
auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_);
if(it != children_.cend()) {
auto pos = it - children_.cbegin();
model_->beginRemoveRows(index(), pos, pos);
children_.erase(it);
delete placeHolderChild_;
model_->endRemoveRows();
placeHolderChild_ = nullptr;
}
}
// recursively show children of children, etc.
for(auto item: children_) {
item->setShowHidden(true);
}
}
else { // hide hidden folders
QModelIndex _index = index();
int pos = 0;
for(auto it = children_.begin(); it != children_.end(); ++pos) {
DirTreeModelItem* item = *it;
if(item->fileInfo_) {
if(item->fileInfo_->isHidden()) { // hidden folder
// remove from the model and add to the hiddenChildren_ list
model_->beginRemoveRows(_index, pos, pos);
it = children_.erase(it);
hiddenChildren_.push_back(item);
model_->endRemoveRows();
}
else { // visible folder, recursively filter its children
item->setShowHidden(show);
++it;
}
}
else {
++it;
}
}
if(children_.empty()) { // no visible children, add a placeholder item to keep the row expanded
addPlaceHolderChild();
placeHolderChild_->displayName_ = DirTreeModel::tr("<No sub folders>");
}
}
}
} // namespace Fm